diff --git a/CHANGELOG.md b/CHANGELOG.md index 37de175f..04d29596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Conformance fixture suite under `tests/fixtures/webhooks/` (13 happy-path event directories + 8 negative cases) for SDK conformance testing across language ports. +- Regenerated from the latest chat OpenAPI spec. New endpoints: `moderation.analyze`, + `moderation.bulk_action_appeals`, `moderation.get_setup_session`, + `moderation.upsert_setup_session`; `feeds.get_or_create_follow`, + `feeds.get_or_create_unfollow`, `feeds.get_user_interests`; `chat.create_segment`, + `chat.update_segment`, `chat.add_segment_targets`; `common.cancel_import_v2_task`; + `video.report_client_call_event`, together with the request/response models backing them. +- New webhook event types `moderation.image_analysis.complete` and + `moderation.text_analysis.complete`, with `ModerationImageAnalysisCompleteEvent` + and `ModerationTextAnalysisCompleteEvent` models. ### Changed @@ -82,6 +91,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Default HTTP transport now caps connections per host at `5` and closes idle sockets after `55.0s`. Previous default was httpx's `100` max-connections with `5.0s` keep-alive expiry. - No breaking changes. All existing webhook helpers (`verify_webhook_signature`, `parse_webhook_event`, `get_event_type`, event type constants) are preserved. +- `FlagResponse` now represents the full flag record (`created_at`, `updated_at`, + `target_message`, `target_user`, `user`, `reason`, `details`, `custom`, and related + fields). The moderation flag-action acknowledgement, which carries `item_id` and + `duration`, moved to the new `FlagItemResponse`; `moderation.flag()` now returns + `FlagItemResponse`. The `/api/v2/moderation/flag` wire response is unchanged, so code + reading `item_id`/`duration` off the parsed response is unaffected. Code referencing + the `FlagResponse` type for those two fields should switch to `FlagItemResponse`. +- `ChannelInput.config_overrides` and `ChannelDataUpdate.config_overrides` are now typed + as `ChannelConfigOverrides` (the override-specific field set) instead of `ChannelConfig`. +- `enabled` on `DeliveryReceiptsResponse`, `ReadReceiptsResponse`, and + `TypingIndicatorsResponse` is now a required `bool` (was `Optional[bool]`). +- `LLMRule.description`, `TargetResolution.bitrate`, and `TranslationSettings.languages` / + `TranslationSettings.enabled` are now optional. ### Deprecated diff --git a/getstream/chat/async_rest_client.py b/getstream/chat/async_rest_client.py index 37fe4a18..bbed3216 100644 --- a/getstream/chat/async_rest_client.py +++ b/getstream/chat/async_rest_client.py @@ -282,10 +282,11 @@ async def grouped_query_channels( self, limit: Optional[int] = None, user_id: Optional[str] = None, + groups: Optional[Dict[str, GroupedChannelsGroupRequest]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[GroupedQueryChannelsResponse]: json = GroupedQueryChannelsRequest( - limit=limit, user_id=user_id, user=user + limit=limit, user_id=user_id, groups=groups, user=user ).to_dict() return await self.post( "/api/v2/chat/channels/grouped", GroupedQueryChannelsResponse, json=json @@ -1657,6 +1658,30 @@ async def search( "/api/v2/chat/search", SearchResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.chat.create_segment") + async def create_segment( + self, + type: str, + all_sender_channels: Optional[bool] = None, + all_users: Optional[bool] = None, + description: Optional[str] = None, + id: Optional[str] = None, + name: Optional[str] = None, + filter: Optional[Dict[str, object]] = None, + ) -> StreamResponse[CreateSegmentResponse]: + json = CreateSegmentRequest( + type=type, + all_sender_channels=all_sender_channels, + all_users=all_users, + description=description, + id=id, + name=name, + filter=filter, + ).to_dict() + return await self.post( + "/api/v2/chat/segments", CreateSegmentResponse, json=json + ) + @telemetry.operation_name("getstream.api.chat.query_segments") async def query_segments( self, @@ -1691,6 +1716,42 @@ async def get_segment(self, id: str) -> StreamResponse[GetSegmentResponse]: "/api/v2/chat/segments/{id}", GetSegmentResponse, path_params=path_params ) + @telemetry.operation_name("getstream.api.chat.update_segment") + async def update_segment( + self, + id: str, + description: Optional[str] = None, + name: Optional[str] = None, + filter: Optional[Dict[str, object]] = None, + ) -> StreamResponse[UpdateSegmentResponse]: + path_params = { + "id": id, + } + json = UpdateSegmentRequest( + description=description, name=name, filter=filter + ).to_dict() + return await self.put( + "/api/v2/chat/segments/{id}", + UpdateSegmentResponse, + path_params=path_params, + json=json, + ) + + @telemetry.operation_name("getstream.api.chat.add_segment_targets") + async def add_segment_targets( + self, id: str, target_ids: List[str] + ) -> StreamResponse[Response]: + path_params = { + "id": id, + } + json = AddSegmentTargetsRequest(target_ids=target_ids).to_dict() + return await self.post( + "/api/v2/chat/segments/{id}/addtargets", + Response, + path_params=path_params, + json=json, + ) + @telemetry.operation_name("getstream.api.chat.delete_segment_targets") async def delete_segment_targets( self, id: str, target_ids: List[str] diff --git a/getstream/chat/rest_client.py b/getstream/chat/rest_client.py index 21279db1..75f1f08d 100644 --- a/getstream/chat/rest_client.py +++ b/getstream/chat/rest_client.py @@ -278,10 +278,11 @@ def grouped_query_channels( self, limit: Optional[int] = None, user_id: Optional[str] = None, + groups: Optional[Dict[str, GroupedChannelsGroupRequest]] = None, user: Optional[UserRequest] = None, ) -> StreamResponse[GroupedQueryChannelsResponse]: json = GroupedQueryChannelsRequest( - limit=limit, user_id=user_id, user=user + limit=limit, user_id=user_id, groups=groups, user=user ).to_dict() return self.post( "/api/v2/chat/channels/grouped", GroupedQueryChannelsResponse, json=json @@ -1643,6 +1644,28 @@ def search( "/api/v2/chat/search", SearchResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.chat.create_segment") + def create_segment( + self, + type: str, + all_sender_channels: Optional[bool] = None, + all_users: Optional[bool] = None, + description: Optional[str] = None, + id: Optional[str] = None, + name: Optional[str] = None, + filter: Optional[Dict[str, object]] = None, + ) -> StreamResponse[CreateSegmentResponse]: + json = CreateSegmentRequest( + type=type, + all_sender_channels=all_sender_channels, + all_users=all_users, + description=description, + id=id, + name=name, + filter=filter, + ).to_dict() + return self.post("/api/v2/chat/segments", CreateSegmentResponse, json=json) + @telemetry.operation_name("getstream.api.chat.query_segments") def query_segments( self, @@ -1677,6 +1700,42 @@ def get_segment(self, id: str) -> StreamResponse[GetSegmentResponse]: "/api/v2/chat/segments/{id}", GetSegmentResponse, path_params=path_params ) + @telemetry.operation_name("getstream.api.chat.update_segment") + def update_segment( + self, + id: str, + description: Optional[str] = None, + name: Optional[str] = None, + filter: Optional[Dict[str, object]] = None, + ) -> StreamResponse[UpdateSegmentResponse]: + path_params = { + "id": id, + } + json = UpdateSegmentRequest( + description=description, name=name, filter=filter + ).to_dict() + return self.put( + "/api/v2/chat/segments/{id}", + UpdateSegmentResponse, + path_params=path_params, + json=json, + ) + + @telemetry.operation_name("getstream.api.chat.add_segment_targets") + def add_segment_targets( + self, id: str, target_ids: List[str] + ) -> StreamResponse[Response]: + path_params = { + "id": id, + } + json = AddSegmentTargetsRequest(target_ids=target_ids).to_dict() + return self.post( + "/api/v2/chat/segments/{id}/addtargets", + Response, + path_params=path_params, + json=json, + ) + @telemetry.operation_name("getstream.api.chat.delete_segment_targets") def delete_segment_targets( self, id: str, target_ids: List[str] diff --git a/getstream/common/async_rest_client.py b/getstream/common/async_rest_client.py index 665f4fc5..5df0c77e 100644 --- a/getstream/common/async_rest_client.py +++ b/getstream/common/async_rest_client.py @@ -40,6 +40,7 @@ async def update_app( self, async_url_enrich_enabled: Optional[bool] = None, auto_translation_enabled: Optional[bool] = None, + before_message_send_hook_attempt_timeout_ms: Optional[int] = None, before_message_send_hook_url: Optional[str] = None, cdn_expiration_seconds: Optional[int] = None, channel_hide_members_only: Optional[bool] = None, @@ -56,6 +57,7 @@ async def update_app( migrate_permissions_to_v2: Optional[bool] = None, moderation_analytics_enabled: Optional[bool] = None, moderation_enabled: Optional[bool] = None, + moderation_onboarding_complete: Optional[bool] = None, moderation_s3_image_access_role_arn: Optional[str] = None, moderation_webhook_url: Optional[str] = None, multi_tenant_enabled: Optional[bool] = None, @@ -70,6 +72,7 @@ async def update_app( sqs_secret: Optional[str] = None, sqs_url: Optional[str] = None, user_response_time_enabled: Optional[bool] = None, + video_primary_use_case: Optional[str] = None, webhook_url: Optional[str] = None, allowed_flag_reasons: Optional[List[str]] = None, event_hooks: Optional[List[EventHook]] = None, @@ -95,6 +98,7 @@ async def update_app( json = UpdateAppRequest( async_url_enrich_enabled=async_url_enrich_enabled, auto_translation_enabled=auto_translation_enabled, + before_message_send_hook_attempt_timeout_ms=before_message_send_hook_attempt_timeout_ms, before_message_send_hook_url=before_message_send_hook_url, cdn_expiration_seconds=cdn_expiration_seconds, channel_hide_members_only=channel_hide_members_only, @@ -111,6 +115,7 @@ async def update_app( migrate_permissions_to_v2=migrate_permissions_to_v2, moderation_analytics_enabled=moderation_analytics_enabled, moderation_enabled=moderation_enabled, + moderation_onboarding_complete=moderation_onboarding_complete, moderation_s3_image_access_role_arn=moderation_s3_image_access_role_arn, moderation_webhook_url=moderation_webhook_url, multi_tenant_enabled=multi_tenant_enabled, @@ -125,6 +130,7 @@ async def update_app( sqs_secret=sqs_secret, sqs_url=sqs_url, user_response_time_enabled=user_response_time_enabled, + video_primary_use_case=video_primary_use_case, webhook_url=webhook_url, allowed_flag_reasons=allowed_flag_reasons, event_hooks=event_hooks, @@ -161,16 +167,20 @@ async def create_block_list( self, name: str, words: List[str], + is_confusable_folding_enabled: Optional[bool] = None, is_leet_check_enabled: Optional[bool] = None, is_plural_check_enabled: Optional[bool] = None, + is_substring_matching_enabled: Optional[bool] = None, team: Optional[str] = None, type: Optional[str] = None, ) -> StreamResponse[CreateBlockListResponse]: json = CreateBlockListRequest( name=name, words=words, + is_confusable_folding_enabled=is_confusable_folding_enabled, is_leet_check_enabled=is_leet_check_enabled, is_plural_check_enabled=is_plural_check_enabled, + is_substring_matching_enabled=is_substring_matching_enabled, team=team, type=type, ).to_dict() @@ -210,8 +220,10 @@ async def get_block_list( async def update_block_list( self, name: str, + is_confusable_folding_enabled: Optional[bool] = None, is_leet_check_enabled: Optional[bool] = None, is_plural_check_enabled: Optional[bool] = None, + is_substring_matching_enabled: Optional[bool] = None, team: Optional[str] = None, words: Optional[List[str]] = None, ) -> StreamResponse[UpdateBlockListResponse]: @@ -219,8 +231,10 @@ async def update_block_list( "name": name, } json = UpdateBlockListRequest( + is_confusable_folding_enabled=is_confusable_folding_enabled, is_leet_check_enabled=is_leet_check_enabled, is_plural_check_enabled=is_plural_check_enabled, + is_substring_matching_enabled=is_substring_matching_enabled, team=team, words=words, ).to_dict() @@ -304,6 +318,7 @@ async def create_device( self, id: str, push_provider: str, + hardware_id: Optional[str] = None, push_provider_name: Optional[str] = None, user_id: Optional[str] = None, voip_token: Optional[bool] = None, @@ -312,6 +327,7 @@ async def create_device( json = CreateDeviceRequest( id=id, push_provider=push_provider, + hardware_id=hardware_id, push_provider_name=push_provider_name, user_id=user_id, voip_token=voip_token, @@ -524,6 +540,19 @@ async def get_import_v2_task( "/api/v2/imports/v2/{id}", GetImportV2TaskResponse, path_params=path_params ) + @telemetry.operation_name("getstream.api.common.cancel_import_v2_task") + async def cancel_import_v2_task( + self, id: str + ) -> StreamResponse[CancelImportV2TaskResponse]: + path_params = { + "id": id, + } + return await self.post( + "/api/v2/imports/v2/{id}/cancel", + CancelImportV2TaskResponse, + path_params=path_params, + ) + @telemetry.operation_name("getstream.api.common.get_import") async def get_import(self, id: str) -> StreamResponse[GetImportResponse]: path_params = { diff --git a/getstream/common/rest_client.py b/getstream/common/rest_client.py index 6e52b407..92abd1f2 100644 --- a/getstream/common/rest_client.py +++ b/getstream/common/rest_client.py @@ -40,6 +40,7 @@ def update_app( self, async_url_enrich_enabled: Optional[bool] = None, auto_translation_enabled: Optional[bool] = None, + before_message_send_hook_attempt_timeout_ms: Optional[int] = None, before_message_send_hook_url: Optional[str] = None, cdn_expiration_seconds: Optional[int] = None, channel_hide_members_only: Optional[bool] = None, @@ -56,6 +57,7 @@ def update_app( migrate_permissions_to_v2: Optional[bool] = None, moderation_analytics_enabled: Optional[bool] = None, moderation_enabled: Optional[bool] = None, + moderation_onboarding_complete: Optional[bool] = None, moderation_s3_image_access_role_arn: Optional[str] = None, moderation_webhook_url: Optional[str] = None, multi_tenant_enabled: Optional[bool] = None, @@ -70,6 +72,7 @@ def update_app( sqs_secret: Optional[str] = None, sqs_url: Optional[str] = None, user_response_time_enabled: Optional[bool] = None, + video_primary_use_case: Optional[str] = None, webhook_url: Optional[str] = None, allowed_flag_reasons: Optional[List[str]] = None, event_hooks: Optional[List[EventHook]] = None, @@ -95,6 +98,7 @@ def update_app( json = UpdateAppRequest( async_url_enrich_enabled=async_url_enrich_enabled, auto_translation_enabled=auto_translation_enabled, + before_message_send_hook_attempt_timeout_ms=before_message_send_hook_attempt_timeout_ms, before_message_send_hook_url=before_message_send_hook_url, cdn_expiration_seconds=cdn_expiration_seconds, channel_hide_members_only=channel_hide_members_only, @@ -111,6 +115,7 @@ def update_app( migrate_permissions_to_v2=migrate_permissions_to_v2, moderation_analytics_enabled=moderation_analytics_enabled, moderation_enabled=moderation_enabled, + moderation_onboarding_complete=moderation_onboarding_complete, moderation_s3_image_access_role_arn=moderation_s3_image_access_role_arn, moderation_webhook_url=moderation_webhook_url, multi_tenant_enabled=multi_tenant_enabled, @@ -125,6 +130,7 @@ def update_app( sqs_secret=sqs_secret, sqs_url=sqs_url, user_response_time_enabled=user_response_time_enabled, + video_primary_use_case=video_primary_use_case, webhook_url=webhook_url, allowed_flag_reasons=allowed_flag_reasons, event_hooks=event_hooks, @@ -161,16 +167,20 @@ def create_block_list( self, name: str, words: List[str], + is_confusable_folding_enabled: Optional[bool] = None, is_leet_check_enabled: Optional[bool] = None, is_plural_check_enabled: Optional[bool] = None, + is_substring_matching_enabled: Optional[bool] = None, team: Optional[str] = None, type: Optional[str] = None, ) -> StreamResponse[CreateBlockListResponse]: json = CreateBlockListRequest( name=name, words=words, + is_confusable_folding_enabled=is_confusable_folding_enabled, is_leet_check_enabled=is_leet_check_enabled, is_plural_check_enabled=is_plural_check_enabled, + is_substring_matching_enabled=is_substring_matching_enabled, team=team, type=type, ).to_dict() @@ -210,8 +220,10 @@ def get_block_list( def update_block_list( self, name: str, + is_confusable_folding_enabled: Optional[bool] = None, is_leet_check_enabled: Optional[bool] = None, is_plural_check_enabled: Optional[bool] = None, + is_substring_matching_enabled: Optional[bool] = None, team: Optional[str] = None, words: Optional[List[str]] = None, ) -> StreamResponse[UpdateBlockListResponse]: @@ -219,8 +231,10 @@ def update_block_list( "name": name, } json = UpdateBlockListRequest( + is_confusable_folding_enabled=is_confusable_folding_enabled, is_leet_check_enabled=is_leet_check_enabled, is_plural_check_enabled=is_plural_check_enabled, + is_substring_matching_enabled=is_substring_matching_enabled, team=team, words=words, ).to_dict() @@ -304,6 +318,7 @@ def create_device( self, id: str, push_provider: str, + hardware_id: Optional[str] = None, push_provider_name: Optional[str] = None, user_id: Optional[str] = None, voip_token: Optional[bool] = None, @@ -312,6 +327,7 @@ def create_device( json = CreateDeviceRequest( id=id, push_provider=push_provider, + hardware_id=hardware_id, push_provider_name=push_provider_name, user_id=user_id, voip_token=voip_token, @@ -512,6 +528,19 @@ def get_import_v2_task(self, id: str) -> StreamResponse[GetImportV2TaskResponse] "/api/v2/imports/v2/{id}", GetImportV2TaskResponse, path_params=path_params ) + @telemetry.operation_name("getstream.api.common.cancel_import_v2_task") + def cancel_import_v2_task( + self, id: str + ) -> StreamResponse[CancelImportV2TaskResponse]: + path_params = { + "id": id, + } + return self.post( + "/api/v2/imports/v2/{id}/cancel", + CancelImportV2TaskResponse, + path_params=path_params, + ) + @telemetry.operation_name("getstream.api.common.get_import") def get_import(self, id: str) -> StreamResponse[GetImportResponse]: path_params = { diff --git a/getstream/feeds/feeds.py b/getstream/feeds/feeds.py index 40da7d45..e53c72f7 100644 --- a/getstream/feeds/feeds.py +++ b/getstream/feeds/feeds.py @@ -33,6 +33,7 @@ def get_or_create( id_around: Optional[str] = None, limit: Optional[int] = None, next: Optional[str] = None, + overwrite_interest_weights: Optional[bool] = None, prev: Optional[str] = None, user_id: Optional[str] = None, view: Optional[str] = None, @@ -54,6 +55,7 @@ def get_or_create( id_around=id_around, limit=limit, next=next, + overwrite_interest_weights=overwrite_interest_weights, prev=prev, user_id=user_id, view=view, diff --git a/getstream/feeds/rest_client.py b/getstream/feeds/rest_client.py index 46c3de3d..89122ea0 100644 --- a/getstream/feeds/rest_client.py +++ b/getstream/feeds/rest_client.py @@ -1249,6 +1249,7 @@ def get_or_create_feed( id_around: Optional[str] = None, limit: Optional[int] = None, next: Optional[str] = None, + overwrite_interest_weights: Optional[bool] = None, prev: Optional[str] = None, user_id: Optional[str] = None, view: Optional[str] = None, @@ -1272,6 +1273,7 @@ def get_or_create_feed( id_around=id_around, limit=limit, next=next, + overwrite_interest_weights=overwrite_interest_weights, prev=prev, user_id=user_id, view=view, @@ -2023,6 +2025,38 @@ def reject_follow( "/api/v2/feeds/follows/reject", RejectFollowResponse, json=json ) + @telemetry.operation_name("getstream.api.feeds.get_or_create_follow") + def get_or_create_follow( + self, + source: str, + target: str, + activity_copy_limit: Optional[int] = None, + copy_custom_to_notification: Optional[bool] = None, + create_notification_activity: Optional[bool] = None, + create_users: Optional[bool] = None, + enrich_own_fields: Optional[bool] = None, + push_preference: Optional[str] = None, + skip_push: Optional[bool] = None, + status: Optional[str] = None, + custom: Optional[Dict[str, object]] = None, + ) -> StreamResponse[GetOrCreateFollowResponse]: + json = FollowRequest( + source=source, + target=target, + activity_copy_limit=activity_copy_limit, + copy_custom_to_notification=copy_custom_to_notification, + create_notification_activity=create_notification_activity, + create_users=create_users, + enrich_own_fields=enrich_own_fields, + push_preference=push_preference, + skip_push=skip_push, + status=status, + custom=custom, + ).to_dict() + return self.post( + "/api/v2/feeds/follows/upsert", GetOrCreateFollowResponse, json=json + ) + @telemetry.operation_name("getstream.api.feeds.unfollow") def unfollow( self, @@ -2183,6 +2217,26 @@ def get_or_create_unfollows( "/api/v2/feeds/unfollow/batch/upsert", UnfollowBatchResponse, json=json ) + @telemetry.operation_name("getstream.api.feeds.get_or_create_unfollow") + def get_or_create_unfollow( + self, + source: str, + target: str, + delete_notification_activity: Optional[bool] = None, + enrich_own_fields: Optional[bool] = None, + keep_history: Optional[bool] = None, + ) -> StreamResponse[GetOrCreateUnfollowResponse]: + json = GetOrCreateUnfollowRequest( + source=source, + target=target, + delete_notification_activity=delete_notification_activity, + enrich_own_fields=enrich_own_fields, + keep_history=keep_history, + ).to_dict() + return self.post( + "/api/v2/feeds/unfollow/upsert", GetOrCreateUnfollowResponse, json=json + ) + @telemetry.operation_name("getstream.api.feeds.delete_feed_user_data") def delete_feed_user_data( self, user_id: str, hard_delete: Optional[bool] = None @@ -2210,3 +2264,18 @@ def export_feed_user_data( ExportFeedUserDataResponse, path_params=path_params, ) + + @telemetry.operation_name("getstream.api.feeds.get_user_interests") + def get_user_interests( + self, user_id: str, limit: Optional[int] = None + ) -> StreamResponse[GetUserInterestsResponse]: + query_params = build_query_param(**{"limit": limit}) + path_params = { + "user_id": user_id, + } + return self.get( + "/api/v2/feeds/users/{user_id}/interests", + GetUserInterestsResponse, + query_params=query_params, + path_params=path_params, + ) diff --git a/getstream/models/__init__.py b/getstream/models/__init__.py index 07739125..46ed1ff5 100644 --- a/getstream/models/__init__.py +++ b/getstream/models/__init__.py @@ -1592,6 +1592,12 @@ class AddReactionResponse(DataClassJsonMixin): ) +@dataclass +class AddSegmentTargetsRequest(DataClassJsonMixin): + # Target IDs + target_ids: List[str] = dc_field(metadata=dc_config(field_name="target_ids")) + + @dataclass class AddUserGroupMembersRequest(DataClassJsonMixin): # List of user IDs to add as members @@ -1678,6 +1684,125 @@ class AggregationConfig(DataClassJsonMixin): ) +@dataclass +class AnalyzeImageField(DataClassJsonMixin): + # Per-image action: keep | flag | remove. + action: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="action") + ) + # Highest confidence (0–1) across detected classifications + sub-classifications. Convenience aggregate over the nested values in `classifications`. + confidence: Optional[float] = dc_field( + default=None, metadata=dc_config(field_name="confidence") + ) + # Set when moderation couldn't be determined for this image — action is absent. + error: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="error") + ) + # Echo of `content_ids[label]` when supplied on the request; omitted otherwise. + id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) + # Hierarchical list of L1 (parent) classifications. Each entry: `name`, `confidence` (0–1), and nested `subclassifications` (L2 leaves with their own confidence). Resolved against the app's effective taxonomy (custom taxonomy when configured, otherwise the standard Bodyguard catalogue). + classifications: "Optional[List[Classification]]" = dc_field( + default=None, metadata=dc_config(field_name="classifications") + ) + # Flat list of Bodyguard OCR text-moderation labels on the image's extracted text (e.g. VULGARITY, PII). Each entry: `name` + `severity`. Populated when BG's OCR pipeline returned non-empty results for this image. + ocr_classifications: "Optional[List[Classification]]" = dc_field( + default=None, metadata=dc_config(field_name="ocr_classifications") + ) + + +@dataclass +class AnalyzeRequest(DataClassJsonMixin): + # When true, the response carries no verdicts (status `pending`) and per-modality results arrive via `moderation.text_analysis.complete` and `moderation.image_analysis.complete` webhooks. Image moderation runs on a background worker; text moderation runs synchronously and is then delivered via webhook. + async_response: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="async_response") + ) + # Moderation policy key. Optional in stateful mode, required in stateless mode. + config_key: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="config_key") + ) + # Original timestamp when the content was produced. Used as the `published_at` timestamp on per-content log entries that surface in `matched_contents` on aggregation-rule webhooks. + content_published_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="content_published_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + # ID of the user who created the content. Required with entity_type + entity_id; omit all three for stateless mode. + entity_creator_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_creator_id") + ) + # Caller-supplied content identifier. Required with entity_type + entity_creator_id; omit all three for stateless mode. + entity_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_id") + ) + # Caller-defined entity type. Required with entity_id + entity_creator_id; omit all three for stateless mode. + entity_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_type") + ) + user_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="user_id") + ) + # Optional map from a content label (either a `texts` key or an `image:` multipart label) to a caller-supplied per-instance identifier. Echoed on per-field verdicts and surfaced in `matched_contents` when an aggregation rule fires. + content_ids: "Optional[Dict[str, str]]" = dc_field( + default=None, metadata=dc_config(field_name="content_ids") + ) + # Arbitrary metadata surfaced in the dashboard. + custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="custom") + ) + # Named text fields to moderate, keyed by caller label (e.g. title, description). + texts: "Optional[Dict[str, str]]" = dc_field( + default=None, metadata=dc_config(field_name="texts") + ) + user: "Optional[UserRequest]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + + +@dataclass +class AnalyzeResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + # Always `complete` — /analyze is sync-only and the full verdict is in the response. + status: str = dc_field(metadata=dc_config(field_name="status")) + # Per-image moderation verdicts keyed by caller label. + images: "Optional[Dict[str, AnalyzeImageField]]" = dc_field( + default=None, metadata=dc_config(field_name="images") + ) + # Per-text-field moderation verdicts keyed by caller label. + texts: "Optional[Dict[str, AnalyzeTextField]]" = dc_field( + default=None, metadata=dc_config(field_name="texts") + ) + + +@dataclass +class AnalyzeTextField(DataClassJsonMixin): + # Per-field action: keep | flag | remove. + action: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="action") + ) + # Set when moderation couldn't be determined for this field — action is absent. + error: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="error") + ) + # Echo of `content_ids[label]` when supplied on the request; omitted otherwise. + id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) + # Detected language code. + language: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="language") + ) + # Aggregate severity across the field: LOW | MEDIUM | HIGH | CRITICAL. + severity: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="severity") + ) + # Flat list of detected Bodyguard text labels (e.g. INSULT, VULGARITY). Each entry carries `name` and `severity`. + classifications: "Optional[List[Classification]]" = dc_field( + default=None, metadata=dc_config(field_name="classifications") + ) + + @dataclass class AppResponseFields(DataClassJsonMixin): allow_multi_user_devices: bool = dc_field( @@ -1791,9 +1916,16 @@ class AppResponseFields(DataClassJsonMixin): push_notifications: "PushNotificationFields" = dc_field( metadata=dc_config(field_name="push_notifications") ) + before_message_send_hook_attempt_timeout_ms: Optional[int] = dc_field( + default=None, + metadata=dc_config(field_name="before_message_send_hook_attempt_timeout_ms"), + ) before_message_send_hook_url: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="before_message_send_hook_url") ) + moderation_onboarding_complete: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="moderation_onboarding_complete") + ) moderation_s3_image_access_role_arn: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="moderation_s3_image_access_role_arn"), @@ -1807,6 +1939,9 @@ class AppResponseFields(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) + video_primary_use_case: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="video_primary_use_case") + ) allowed_flag_reasons: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="allowed_flag_reasons") ) @@ -1915,17 +2050,63 @@ class AppealItemResponse(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + # Text severity level assigned by the AI provider + ai_text_severity: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="ai_text_severity") + ) + # CID of the channel the entity belongs to, if applicable + channel_cid: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_cid") + ) + # Moderation policy key that was applied + config_key: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="config_key") + ) # Decision Reason of the Appeal Item decision_reason: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="decision_reason") ) + # Action recommended by the automated moderation system (e.g. flag, remove, shadow) + recommended_action: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="recommended_action") + ) + # ID of the review queue item linked to this appeal, if the appeal was submitted with one + review_queue_item_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="review_queue_item_id") + ) + # Overall content severity score (1–100) + severity: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="severity") + ) + # Full chronological history of all moderation actions on the review queue item + actions: "Optional[List[ActionLogResponse]]" = dc_field( + default=None, metadata=dc_config(field_name="actions") + ) # Attachments(e.g. Images) of the Appeal Item attachments: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="attachments") ) + # Classification labels from automated and manual review + flag_labels: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="flag_labels") + ) + # Types of flags applied to the entity (e.g. user_report, bodyguard) + flag_types: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="flag_types") + ) + # Per-provider flag records explaining why the action was taken + flags: "Optional[List[ModerationFlagResponse]]" = dc_field( + default=None, metadata=dc_config(field_name="flags") + ) entity_content: "Optional[ModerationPayload]" = dc_field( default=None, metadata=dc_config(field_name="entity_content") ) + moderation_action: "Optional[ActionLogResponse]" = dc_field( + default=None, metadata=dc_config(field_name="moderation_action") + ) + original_moderation_action: "Optional[ActionLogResponse]" = dc_field( + default=None, metadata=dc_config(field_name="original_moderation_action") + ) user: "Optional[UserResponse]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -1967,6 +2148,10 @@ class AppealRequest(DataClassJsonMixin): entity_id: str = dc_field(metadata=dc_config(field_name="entity_id")) # Type of entity being appealed (e.g., message, user) entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) + # ID of the review queue item (flagged message) that triggered the ban. Applicable only for user ban appeals. + review_queue_item_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="review_queue_item_id") + ) user_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="user_id") ) @@ -2103,8 +2288,7 @@ 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.bulk_image_moderation.error", - metadata=dc_config(field_name="type"), + default="export.moderation_logs.error", metadata=dc_config(field_name="type") ) received_at: Optional[datetime] = dc_field( default=None, @@ -2669,12 +2853,18 @@ class BlockListOptions(DataClassJsonMixin): @dataclass class BlockListResponse(DataClassJsonMixin): + is_confusable_folding_enabled: bool = dc_field( + metadata=dc_config(field_name="is_confusable_folding_enabled") + ) is_leet_check_enabled: bool = dc_field( metadata=dc_config(field_name="is_leet_check_enabled") ) is_plural_check_enabled: bool = dc_field( metadata=dc_config(field_name="is_plural_check_enabled") ) + is_substring_matching_enabled: bool = dc_field( + metadata=dc_config(field_name="is_substring_matching_enabled") + ) # Block list name name: str = dc_field(metadata=dc_config(field_name="name")) # Block list type. One of: regex, domain, domain_allowlist, email, email_allowlist, word @@ -3109,6 +3299,60 @@ class BrowserDataResponse(DataClassJsonMixin): ) +@dataclass +class BulkActionAppealsRequest(DataClassJsonMixin): + # Action to apply: unban, restore, unblock, mark_reviewed, or reject_appeal + action_type: str = dc_field(metadata=dc_config(field_name="action_type")) + # List of appeal UUIDs to process + appeal_ids: List[str] = dc_field(metadata=dc_config(field_name="appeal_ids")) + user_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="user_id") + ) + mark_reviewed: "Optional[MarkReviewedRequestPayload]" = dc_field( + default=None, metadata=dc_config(field_name="mark_reviewed") + ) + reject_appeal: "Optional[RejectAppealRequestPayload]" = dc_field( + default=None, metadata=dc_config(field_name="reject_appeal") + ) + restore: "Optional[RestoreActionRequestPayload]" = dc_field( + default=None, metadata=dc_config(field_name="restore") + ) + unban: "Optional[UnbanActionRequestPayload]" = dc_field( + default=None, metadata=dc_config(field_name="unban") + ) + unblock: "Optional[UnblockActionRequestPayload]" = dc_field( + default=None, metadata=dc_config(field_name="unblock") + ) + user: "Optional[UserRequest]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + + +@dataclass +class BulkActionAppealsResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + # Appeals that could not be processed, with per-item error messages + errors: "List[BulkAppealError]" = dc_field(metadata=dc_config(field_name="errors")) + # Successfully processed appeals + results: "List[BulkAppealResult]" = dc_field( + metadata=dc_config(field_name="results") + ) + + +@dataclass +class BulkAppealError(DataClassJsonMixin): + appeal_id: str = dc_field(metadata=dc_config(field_name="appeal_id")) + error: str = dc_field(metadata=dc_config(field_name="error")) + + +@dataclass +class BulkAppealResult(DataClassJsonMixin): + appeal_id: str = dc_field(metadata=dc_config(field_name="appeal_id")) + appeal_item: "Optional[AppealItemResponse]" = dc_field( + default=None, metadata=dc_config(field_name="appeal_item") + ) + + @dataclass class BulkDeleteActionConfigRequest(DataClassJsonMixin): # UUIDs of the action configs to delete @@ -4691,6 +4935,9 @@ class CallStatsParticipantCounts(DataClassJsonMixin): average_latency_ms: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="average_latency_ms") ) + avg_user_rating: Optional[float] = dc_field( + default=None, metadata=dc_config(field_name="avg_user_rating") + ) call_event_count: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="call_event_count") ) @@ -4700,6 +4947,9 @@ class CallStatsParticipantCounts(DataClassJsonMixin): max_freezes_duration_ms: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="max_freezes_duration_ms") ) + min_user_rating: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="min_user_rating") + ) total_participant_duration: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="total_participant_duration") ) @@ -5347,6 +5597,17 @@ class CampaignStatsResponse(DataClassJsonMixin): stats_users_sent: int = dc_field(metadata=dc_config(field_name="stats_users_sent")) +@dataclass +class CancelImportV2TaskRequest(DataClassJsonMixin): + pass + + +@dataclass +class CancelImportV2TaskResponse(DataClassJsonMixin): + # Duration of the request in milliseconds + duration: str = dc_field(metadata=dc_config(field_name="duration")) + + @dataclass class CastPollVoteRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( @@ -5588,6 +5849,70 @@ class ChannelConfig(DataClassJsonMixin): ) +@dataclass +class ChannelConfigOverrides(DataClassJsonMixin): + blocklist: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="blocklist") + ) + blocklist_behavior: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="blocklist_behavior") + ) + # Enable/disable message counting + count_messages: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="count_messages") + ) + # Overrides max message length + max_message_length: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="max_message_length") + ) + # Overrides the push notification level for this channel + push_level: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="push_level") + ) + # Enables message quotes + quotes: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="quotes") + ) + # Enables or disables reactions + reactions: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="reactions") + ) + # Enables message replies (threads) + replies: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="replies") + ) + # Enable/disable shared locations + shared_locations: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="shared_locations") + ) + # Enables or disables typing events + typing_events: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="typing_events") + ) + # Enables or disables file uploads + uploads: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="uploads") + ) + # Enables or disables URL enrichment + url_enrichment: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="url_enrichment") + ) + # Enable/disable user message reminders + user_message_reminders: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="user_message_reminders") + ) + # List of commands that channel supports + commands: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="commands") + ) + chat_preferences: "Optional[ChatPreferences]" = dc_field( + default=None, metadata=dc_config(field_name="chat_preferences") + ) + grants: "Optional[Dict[str, List[str]]]" = dc_field( + default=None, metadata=dc_config(field_name="grants") + ) + + @dataclass class ChannelConfigWithInfo(DataClassJsonMixin): automod: str = dc_field(metadata=dc_config(field_name="automod")) @@ -5741,7 +6066,7 @@ class ChannelDataUpdate(DataClassJsonMixin): default=None, metadata=dc_config(field_name="frozen") ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - config_overrides: "Optional[ChannelConfig]" = dc_field( + config_overrides: "Optional[ChannelConfigOverrides]" = dc_field( default=None, metadata=dc_config(field_name="config_overrides") ) custom: Optional[Dict[str, object]] = dc_field( @@ -5984,7 +6309,7 @@ class ChannelInput(DataClassJsonMixin): members: "Optional[List[ChannelMemberRequest]]" = dc_field( default=None, metadata=dc_config(field_name="members") ) - config_overrides: "Optional[ChannelConfig]" = dc_field( + config_overrides: "Optional[ChannelConfigOverrides]" = dc_field( default=None, metadata=dc_config(field_name="config_overrides") ) created_by: "Optional[UserRequest]" = dc_field( @@ -6253,6 +6578,7 @@ class ChannelOwnCapability: CAST_POLL_VOTE: Final[ChannelOwnCapabilityType] = "cast-poll-vote" CONNECT_EVENTS: Final[ChannelOwnCapabilityType] = "connect-events" CREATE_ATTACHMENT: Final[ChannelOwnCapabilityType] = "create-attachment" + CREATE_MENTION: Final[ChannelOwnCapabilityType] = "create-mention" DELETE_ANY_MESSAGE: Final[ChannelOwnCapabilityType] = "delete-any-message" DELETE_CHANNEL: Final[ChannelOwnCapabilityType] = "delete-channel" DELETE_OWN_MESSAGE: Final[ChannelOwnCapabilityType] = "delete-own-message" @@ -6262,6 +6588,10 @@ class ChannelOwnCapability: JOIN_CHANNEL: Final[ChannelOwnCapabilityType] = "join-channel" LEAVE_CHANNEL: Final[ChannelOwnCapabilityType] = "leave-channel" MUTE_CHANNEL: Final[ChannelOwnCapabilityType] = "mute-channel" + NOTIFY_CHANNEL: Final[ChannelOwnCapabilityType] = "notify-channel" + NOTIFY_GROUP: Final[ChannelOwnCapabilityType] = "notify-group" + NOTIFY_HERE: Final[ChannelOwnCapabilityType] = "notify-here" + NOTIFY_ROLE: Final[ChannelOwnCapabilityType] = "notify-role" PIN_MESSAGE: Final[ChannelOwnCapabilityType] = "pin-message" QUERY_POLL_VOTES: Final[ChannelOwnCapabilityType] = "query-poll-votes" QUOTE_MESSAGE: Final[ChannelOwnCapabilityType] = "quote-message" @@ -7077,6 +7407,9 @@ class ChatMessageResponse(DataClassJsonMixin): mentioned_group_ids: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="mentioned_group_ids") ) + mentioned_groups: "Optional[List[UserGroupResponse]]" = dc_field( + default=None, metadata=dc_config(field_name="mentioned_groups") + ) mentioned_roles: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="mentioned_roles") ) @@ -7571,28 +7904,158 @@ class CheckSQSRequest(DataClassJsonMixin): sqs_key: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="sqs_key") ) - # AWS SQS key secret - sqs_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_secret") + # AWS SQS key secret + sqs_secret: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="sqs_secret") + ) + # AWS SQS endpoint URL + sqs_url: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="sqs_url") + ) + + +@dataclass +class CheckSQSResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + # Validation result. One of: ok, error + status: str = dc_field(metadata=dc_config(field_name="status")) + # Error text + error: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="error") + ) + # Error data + data: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="data") + ) + + +@dataclass +class Classification(DataClassJsonMixin): + name: str = dc_field(metadata=dc_config(field_name="name")) + confidence: Optional[float] = dc_field( + default=None, metadata=dc_config(field_name="confidence") + ) + severity: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="severity") + ) + subclassifications: "Optional[List[Classification]]" = dc_field( + default=None, metadata=dc_config(field_name="subclassifications") + ) + + +@dataclass +class ClientEvent(DataClassJsonMixin): + # Call session ID associated with the attempt. Required on every event except CoordinatorJoin initiation and CoordinatorJoin failure (where the call session is not yet established); optional on MediaDevicePermission. + call_session_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="call_session_id") + ) + # Camera permission status: INITIATED, FAILED, GRANTED, or NOT_INITIATED. Required on every MediaDevicePermission event. + camera_permission_status: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="camera_permission_status") + ) + # UUID generated by the client and shared across every event of the same coordinator connection. Required on every event except JoinInitiated, which is reported before a coordinator connection exists. + coordinator_connect_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="coordinator_connect_id") + ) + # Milliseconds elapsed between the stage attempt's initiation and this event. + elapsed_time: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="elapsed_time") + ) + # Whether the event marks the start (initiated) or resolution (completed) of a stage attempt, or another event-specific value + event_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="event_type") + ) + # Terminal state of the peer connection. Required on PeerConnectionConnect failure. + ice_state: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="ice_state") + ) + # Call ID associated with the event. Required on every stage except CoordinatorWS, where it is optional. + id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) + # UUID generated by the client and shared across JoinInitiated and the join-lifecycle events (CoordinatorJoin, WSJoin, PeerConnectionConnect) of the same overall join attempt. Required on every join event except CoordinatorWS, which is reported before a join attempt is established. + join_attempt_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="join_attempt_id") + ) + # Microphone permission status: INITIATED, FAILED, GRANTED, or NOT_INITIATED. Required on every MediaDevicePermission event. + microphone_permission_status: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="microphone_permission_status") + ) + # Resolution of a completed event: success or failure. Required on completed join events; forbidden on initiated join events. + outcome: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="outcome") + ) + # Which peer connection a PeerConnectionConnect event reports on: publish or subscribe. Required on every PeerConnectionConnect event. + peer_connection: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="peer_connection") + ) + # UTC timestamp at which the ICE connection was established earlier in the session, when applicable + previously_connected_timestamp: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="previously_connected_timestamp", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + # Total in-stage retries the client made before resolving (0–1000). Required on completed join events. + retry_count_attempt: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="retry_count_attempt") + ) + # Failure code string. Required on CoordinatorJoin, CoordinatorWS, WSJoin, and PeerConnectionConnect failure. + retry_failure_code: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="retry_failure_code") + ) + # Failure reason string. Required on CoordinatorJoin, CoordinatorWS, WSJoin, and PeerConnectionConnect failure. + retry_failure_reason: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="retry_failure_reason") + ) + # Screen-share permission status: INITIATED, FAILED, GRANTED, or NOT_INITIATED. Optional on MediaDevicePermission events. + screen_share_status: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="screen_share_status") + ) + # Version of the client SDK + sdk_version: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="sdk_version") + ) + # Identifier of the SFU the client was attempting to connect to. Required on WSJoin and PeerConnectionConnect failure, and on FirstAudioFrame and FirstVideoFrame. + sfu_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="sfu_id") + ) + # Discriminator identifying the event kind. JoinInitiated marks the start of a join attempt; join-lifecycle events use CoordinatorJoin, CoordinatorWS, WSJoin, or PeerConnectionConnect; media-readiness events use FirstAudioFrame or FirstVideoFrame; MediaDevicePermission reports device permission results; other values denote generic client events. + stage: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="stage") + ) + # UUID generated by the client at initiation. Identical on the matching completion event. Absent on JoinInitiated. + stage_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="stage_id") + ) + # UTC timestamp at which the event was recorded + timestamp: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="timestamp", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + # Identifier of the media track the frame belongs to. Required on FirstVideoFrame; optional on FirstAudioFrame. + track_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="track_id") ) - # AWS SQS endpoint URL - sqs_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="sqs_url") + # Call type associated with the event. Required on every stage except CoordinatorWS, where it is optional. + type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) + # User agent string of the client SDK + user_agent: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="user_agent") ) - - -@dataclass -class CheckSQSResponse(DataClassJsonMixin): - duration: str = dc_field(metadata=dc_config(field_name="duration")) - # Validation result. One of: ok, error - status: str = dc_field(metadata=dc_config(field_name="status")) - # Error text - error: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="error") + # ID of the user the event was recorded for + user_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="user_id") ) - # Error data - data: Optional[Dict[str, object]] = dc_field( - default=None, metadata=dc_config(field_name="data") + # Whether the ICE connection had been established earlier in the same session. Required on every PeerConnectionConnect event so reconnects can be distinguished from fresh connects. + was_previously_connected: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="was_previously_connected") ) @@ -7632,6 +8095,9 @@ class ClosedCaptionRuleParameters(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") + ) harm_labels: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="harm_labels") ) @@ -8243,6 +8709,9 @@ class ConfigResponse(DataClassJsonMixin): block_list_config: "Optional[BlockListConfig]" = dc_field( default=None, metadata=dc_config(field_name="block_list_config") ) + flood_config: "Optional[FloodConfig]" = dc_field( + default=None, metadata=dc_config(field_name="flood_config") + ) llm_config: "Optional[LLMConfig]" = dc_field( default=None, metadata=dc_config(field_name="llm_config") ) @@ -8264,6 +8733,32 @@ class ContentCountRuleParameters(DataClassJsonMixin): ) +@dataclass +class ContentCustomPropertyCountParameters(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") + ) + 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 ContentCustomPropertyParameters(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 CoordinatesResponse(DataClassJsonMixin): # Latitude coordinate @@ -8291,12 +8786,18 @@ class CreateBlockListRequest(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) # List of words to block words: List[str] = dc_field(metadata=dc_config(field_name="words")) + is_confusable_folding_enabled: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="is_confusable_folding_enabled") + ) is_leet_check_enabled: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="is_leet_check_enabled") ) is_plural_check_enabled: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="is_plural_check_enabled") ) + is_substring_matching_enabled: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="is_substring_matching_enabled") + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) # Block list type. One of: regex, domain, domain_allowlist, email, email_allowlist, word type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) @@ -8693,6 +9194,10 @@ class CreateDeviceRequest(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) # Push provider push_provider: str = dc_field(metadata=dc_config(field_name="push_provider")) + # Stable physical device identifier used to deduplicate pushes across push providers (e.g. APNs VoIP and Firebase on the same iOS device). Distinct from 'id', which is the push token. + hardware_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="hardware_id") + ) # Push provider name push_provider_name: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="push_provider_name") @@ -9062,6 +9567,41 @@ class CreateSIPTrunkResponse(DataClassJsonMixin): ) +@dataclass +class CreateSegmentRequest(DataClassJsonMixin): + # The type of the segment + type: str = dc_field(metadata=dc_config(field_name="type")) + # If true, all sender channels are included in the segment + all_sender_channels: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="all_sender_channels") + ) + # If true, all users are included in the segment + all_users: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="all_users") + ) + # The description of the segment (max 256 characters) + description: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="description") + ) + # The ID of the segment + id: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="id")) + # The name of the segment (max 128 characters) + name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) + # Filter to apply to the query + filter: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="filter") + ) + + +@dataclass +class CreateSegmentResponse(DataClassJsonMixin): + # Duration of the request in milliseconds + duration: str = dc_field(metadata=dc_config(field_name="duration")) + segment: "Optional[SegmentResponse]" = dc_field( + default=None, metadata=dc_config(field_name="segment") + ) + + @dataclass class CreateUserGroupRequest(DataClassJsonMixin): # The user friendly name of the user group @@ -9816,9 +10356,7 @@ class DeliveredMessagePayload(DataClassJsonMixin): @dataclass class DeliveryReceiptsResponse(DataClassJsonMixin): - enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") - ) + enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) @dataclass @@ -9861,6 +10399,10 @@ class DeviceResponse(DataClassJsonMixin): disabled_reason: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="disabled_reason") ) + # Stable physical device identifier used to deduplicate pushes across push providers + hardware_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="hardware_id") + ) # Push provider name push_provider_name: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="push_provider_name") @@ -12040,13 +12582,20 @@ class FileUploadResponse(DataClassJsonMixin): @dataclass class FilterConfigResponse(DataClassJsonMixin): + # LLM moderation labels available as filter values llm_labels: List[str] = dc_field(metadata=dc_config(field_name="llm_labels")) + # AI text moderation labels available as filter values ai_text_labels: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="ai_text_labels") ) + # Moderation config keys present in the queue, available as filter values config_keys: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="config_keys") ) + # The moderation_payload.custom keys the app has configured as review-queue filter chips (via moderation_dashboard_preferences.filterable_custom_keys). Discovery hint for the dashboard only — the filter accepts any custom key regardless of this list. + filterable_custom_keys: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="filterable_custom_keys") + ) @dataclass @@ -12098,6 +12647,14 @@ class FlagCountRuleParameters(DataClassJsonMixin): ) +@dataclass +class FlagDetails(DataClassJsonMixin): + original_text: str = dc_field(metadata=dc_config(field_name="original_text")) + automod: "Optional[AutomodDetailsResponse]" = dc_field( + default=None, metadata=dc_config(field_name="automod") + ) + + @dataclass class FlagDetailsResponse(DataClassJsonMixin): original_text: str = dc_field(metadata=dc_config(field_name="original_text")) @@ -12123,6 +12680,13 @@ class FlagFeedbackResponse(DataClassJsonMixin): labels: "List[LabelResponse]" = dc_field(metadata=dc_config(field_name="labels")) +@dataclass +class FlagItemResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + # Unique identifier of the created moderation item + item_id: str = dc_field(metadata=dc_config(field_name="item_id")) + + @dataclass class FlagMessageDetailsResponse(DataClassJsonMixin): pin_changed: Optional[bool] = dc_field( @@ -12170,9 +12734,76 @@ class FlagRequest(DataClassJsonMixin): @dataclass class FlagResponse(DataClassJsonMixin): - duration: str = dc_field(metadata=dc_config(field_name="duration")) - # Unique identifier of the created moderation item - item_id: str = dc_field(metadata=dc_config(field_name="item_id")) + 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[MessageResponse]" = dc_field( + default=None, metadata=dc_config(field_name="target_message") + ) + target_user: "Optional[UserResponse]" = dc_field( + default=None, metadata=dc_config(field_name="target_user") + ) + user: "Optional[UserResponse]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) @dataclass @@ -12214,6 +12845,51 @@ class FlagUserOptions(DataClassJsonMixin): ) +@dataclass +class FloodConfig(DataClassJsonMixin): + identical: "Optional[FloodIdenticalConfig]" = dc_field( + default=None, metadata=dc_config(field_name="identical") + ) + similar: "Optional[FloodSimilarConfig]" = dc_field( + default=None, metadata=dc_config(field_name="similar") + ) + + +@dataclass +class FloodIdenticalConfig(DataClassJsonMixin): + action: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="action") + ) + enabled: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="enabled") + ) + 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 FloodSimilarConfig(DataClassJsonMixin): + action: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="action") + ) + enabled: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="enabled") + ) + similarity_distance: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="similarity_distance") + ) + 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 FollowBatchRequest(DataClassJsonMixin): # List of follow relationships to create @@ -12320,7 +12996,7 @@ class FollowRequest(DataClassJsonMixin): create_notification_activity: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="create_notification_activity") ) - # If true, auto-creates users referenced by the source and target FIDs when they don't already exist. Server-side only. Defaults to false. For FollowBatch/GetOrCreateFollows, use the top-level create_users field; per-item follows[i].create_users is rejected. + # If true, auto-creates users referenced by the source and target FIDs when they don't already exist. Server-side only. Defaults to false. Use directly on single follow endpoints (Follow, GetOrCreateFollow). On batch endpoints (FollowBatch, GetOrCreateFollows), use the top-level create_users field; per-item follows[i].create_users is rejected. create_users: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="create_users") ) @@ -13379,6 +14055,9 @@ class GetOrCreateFeedRequest(DataClassJsonMixin): default=None, metadata=dc_config(field_name="limit") ) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) + overwrite_interest_weights: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="overwrite_interest_weights") + ) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) user_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="user_id") @@ -13481,6 +14160,48 @@ class GetOrCreateFeedViewResponse(DataClassJsonMixin): feed_view: "FeedViewResponse" = dc_field(metadata=dc_config(field_name="feed_view")) +@dataclass +class GetOrCreateFollowResponse(DataClassJsonMixin): + # True if the follow was newly created by this request; false if it already existed + created: bool = dc_field(metadata=dc_config(field_name="created")) + duration: str = dc_field(metadata=dc_config(field_name="duration")) + follow: "FollowResponse" = dc_field(metadata=dc_config(field_name="follow")) + # Whether a notification activity was successfully created (only set when the follow was newly created) + notification_created: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="notification_created") + ) + + +@dataclass +class GetOrCreateUnfollowRequest(DataClassJsonMixin): + # Fully qualified ID of the source feed + source: str = dc_field(metadata=dc_config(field_name="source")) + # Fully qualified ID of the target feed + target: str = dc_field(metadata=dc_config(field_name="target")) + # Whether to delete the corresponding notification activity (default: false) + delete_notification_activity: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="delete_notification_activity") + ) + # If true, enriches the follow's source_feed and target_feed with own_* fields (own_follows, own_followings, own_capabilities, own_membership). Defaults to false for performance. + enrich_own_fields: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="enrich_own_fields") + ) + # When true, activities from the unfollowed feed will remain in the source feed's timeline (default: false) + keep_history: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="keep_history") + ) + + +@dataclass +class GetOrCreateUnfollowResponse(DataClassJsonMixin): + # True if a follow was found and removed by this request; false if no follow existed + deleted: bool = dc_field(metadata=dc_config(field_name="deleted")) + duration: str = dc_field(metadata=dc_config(field_name="duration")) + follow: "Optional[FollowResponse]" = dc_field( + default=None, metadata=dc_config(field_name="follow") + ) + + @dataclass class GetPushTemplatesResponse(DataClassJsonMixin): # Duration of the request in milliseconds @@ -13581,6 +14302,14 @@ class GetSegmentResponse(DataClassJsonMixin): ) +@dataclass +class GetSetupSessionResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + setup_session: "Optional[SetupSession]" = dc_field( + default=None, metadata=dc_config(field_name="setup_session") + ) + + @dataclass class GetTaskResponse(DataClassJsonMixin): created_at: datetime = dc_field( @@ -13627,6 +14356,15 @@ class GetUserGroupResponse(DataClassJsonMixin): ) +@dataclass +class GetUserInterestsResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + # Top-N interest tags sorted by descending count, then alphabetically by tag + interests: "List[InterestTagResponse]" = dc_field( + metadata=dc_config(field_name="interests") + ) + + @dataclass class GoLiveRequest(DataClassJsonMixin): recording_storage_name: Optional[str] = dc_field( @@ -13678,21 +14416,38 @@ class GroupedChannelsBucket(DataClassJsonMixin): channels: "List[ChannelStateResponseFields]" = dc_field( metadata=dc_config(field_name="channels") ) + # Cursor for the next page of this group + next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) + # Cursor for the previous page of this group + prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) # Unread channels currently classified into this bucket unread_channels: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="unread_channels") ) +@dataclass +class GroupedChannelsGroupRequest(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 GroupedQueryChannelsRequest(DataClassJsonMixin): - # Max channels per bucket (default 10) + # Default max channels per group (default 10) limit: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="limit") ) user_id: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="user_id") ) + # Groups to return, keyed by group name. Each group can define limit, next, or prev. 'next' and 'prev' cursors are only allowed when the request contains exactly one group; multi-group pagination is rejected. + groups: "Optional[Dict[str, GroupedChannelsGroupRequest]]" = dc_field( + default=None, metadata=dc_config(field_name="groups") + ) user: "Optional[UserRequest]" = dc_field( default=None, metadata=dc_config(field_name="user") ) @@ -14000,6 +14755,9 @@ class ImportV2TaskSettings(DataClassJsonMixin): source: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="source") ) + use_import_time_as_op_time: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="use_import_time_as_op_time") + ) s3: "Optional[ImportV2TaskSettingsS3]" = dc_field( default=None, metadata=dc_config(field_name="s3") ) @@ -14299,6 +15057,14 @@ class InsertActionLogResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) +@dataclass +class InterestTagResponse(DataClassJsonMixin): + # Number of distinct reacted-to activities tagged with this value + count: int = dc_field(metadata=dc_config(field_name="count")) + # The interest tag value + tag: str = dc_field(metadata=dc_config(field_name="tag")) + + @dataclass class JoinCallAPIMetrics(DataClassJsonMixin): failures: float = dc_field(metadata=dc_config(field_name="failures")) @@ -14308,6 +15074,19 @@ class JoinCallAPIMetrics(DataClassJsonMixin): ) +@dataclass +class KeyframeOCRRuleParameters(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") + ) + harm_labels: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="harm_labels") + ) + + @dataclass class KeyframeRuleParameters(DataClassJsonMixin): min_confidence: Optional[float] = dc_field( @@ -14316,6 +15095,9 @@ class KeyframeRuleParameters(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") + ) harm_labels: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="harm_labels") ) @@ -14386,11 +15168,13 @@ class LLMConfig(DataClassJsonMixin): @dataclass class LLMRule(DataClassJsonMixin): - description: str = dc_field(metadata=dc_config(field_name="description")) label: str = dc_field(metadata=dc_config(field_name="label")) action: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="action") ) + description: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="description") + ) severity_rules: "Optional[List[BodyguardSeverityRule]]" = dc_field( default=None, metadata=dc_config(field_name="severity_rules") ) @@ -14445,6 +15229,10 @@ class LabelResultResponse(DataClassJsonMixin): directed_at: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="directed_at") ) + # The stored content with every non-whitespace character masked. Present only when recommended_action is not 'keep'. Derived at runtime and never stored. + fully_masked_content: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="fully_masked_content") + ) # Content with blocklisted tokens masked (when a blocklist rule with action=mask rewrote the original) masked_content: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="masked_content") @@ -14490,7 +15278,7 @@ class LabelsRequest(DataClassJsonMixin): dry_run: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="dry_run") ) - # Optional moderation policy key (max 128 chars) + # Optional moderation policy key (max 128 chars). For username moderation, set this to a policy whose key starts with 'username:' (e.g. 'username:default') to opt into the low-latency fast-path: blocklists (customer + Stream-managed defaults) short-circuit the LLM, and the LLM fallback uses gpt-4.1-nano with a 24h Valkey verdict cache. Without a 'username:' prefix the request falls through to the standard Bodyguard Analyze v1 username path. policy: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="policy") ) @@ -14515,6 +15303,10 @@ class LabelsResponse(DataClassJsonMixin): directed_at: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="directed_at") ) + # The original content with every non-whitespace character masked. Present only when recommended_action is not 'keep'. Derived at runtime and never stored. + fully_masked_content: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="fully_masked_content") + ) # High-level harm category harm_type: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="harm_type") @@ -14990,6 +15782,39 @@ class MarkUnreadRequest(DataClassJsonMixin): ) +@dataclass +class MatchedContent(DataClassJsonMixin): + # The `content_ids[label]` value supplied on the `/analyze` request that contributed this entry. + id: str = dc_field(metadata=dc_config(field_name="id")) + # `content_published_at` from the contributing `/analyze` request, or server receive time when that field was omitted. + published_at: datetime = dc_field( + metadata=dc_config( + field_name="published_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ) + ) + # Content type that contributed this entry: `image` or `text`. + type: str = dc_field(metadata=dc_config(field_name="type")) + # Image-classification entries only. Aggregate (max) confidence score across the entry's classifications + sub-classifications. Absent on text and OCR entries. + confidence: Optional[float] = dc_field( + default=None, metadata=dc_config(field_name="confidence") + ) + # Text and OCR entries. Aggregate (max) Bodyguard severity level (`LOW` / `MEDIUM` / `HIGH` / `CRITICAL`). Absent on image-classification entries. + severity: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="severity") + ) + # Image-classification entries (keyframe rule, Type=image) carry nested L1 → L2 classifications. Text entries (closed_caption rule, Type=text) carry flat label + severity. Resolved against the app's effective taxonomy on the image side. + classifications: "Optional[List[Classification]]" = dc_field( + default=None, metadata=dc_config(field_name="classifications") + ) + # OCR entries only (keyframe_ocr rule, Type=image). Bodyguard labels that fired against the keyframe's OCR-extracted text (e.g. `INSULT`, `HATE_SPEECH`). Distinct from `classifications` so consumers can route OCR matches separately from image-classification matches. + ocr_classifications: "Optional[List[Classification]]" = dc_field( + default=None, metadata=dc_config(field_name="ocr_classifications") + ) + + @dataclass class MaxStreakChangedEvent(DataClassJsonMixin): created_at: datetime = dc_field( @@ -16051,6 +16876,10 @@ class MessageResponse(DataClassJsonMixin): mentioned_group_ids: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="mentioned_group_ids") ) + # List of mentioned user group objects. + mentioned_groups: "Optional[List[UserGroupResponse]]" = dc_field( + default=None, metadata=dc_config(field_name="mentioned_groups") + ) # List of roles mentioned in the message (e.g. admin, channel_moderator, custom roles). Members with matching roles will receive push notifications based on their push preferences. Max 10 roles mentioned_roles: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="mentioned_roles") @@ -16402,6 +17231,10 @@ class MessageWithChannelResponse(DataClassJsonMixin): mentioned_group_ids: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="mentioned_group_ids") ) + # List of mentioned user group objects. + mentioned_groups: "Optional[List[UserGroupResponse]]" = dc_field( + default=None, metadata=dc_config(field_name="mentioned_groups") + ) # List of roles mentioned in the message (e.g. admin, channel_moderator, custom roles). Members with matching roles will receive push notifications based on their push preferences. Max 10 roles mentioned_roles: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="mentioned_roles") @@ -16509,6 +17342,11 @@ class ModerationActionConfigResponse(DataClassJsonMixin): ) +@dataclass +class ModerationBanResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + + @dataclass class ModerationCheckCompletedEvent(DataClassJsonMixin): created_at: datetime = dc_field( @@ -16602,6 +17440,9 @@ class ModerationConfig(DataClassJsonMixin): block_list_config: "Optional[BlockListConfig]" = dc_field( default=None, metadata=dc_config(field_name="block_list_config") ) + flood_config: "Optional[FloodConfig]" = dc_field( + default=None, metadata=dc_config(field_name="flood_config") + ) google_vision_config: "Optional[GoogleVisionConfig]" = dc_field( default=None, metadata=dc_config(field_name="google_vision_config") ) @@ -16655,6 +17496,9 @@ class ModerationCustomActionEvent(DataClassJsonMixin): @dataclass class ModerationDashboardPreferences(DataClassJsonMixin): + analyze_max_image_size_bytes: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="analyze_max_image_size_bytes") + ) async_review_queue_upsert: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="async_review_queue_upsert") ) @@ -16676,12 +17520,19 @@ class ModerationDashboardPreferences(DataClassJsonMixin): media_queue_blur_enabled: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="media_queue_blur_enabled") ) + webhook_header_client_request_id_key: Optional[str] = dc_field( + default=None, + metadata=dc_config(field_name="webhook_header_client_request_id_key"), + ) allowed_moderation_action_reasons: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="allowed_moderation_action_reasons") ) escalation_reasons: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="escalation_reasons") ) + filterable_custom_keys: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="filterable_custom_keys") + ) keyframe_classifications_map: "Optional[Dict[str, Dict[str, bool]]]" = dc_field( default=None, metadata=dc_config(field_name="keyframe_classifications_map") ) @@ -16770,6 +17621,63 @@ class ModerationFlaggedEvent(DataClassJsonMixin): ) +@dataclass +class ModerationImageAnalysisCompleteEvent(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="moderation.image_analysis.complete", + metadata=dc_config(field_name="type"), + ) + # The moderation policy key that was applied. + config_key: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="config_key") + ) + # Echo of the `entity_creator_id` on the /analyze request. + entity_creator_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_creator_id") + ) + # Echo of the `entity_id` on the /analyze request. + entity_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_id") + ) + # Echo of the `entity_type` on the /analyze request. + entity_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_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 row ID for deep-linking into the dashboard. + review_queue_item_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="review_queue_item_id") + ) + # Echo of the `custom` metadata on the /analyze request. + custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="custom") + ) + # Per-image verdicts, same shape as the /analyze HTTP response. Each entry carries `id` when the request supplied `content_ids`. + images: "Optional[Dict[str, AnalyzeImageField]]" = dc_field( + default=None, metadata=dc_config(field_name="images") + ) + # Per-text-field verdicts, same shape as the /analyze HTTP response. Each entry carries `id` when the request supplied `content_ids`. + texts: "Optional[Dict[str, AnalyzeTextField]]" = dc_field( + default=None, metadata=dc_config(field_name="texts") + ) + + @dataclass class ModerationMarkReviewedEvent(DataClassJsonMixin): created_at: datetime = dc_field( @@ -16958,6 +17866,63 @@ class ModerationRulesTriggeredEvent(DataClassJsonMixin): violation_number: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="violation_number") ) + # Ordered list of contents whose verdicts contributed to an aggregation rule's threshold. Populated only for aggregation rules when callers supplied `content_ids`. + matched_contents: "Optional[List[MatchedContent]]" = dc_field( + default=None, metadata=dc_config(field_name="matched_contents") + ) + + +@dataclass +class ModerationTextAnalysisCompleteEvent(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="moderation.text_analysis.complete", + metadata=dc_config(field_name="type"), + ) + # The moderation policy key that was applied. + config_key: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="config_key") + ) + # Echo of the `entity_creator_id` on the /analyze request. + entity_creator_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_creator_id") + ) + # Echo of the `entity_id` on the /analyze request. + entity_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_id") + ) + # Echo of the `entity_type` on the /analyze request. + entity_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_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 row ID for deep-linking into the dashboard. + review_queue_item_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="review_queue_item_id") + ) + # Echo of the `custom` metadata on the /analyze request. + custom: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="custom") + ) + # Per-text-field verdicts, same shape as the /analyze HTTP response. Each entry carries `id` when the request supplied `content_ids`. + texts: "Optional[Dict[str, AnalyzeTextField]]" = dc_field( + default=None, metadata=dc_config(field_name="texts") + ) @dataclass @@ -20120,6 +21085,8 @@ class QueryModerationRulesResponse(DataClassJsonMixin): keyframe_labels: List[str] = dc_field( metadata=dc_config(field_name="keyframe_labels") ) + # Available harm labels for keyframe OCR rules. Mirrors `closed_caption_labels` today but kept as a separate field so the two pickers can diverge later. + ocr_labels: List[str] = dc_field(metadata=dc_config(field_name="ocr_labels")) # List of moderation rules rules: "List[ModerationRuleV2Response]" = dc_field( metadata=dc_config(field_name="rules") @@ -20132,6 +21099,10 @@ class QueryModerationRulesResponse(DataClassJsonMixin): default_llm_labels: "Dict[str, str]" = dc_field( metadata=dc_config(field_name="default_llm_labels") ) + # Recommended LLM label descriptions for username-scoped policies (key starts with 'username:'). Used by /moderation/v2/labels fast-path. + default_username_llm_labels: "Dict[str, str]" = dc_field( + metadata=dc_config(field_name="default_username_llm_labels") + ) # L1 to L2 mapping of keyframe harm label classifications keyframe_label_classifications: "Dict[str, List[str]]" = dc_field( metadata=dc_config(field_name="keyframe_label_classifications") @@ -20319,7 +21290,7 @@ class QueryReviewQueueRequest(DataClassJsonMixin): sort: "Optional[List[SortParamRequest]]" = dc_field( default=None, metadata=dc_config(field_name="sort") ) - # Filter conditions for review queue items + # Filter conditions for review queue items. Accepts built-in fields (e.g. status, channel_cid, severity, recommended_action) and customer-supplied moderation_payload.custom keys: any key that is not a built-in field is matched against the item's custom moderation data (e.g. {"location_id": "loc-42"}). Use filter_config.filterable_custom_keys to discover which custom keys the app exposes as chips. filter: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="filter") ) @@ -21127,9 +22098,7 @@ class ReadCollectionsResponse(DataClassJsonMixin): @dataclass class ReadReceiptsResponse(DataClassJsonMixin): - enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") - ) + enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) @dataclass @@ -21480,6 +22449,18 @@ class ReportByHistogramBucket(DataClassJsonMixin): ) +@dataclass +class ReportClientEventRequest(DataClassJsonMixin): + # Client-side events to report (1-100 per request) + events: "List[ClientEvent]" = dc_field(metadata=dc_config(field_name="events")) + + +@dataclass +class ReportClientEventResponse(DataClassJsonMixin): + # Duration of the request in milliseconds + duration: str = dc_field(metadata=dc_config(field_name="duration")) + + @dataclass class ReportResponse(DataClassJsonMixin): call: "CallReportResponse" = dc_field(metadata=dc_config(field_name="call")) @@ -21991,6 +22972,9 @@ class Role(DataClassJsonMixin): @dataclass class RuleBuilderAction(DataClassJsonMixin): + reason: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="reason") + ) skip_inbox: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_inbox") ) @@ -22033,6 +23017,16 @@ class RuleBuilderCondition(DataClassJsonMixin): content_count_rule_params: "Optional[ContentCountRuleParameters]" = dc_field( default=None, metadata=dc_config(field_name="content_count_rule_params") ) + content_custom_property_count_params: "Optional[ContentCustomPropertyCountParameters]" = dc_field( + default=None, + metadata=dc_config(field_name="content_custom_property_count_params"), + ) + content_custom_property_params: "Optional[ContentCustomPropertyParameters]" = ( + dc_field( + default=None, + metadata=dc_config(field_name="content_custom_property_params"), + ) + ) content_flag_count_rule_params: "Optional[FlagCountRuleParameters]" = dc_field( default=None, metadata=dc_config(field_name="content_flag_count_rule_params") ) @@ -22042,6 +23036,9 @@ class RuleBuilderCondition(DataClassJsonMixin): image_rule_params: "Optional[ImageRuleParameters]" = dc_field( default=None, metadata=dc_config(field_name="image_rule_params") ) + keyframe_ocr_rule_params: "Optional[KeyframeOCRRuleParameters]" = dc_field( + default=None, metadata=dc_config(field_name="keyframe_ocr_rule_params") + ) keyframe_rule_params: "Optional[KeyframeRuleParameters]" = dc_field( default=None, metadata=dc_config(field_name="keyframe_rule_params") ) @@ -22714,6 +23711,9 @@ class SearchResultMessage(DataClassJsonMixin): mentioned_group_ids: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="mentioned_group_ids") ) + mentioned_groups: "Optional[List[UserGroupResponse]]" = dc_field( + default=None, metadata=dc_config(field_name="mentioned_groups") + ) mentioned_roles: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="mentioned_roles") ) @@ -23083,6 +24083,40 @@ class SetRetentionPolicyResponse(DataClassJsonMixin): policy: "RetentionPolicy" = dc_field(metadata=dc_config(field_name="policy")) +@dataclass +class SetupSession(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"), + ) + ) + current_step: str = dc_field(metadata=dc_config(field_name="current_step")) + status: str = dc_field(metadata=dc_config(field_name="status")) + 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"), + ) + ) + setup_data: Dict[str, object] = dc_field( + metadata=dc_config(field_name="setup_data") + ) + completed_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="completed_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + + @dataclass class ShadowBlockActionRequestPayload(DataClassJsonMixin): # Reason for shadow blocking @@ -23666,6 +24700,10 @@ class SubmitActionRequest(DataClassJsonMixin): @dataclass class SubmitActionResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) + # Present when the appeal was accepted but the entity could not be restored automatically. The moderator should restore it manually. + auto_restore_warning: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="auto_restore_warning") + ) appeal_item: "Optional[AppealItemResponse]" = dc_field( default=None, metadata=dc_config(field_name="appeal_item") ) @@ -23770,9 +24808,11 @@ class SubscribersMetrics(DataClassJsonMixin): @dataclass class TargetResolution(DataClassJsonMixin): - bitrate: int = dc_field(metadata=dc_config(field_name="bitrate")) height: int = dc_field(metadata=dc_config(field_name="height")) width: int = dc_field(metadata=dc_config(field_name="width")) + bitrate: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="bitrate") + ) @dataclass @@ -23836,6 +24876,12 @@ class TextContentParameters(DataClassJsonMixin): severity: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="severity") ) + text_length: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="text_length") + ) + text_length_operator: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="text_length_operator") + ) blocklist_match: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="blocklist_match") ) @@ -24378,8 +25424,12 @@ class TranslateMessageRequest(DataClassJsonMixin): @dataclass class TranslationSettings(DataClassJsonMixin): - enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) - languages: List[str] = dc_field(metadata=dc_config(field_name="languages")) + enabled: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="enabled") + ) + languages: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="languages") + ) @dataclass @@ -24450,9 +25500,7 @@ class TruncateChannelResponse(DataClassJsonMixin): @dataclass class TypingIndicatorsResponse(DataClassJsonMixin): - enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="enabled") - ) + enabled: bool = dc_field(metadata=dc_config(field_name="enabled")) @dataclass @@ -24934,6 +25982,10 @@ class UpdateAppRequest(DataClassJsonMixin): auto_translation_enabled: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="auto_translation_enabled") ) + before_message_send_hook_attempt_timeout_ms: Optional[int] = dc_field( + default=None, + metadata=dc_config(field_name="before_message_send_hook_attempt_timeout_ms"), + ) before_message_send_hook_url: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="before_message_send_hook_url") ) @@ -24982,6 +26034,9 @@ class UpdateAppRequest(DataClassJsonMixin): moderation_enabled: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="moderation_enabled") ) + moderation_onboarding_complete: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="moderation_onboarding_complete") + ) moderation_s3_image_access_role_arn: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="moderation_s3_image_access_role_arn"), @@ -25031,6 +26086,9 @@ class UpdateAppRequest(DataClassJsonMixin): user_response_time_enabled: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="user_response_time_enabled") ) + video_primary_use_case: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="video_primary_use_case") + ) webhook_url: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="webhook_url") ) @@ -25095,12 +26153,18 @@ class UpdateAppRequest(DataClassJsonMixin): @dataclass class UpdateBlockListRequest(DataClassJsonMixin): + is_confusable_folding_enabled: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="is_confusable_folding_enabled") + ) is_leet_check_enabled: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="is_leet_check_enabled") ) is_plural_check_enabled: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="is_plural_check_enabled") ) + is_substring_matching_enabled: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="is_substring_matching_enabled") + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) # List of words to block words: Optional[List[str]] = dc_field( @@ -26005,7 +27069,7 @@ class UpdateFollowRequest(DataClassJsonMixin): create_notification_activity: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="create_notification_activity") ) - # If true, auto-creates users referenced by the source and target FIDs when they don't already exist. Server-side only. Defaults to false. For FollowBatch/GetOrCreateFollows, use the top-level create_users field; per-item follows[i].create_users is rejected. + # If true, auto-creates users referenced by the source and target FIDs when they don't already exist. Server-side only. Defaults to false. Use directly on single follow endpoints (Follow, GetOrCreateFollow). On batch endpoints (FollowBatch, GetOrCreateFollows), use the top-level create_users field; per-item follows[i].create_users is rejected. create_users: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="create_users") ) @@ -26348,6 +27412,27 @@ class UpdateSIPTrunkResponse(DataClassJsonMixin): ) +@dataclass +class UpdateSegmentRequest(DataClassJsonMixin): + # The description of the segment (max 256 characters) + description: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="description") + ) + # The name of the segment (max 128 characters) + name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) + # Filter to apply to the query + filter: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="filter") + ) + + +@dataclass +class UpdateSegmentResponse(DataClassJsonMixin): + # Duration of the request in milliseconds + duration: str = dc_field(metadata=dc_config(field_name="duration")) + segment: "SegmentResponse" = dc_field(metadata=dc_config(field_name="segment")) + + @dataclass class UpdateThreadPartialRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( @@ -26678,6 +27763,9 @@ class UpsertConfigRequest(DataClassJsonMixin): bodyguard_config: "Optional[AITextConfig]" = dc_field( default=None, metadata=dc_config(field_name="bodyguard_config") ) + flood_config: "Optional[FloodConfig]" = dc_field( + default=None, metadata=dc_config(field_name="flood_config") + ) google_vision_config: "Optional[GoogleVisionConfig]" = dc_field( default=None, metadata=dc_config(field_name="google_vision_config") ) @@ -26841,11 +27929,11 @@ class UpsertPushPreferencesResponse(DataClassJsonMixin): # Duration of the request in milliseconds duration: str = dc_field(metadata=dc_config(field_name="duration")) # The channel specific push notification preferences, only returned for channels you've edited. - user_channel_preferences: "Dict[str, Dict[str, Optional[ChannelPushPreferencesResponse]]]" = dc_field( - metadata=dc_config(field_name="user_channel_preferences") + user_channel_preferences: "Dict[str, Dict[str, ChannelPushPreferencesResponse]]" = ( + dc_field(metadata=dc_config(field_name="user_channel_preferences")) ) # The user preferences, always returned regardless if you edited it - user_preferences: "Dict[str, Optional[PushPreferencesResponse]]" = dc_field( + user_preferences: "Dict[str, PushPreferencesResponse]" = dc_field( metadata=dc_config(field_name="user_preferences") ) @@ -26897,6 +27985,26 @@ class UpsertPushTemplateResponse(DataClassJsonMixin): ) +@dataclass +class UpsertSetupSessionRequest(DataClassJsonMixin): + # The current step of the setup wizard. One of: welcome, input, configure, live + current_step: str = dc_field(metadata=dc_config(field_name="current_step")) + # The status of the setup session. One of: in_progress, completed + status: str = dc_field(metadata=dc_config(field_name="status")) + # Per-step data keyed by step name (welcome, input, configure, live) + setup_data: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="setup_data") + ) + + +@dataclass +class UpsertSetupSessionResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + setup_session: "Optional[SetupSession]" = dc_field( + default=None, metadata=dc_config(field_name="setup_session") + ) + + @dataclass class User(DataClassJsonMixin): id: str = dc_field(metadata=dc_config(field_name="id")) @@ -26959,6 +28067,10 @@ class UserBannedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) + # ID of the review queue item (flagged message) that triggered the ban, if the ban was applied from the moderation review queue + review_queue_item_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="review_queue_item_id") + ) # Whether the user was shadow banned shadow: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="shadow") diff --git a/getstream/moderation/async_rest_client.py b/getstream/moderation/async_rest_client.py index 3c249d23..473fba30 100644 --- a/getstream/moderation/async_rest_client.py +++ b/getstream/moderation/async_rest_client.py @@ -158,12 +158,43 @@ async def insert_action_log( "/api/v2/moderation/action_logs", InsertActionLogResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.analyze") + async def analyze( + self, + async_response: Optional[bool] = None, + config_key: Optional[str] = None, + content_published_at: Optional[datetime] = None, + entity_creator_id: Optional[str] = None, + entity_id: Optional[str] = None, + entity_type: Optional[str] = None, + user_id: Optional[str] = None, + content_ids: Optional[Dict[str, str]] = None, + custom: Optional[Dict[str, object]] = None, + texts: Optional[Dict[str, str]] = None, + user: Optional[UserRequest] = None, + ) -> StreamResponse[AnalyzeResponse]: + json = AnalyzeRequest( + async_response=async_response, + config_key=config_key, + content_published_at=content_published_at, + entity_creator_id=entity_creator_id, + entity_id=entity_id, + entity_type=entity_type, + user_id=user_id, + content_ids=content_ids, + custom=custom, + texts=texts, + user=user, + ).to_dict() + return await self.post("/api/v2/moderation/analyze", AnalyzeResponse, json=json) + @telemetry.operation_name("getstream.api.moderation.appeal") async def appeal( self, appeal_reason: str, entity_id: str, entity_type: str, + review_queue_item_id: Optional[str] = None, user_id: Optional[str] = None, attachments: Optional[List[str]] = None, user: Optional[UserRequest] = None, @@ -172,6 +203,7 @@ async def appeal( appeal_reason=appeal_reason, entity_id=entity_id, entity_type=entity_type, + review_queue_item_id=review_queue_item_id, user_id=user_id, attachments=attachments, user=user, @@ -211,6 +243,36 @@ async def query_appeals( "/api/v2/moderation/appeals", QueryAppealsResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.bulk_action_appeals") + async def bulk_action_appeals( + self, + action_type: str, + appeal_ids: List[str], + user_id: Optional[str] = None, + mark_reviewed: Optional[MarkReviewedRequestPayload] = None, + reject_appeal: Optional[RejectAppealRequestPayload] = None, + restore: Optional[RestoreActionRequestPayload] = None, + unban: Optional[UnbanActionRequestPayload] = None, + unblock: Optional[UnblockActionRequestPayload] = None, + user: Optional[UserRequest] = None, + ) -> StreamResponse[BulkActionAppealsResponse]: + json = BulkActionAppealsRequest( + action_type=action_type, + appeal_ids=appeal_ids, + user_id=user_id, + mark_reviewed=mark_reviewed, + reject_appeal=reject_appeal, + restore=restore, + unban=unban, + unblock=unblock, + user=user, + ).to_dict() + return await self.post( + "/api/v2/moderation/appeals/bulk_action", + BulkActionAppealsResponse, + json=json, + ) + @telemetry.operation_name("getstream.api.moderation.ban") async def ban( self, @@ -223,7 +285,7 @@ async def ban( shadow: Optional[bool] = None, timeout: Optional[int] = None, banned_by: Optional[UserRequest] = None, - ) -> StreamResponse[BanResponse]: + ) -> StreamResponse[ModerationBanResponse]: json = BanRequest( target_user_id=target_user_id, banned_by_id=banned_by_id, @@ -235,7 +297,9 @@ async def ban( timeout=timeout, banned_by=banned_by, ).to_dict() - return await self.post("/api/v2/moderation/ban", BanResponse, json=json) + return await self.post( + "/api/v2/moderation/ban", ModerationBanResponse, json=json + ) @telemetry.operation_name("getstream.api.moderation.bulk_image_moderation") async def bulk_image_moderation( @@ -314,6 +378,7 @@ async def upsert_config( aws_rekognition_config: Optional[AIImageConfig] = None, block_list_config: Optional[BlockListConfig] = None, bodyguard_config: Optional[AITextConfig] = None, + flood_config: Optional[FloodConfig] = None, google_vision_config: Optional[GoogleVisionConfig] = None, llm_config: Optional[LLMConfig] = None, rule_builder_config: Optional[RuleBuilderConfig] = None, @@ -335,6 +400,7 @@ async def upsert_config( aws_rekognition_config=aws_rekognition_config, block_list_config=block_list_config, bodyguard_config=bodyguard_config, + flood_config=flood_config, google_vision_config=google_vision_config, llm_config=llm_config, rule_builder_config=rule_builder_config, @@ -464,7 +530,7 @@ async def flag( custom: Optional[Dict[str, object]] = None, moderation_payload: Optional[ModerationPayload] = None, user: Optional[UserRequest] = None, - ) -> StreamResponse[FlagResponse]: + ) -> StreamResponse[FlagItemResponse]: json = FlagRequest( entity_id=entity_id, entity_type=entity_type, @@ -475,7 +541,7 @@ async def flag( moderation_payload=moderation_payload, user=user, ).to_dict() - return await self.post("/api/v2/moderation/flag", FlagResponse, json=json) + return await self.post("/api/v2/moderation/flag", FlagItemResponse, json=json) @telemetry.operation_name("getstream.api.moderation.get_flag_count") async def get_flag_count( @@ -717,6 +783,24 @@ async def get_review_queue_item( path_params=path_params, ) + @telemetry.operation_name("getstream.api.moderation.get_setup_session") + async def get_setup_session(self) -> StreamResponse[GetSetupSessionResponse]: + return await self.get("/api/v2/moderation/setup", GetSetupSessionResponse) + + @telemetry.operation_name("getstream.api.moderation.upsert_setup_session") + async def upsert_setup_session( + self, + current_step: str, + status: str, + setup_data: Optional[Dict[str, object]] = None, + ) -> StreamResponse[UpsertSetupSessionResponse]: + json = UpsertSetupSessionRequest( + current_step=current_step, status=status, setup_data=setup_data + ).to_dict() + return await self.post( + "/api/v2/moderation/setup", UpsertSetupSessionResponse, json=json + ) + @telemetry.operation_name("getstream.api.moderation.submit_action") async def submit_action( self, diff --git a/getstream/moderation/rest_client.py b/getstream/moderation/rest_client.py index 21198dc7..79e7654d 100644 --- a/getstream/moderation/rest_client.py +++ b/getstream/moderation/rest_client.py @@ -158,12 +158,43 @@ def insert_action_log( "/api/v2/moderation/action_logs", InsertActionLogResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.analyze") + def analyze( + self, + async_response: Optional[bool] = None, + config_key: Optional[str] = None, + content_published_at: Optional[datetime] = None, + entity_creator_id: Optional[str] = None, + entity_id: Optional[str] = None, + entity_type: Optional[str] = None, + user_id: Optional[str] = None, + content_ids: Optional[Dict[str, str]] = None, + custom: Optional[Dict[str, object]] = None, + texts: Optional[Dict[str, str]] = None, + user: Optional[UserRequest] = None, + ) -> StreamResponse[AnalyzeResponse]: + json = AnalyzeRequest( + async_response=async_response, + config_key=config_key, + content_published_at=content_published_at, + entity_creator_id=entity_creator_id, + entity_id=entity_id, + entity_type=entity_type, + user_id=user_id, + content_ids=content_ids, + custom=custom, + texts=texts, + user=user, + ).to_dict() + return self.post("/api/v2/moderation/analyze", AnalyzeResponse, json=json) + @telemetry.operation_name("getstream.api.moderation.appeal") def appeal( self, appeal_reason: str, entity_id: str, entity_type: str, + review_queue_item_id: Optional[str] = None, user_id: Optional[str] = None, attachments: Optional[List[str]] = None, user: Optional[UserRequest] = None, @@ -172,6 +203,7 @@ def appeal( appeal_reason=appeal_reason, entity_id=entity_id, entity_type=entity_type, + review_queue_item_id=review_queue_item_id, user_id=user_id, attachments=attachments, user=user, @@ -209,6 +241,36 @@ def query_appeals( ).to_dict() return self.post("/api/v2/moderation/appeals", QueryAppealsResponse, json=json) + @telemetry.operation_name("getstream.api.moderation.bulk_action_appeals") + def bulk_action_appeals( + self, + action_type: str, + appeal_ids: List[str], + user_id: Optional[str] = None, + mark_reviewed: Optional[MarkReviewedRequestPayload] = None, + reject_appeal: Optional[RejectAppealRequestPayload] = None, + restore: Optional[RestoreActionRequestPayload] = None, + unban: Optional[UnbanActionRequestPayload] = None, + unblock: Optional[UnblockActionRequestPayload] = None, + user: Optional[UserRequest] = None, + ) -> StreamResponse[BulkActionAppealsResponse]: + json = BulkActionAppealsRequest( + action_type=action_type, + appeal_ids=appeal_ids, + user_id=user_id, + mark_reviewed=mark_reviewed, + reject_appeal=reject_appeal, + restore=restore, + unban=unban, + unblock=unblock, + user=user, + ).to_dict() + return self.post( + "/api/v2/moderation/appeals/bulk_action", + BulkActionAppealsResponse, + json=json, + ) + @telemetry.operation_name("getstream.api.moderation.ban") def ban( self, @@ -221,7 +283,7 @@ def ban( shadow: Optional[bool] = None, timeout: Optional[int] = None, banned_by: Optional[UserRequest] = None, - ) -> StreamResponse[BanResponse]: + ) -> StreamResponse[ModerationBanResponse]: json = BanRequest( target_user_id=target_user_id, banned_by_id=banned_by_id, @@ -233,7 +295,7 @@ def ban( timeout=timeout, banned_by=banned_by, ).to_dict() - return self.post("/api/v2/moderation/ban", BanResponse, json=json) + return self.post("/api/v2/moderation/ban", ModerationBanResponse, json=json) @telemetry.operation_name("getstream.api.moderation.bulk_image_moderation") def bulk_image_moderation( @@ -312,6 +374,7 @@ def upsert_config( aws_rekognition_config: Optional[AIImageConfig] = None, block_list_config: Optional[BlockListConfig] = None, bodyguard_config: Optional[AITextConfig] = None, + flood_config: Optional[FloodConfig] = None, google_vision_config: Optional[GoogleVisionConfig] = None, llm_config: Optional[LLMConfig] = None, rule_builder_config: Optional[RuleBuilderConfig] = None, @@ -333,6 +396,7 @@ def upsert_config( aws_rekognition_config=aws_rekognition_config, block_list_config=block_list_config, bodyguard_config=bodyguard_config, + flood_config=flood_config, google_vision_config=google_vision_config, llm_config=llm_config, rule_builder_config=rule_builder_config, @@ -458,7 +522,7 @@ def flag( custom: Optional[Dict[str, object]] = None, moderation_payload: Optional[ModerationPayload] = None, user: Optional[UserRequest] = None, - ) -> StreamResponse[FlagResponse]: + ) -> StreamResponse[FlagItemResponse]: json = FlagRequest( entity_id=entity_id, entity_type=entity_type, @@ -469,7 +533,7 @@ def flag( moderation_payload=moderation_payload, user=user, ).to_dict() - return self.post("/api/v2/moderation/flag", FlagResponse, json=json) + return self.post("/api/v2/moderation/flag", FlagItemResponse, json=json) @telemetry.operation_name("getstream.api.moderation.get_flag_count") def get_flag_count( @@ -711,6 +775,24 @@ def get_review_queue_item( path_params=path_params, ) + @telemetry.operation_name("getstream.api.moderation.get_setup_session") + def get_setup_session(self) -> StreamResponse[GetSetupSessionResponse]: + return self.get("/api/v2/moderation/setup", GetSetupSessionResponse) + + @telemetry.operation_name("getstream.api.moderation.upsert_setup_session") + def upsert_setup_session( + self, + current_step: str, + status: str, + setup_data: Optional[Dict[str, object]] = None, + ) -> StreamResponse[UpsertSetupSessionResponse]: + json = UpsertSetupSessionRequest( + current_step=current_step, status=status, setup_data=setup_data + ).to_dict() + return self.post( + "/api/v2/moderation/setup", UpsertSetupSessionResponse, json=json + ) + @telemetry.operation_name("getstream.api.moderation.submit_action") def submit_action( self, diff --git a/getstream/tests/test_webhook.py b/getstream/tests/test_webhook.py index 3c653da0..22efe542 100644 --- a/getstream/tests/test_webhook.py +++ b/getstream/tests/test_webhook.py @@ -906,12 +906,24 @@ def test_parse_moderation_flagged(self): event = parse_webhook_event({"type": "moderation.flagged"}) assert type(event).__name__ == "ModerationFlaggedEvent" + def test_parse_moderation_image_analysis_complete(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "moderation.image_analysis.complete"}) + assert type(event).__name__ == "ModerationImageAnalysisCompleteEvent" + 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_text_analysis_complete(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + event = parse_webhook_event({"type": "moderation.text_analysis.complete"}) + assert type(event).__name__ == "ModerationTextAnalysisCompleteEvent" + def test_parse_moderation_check_completed(self): with warnings.catch_warnings(): warnings.simplefilter("ignore", RuntimeWarning) diff --git a/getstream/video/async_rest_client.py b/getstream/video/async_rest_client.py index 39a72721..fd21126e 100644 --- a/getstream/video/async_rest_client.py +++ b/getstream/video/async_rest_client.py @@ -968,6 +968,15 @@ async def delete_transcription( path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.report_client_call_event") + async def report_client_call_event( + self, events: List[ClientEvent] + ) -> StreamResponse[ReportClientEventResponse]: + json = ReportClientEventRequest(events=events).to_dict() + return await self.post( + "/api/v2/video/call_client_event", ReportClientEventResponse, json=json + ) + @telemetry.operation_name("getstream.api.video.query_call_session_stats") async def query_call_session_stats( self, diff --git a/getstream/video/rest_client.py b/getstream/video/rest_client.py index 1fd2e645..fc8745c4 100644 --- a/getstream/video/rest_client.py +++ b/getstream/video/rest_client.py @@ -964,6 +964,15 @@ def delete_transcription( path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.report_client_call_event") + def report_client_call_event( + self, events: List[ClientEvent] + ) -> StreamResponse[ReportClientEventResponse]: + json = ReportClientEventRequest(events=events).to_dict() + return self.post( + "/api/v2/video/call_client_event", ReportClientEventResponse, json=json + ) + @telemetry.operation_name("getstream.api.video.query_call_session_stats") def query_call_session_stats( self, diff --git a/getstream/webhook.py b/getstream/webhook.py index 3d1109e9..4fb449d6 100644 --- a/getstream/webhook.py +++ b/getstream/webhook.py @@ -143,7 +143,9 @@ MessageUpdatedEvent, ModerationCustomActionEvent, ModerationFlaggedEvent, + ModerationImageAnalysisCompleteEvent, ModerationMarkReviewedEvent, + ModerationTextAnalysisCompleteEvent, ModerationCheckCompletedEvent, ModerationRulesTriggeredEvent, NotificationMarkUnreadEvent, @@ -316,7 +318,9 @@ EVENT_TYPE_MESSAGE_UPDATED = "message.updated" EVENT_TYPE_MODERATION_CUSTOM_ACTION = "moderation.custom_action" EVENT_TYPE_MODERATION_FLAGGED = "moderation.flagged" +EVENT_TYPE_MODERATION_IMAGE_ANALYSIS_COMPLETE = "moderation.image_analysis.complete" EVENT_TYPE_MODERATION_MARK_REVIEWED = "moderation.mark_reviewed" +EVENT_TYPE_MODERATION_TEXT_ANALYSIS_COMPLETE = "moderation.text_analysis.complete" EVENT_TYPE_MODERATION_CHECK_COMPLETED = "moderation_check.completed" EVENT_TYPE_MODERATION_RULE_TRIGGERED = "moderation_rule.triggered" EVENT_TYPE_NOTIFICATION_MARK_UNREAD = "notification.mark_unread" @@ -579,7 +583,9 @@ def _get_event_class(event_type: str): "message.updated": MessageUpdatedEvent, "moderation.custom_action": ModerationCustomActionEvent, "moderation.flagged": ModerationFlaggedEvent, + "moderation.image_analysis.complete": ModerationImageAnalysisCompleteEvent, "moderation.mark_reviewed": ModerationMarkReviewedEvent, + "moderation.text_analysis.complete": ModerationTextAnalysisCompleteEvent, "moderation_check.completed": ModerationCheckCompletedEvent, "moderation_rule.triggered": ModerationRulesTriggeredEvent, "notification.mark_unread": NotificationMarkUnreadEvent,