Skip to content

Conversation

@hasinaxp
Copy link
Contributor

@hasinaxp hasinaxp commented Apr 30, 2025

Summary by CodeRabbit

  • New Features
    • Added support for handling and saving multimedia content (images, audio, documents, videos, voice messages) received via WhatsApp, enabling these media files to be processed and stored.
  • Documentation
    • Improved and added detailed descriptions for media handling methods to clarify their usage and parameters.
  • Tests
    • Enhanced test coverage for WhatsApp media handling, including new tests for media download scenarios and error handling.

@coderabbitai
Copy link

coderabbitai bot commented Apr 30, 2025

Walkthrough

This update introduces the capability to handle WhatsApp multimedia messages by capturing and saving media identifiers as part of the message processing flow. A new static method, save_whatsapp_media_content, is added to manage downloading and storing WhatsApp media, supporting both 360dialog and Meta providers. The WhatsApp channel handler is updated to propagate media IDs through its processing pipeline, including changes to method signatures. Tests are extended to cover the new media handling logic, including both success and failure scenarios for different providers, and existing tests are updated to mock the new functionality where relevant.

Changes

File(s) Change Summary
kairon/chat/handlers/channels/whatsapp.py Added logic to capture WhatsApp media IDs, call UserMedia.save_whatsapp_media_content, and propagate media_ids through the message handling pipeline. Updated method signatures for _handle_user_message and process_message to accept an optional media_ids parameter.
kairon/shared/chat/user_media.py Added static method save_whatsapp_media_content to download and save WhatsApp media from 360dialog or Meta. Added detailed docstrings to multiple static methods. Added imports for mimetypes and requests.
tests/unit_test/channels/whatsapp_test.py Updated two test methods to patch UserMedia.save_whatsapp_media_content, ensuring consistent return values for WhatsApp media handling logic. Adjusted test signatures to accept the mock object.
tests/unit_test/chat/user_media_test.py Added async tests for save_whatsapp_media_content, covering both 360dialog and Meta providers, with success and failure scenarios. Updated imports to include UserMediaUploadType. No changes to existing test logic.

Sequence Diagram(s)

sequenceDiagram
    participant WhatsAppAPI as WhatsApp API
    participant ChannelHandler as Whatsapp ChannelHandler
    participant UserMedia as UserMedia
    participant Processor as AgentProcessor

    WhatsAppAPI->>ChannelHandler: Incoming multimedia message (with media ID)
    ChannelHandler->>UserMedia: save_whatsapp_media_content(bot, sender_id, media_id, config)
    UserMedia-->>ChannelHandler: [media_id]
    ChannelHandler->>Processor: handle_channel_message(..., media_ids=[media_id])
Loading

Poem

In the warren of code, a new tunnel appears,
WhatsApp sends pictures, and now Kairon hears!
Media IDs hop along, saved with bunny care,
Through docstrings and tests, we’re sure to prepare.
With every new message, and files that arrive,
Our chatbots grow clever, and media will thrive!
🐰✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78086ed and 3934962.

📒 Files selected for processing (2)
  • kairon/shared/chat/user_media.py (6 hunks)
  • tests/unit_test/chat/user_media_test.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit_test/chat/user_media_test.py
  • kairon/shared/chat/user_media.py
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Codacy Static Code Analysis
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
kairon/shared/chat/user_media.py (3)

119-194: Well-structured implementation for downloading WhatsApp media

The new save_whatsapp_media_content method provides a comprehensive solution for downloading media from WhatsApp BSPs. The implementation handles both 360dialog and Meta providers with appropriate error handling and efficient chunked downloads.

I especially like the use of:

  • Provider-specific logic separation
  • Proper HTTP request timeout settings
  • Chunked downloads to handle large files efficiently
  • Asynchronous task scheduling for non-blocking operation

Consider adding a comment explaining why the 360dialog URL needs replacement on line 143 for better code maintainability.


146-146: Potential typo in filename prefix

There's a typo in the filename string "whataspp_360_" - it should be "whatsapp_360_".

-            file_path = f"whataspp_360_{whatsapp_media_id}{extension}"
+            file_path = f"whatsapp_360_{whatsapp_media_id}{extension}"

162-162: Identical typo in Meta filename prefix

The same "whataspp" typo appears in the Meta filename prefix.

-            file_path = f"whataspp_meta_{whatsapp_media_id}{extension}"
+            file_path = f"whatsapp_meta_{whatsapp_media_id}{extension}"
tests/unit_test/chat/user_media_test.py (1)

404-406: Unused helper function

The created_coros function doesn't appear to be used anywhere in the tests.

Consider removing this unused helper function:

-def created_coros(coros):
-    return coros
-
kairon/chat/handlers/channels/whatsapp.py (1)

60-70: Consider adding error handling for media download failures

The current implementation doesn't handle failures that might occur during media download. If an exception is raised by save_whatsapp_media_content, it will bubble up and be caught by the outer try/except in _handle_user_message, but specific error handling for media download would provide a better user experience.

Consider adding specific error handling:

        elif message.get("type") in {"image", "audio", "document", "video", "voice"}:
            if message['type'] == "voice":
                message['type'] = "audio"
            text = f"/k_multimedia_msg{{\"{message['type']}\": \"{message[message['type']]['id']}\"}}"
-            media_ids = UserMedia.save_whatsapp_media_content(
-                bot=bot,
-                sender_id=message["from"],
-                whatsapp_media_id=message[message['type']]['id'],
-                config=self.config
-            )
+            try:
+                media_ids = UserMedia.save_whatsapp_media_content(
+                    bot=bot,
+                    sender_id=message["from"],
+                    whatsapp_media_id=message[message['type']]['id'],
+                    config=self.config
+                )
+            except Exception as e:
+                logger.error(f"Failed to download WhatsApp media: {str(e)}")
+                # Continue with message processing even if media download fails
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d30d7ce and 78086ed.

📒 Files selected for processing (4)
  • kairon/chat/handlers/channels/whatsapp.py (6 hunks)
  • kairon/shared/chat/user_media.py (6 hunks)
  • tests/unit_test/channels/whatsapp_test.py (2 hunks)
  • tests/unit_test/chat/user_media_test.py (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
kairon/shared/chat/user_media.py (3)
tests/unit_test/utility_test.py (1)
  • bot (3273-3274)
tests/unit_test/live_agent_test.py (1)
  • sender_id (27-28)
kairon/shared/models.py (1)
  • UserMediaUploadType (132-134)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Analyze (python)
  • GitHub Check: Python CI
🔇 Additional comments (10)
kairon/shared/chat/user_media.py (1)

177-178: Use UUID generation strategy consistent with other methods

The current implementation uses uuid7().hex for generating media IDs. This appears consistent with other methods in the class, but it's worth verifying that this is the intended UUID generation strategy.

tests/unit_test/chat/user_media_test.py (4)

327-366: Comprehensive test for 360dialog media download

The test effectively covers the happy path for downloading media from 360dialog, including verification of the correct API calls and parameters.


367-402: Good coverage of Meta provider media download

The test case properly verifies the functionality with the Meta WhatsApp BSP, including checking proper URL construction and parameter passing.


409-418: Effective error case testing for 360dialog

This test correctly verifies that the method raises an appropriate exception when the API call to 360dialog fails.


421-429: Effective error case testing for Meta

This test case properly verifies exception handling for the Meta API failure scenario.

tests/unit_test/channels/whatsapp_test.py (2)

443-444: Good test isolation with patching

Patching UserMedia.save_whatsapp_media_content ensures the order message test can focus on message handling logic without requiring actual media downloads.


524-525: Consistent patching approach for attachment message test

Using the same patching approach for the attachment message test maintains consistency in testing methodology.

kairon/chat/handlers/channels/whatsapp.py (3)

15-15: Good module import for media handling

The addition of the UserMedia import is appropriate for the new functionality.


64-69: Properly integrated media download for WhatsApp messages

The implementation cleanly integrates the new media handling functionality into the existing message processing flow, calling save_whatsapp_media_content when relevant media types are detected.


165-165: Complete parameter propagation through the message handling pipeline

The signature updates to _handle_user_message and process_message ensure media IDs are properly propagated through the entire message handling pipeline.

Also applies to: 181-182

Comment on lines 132 to 163
provider = config.get("bsp_type", "meta")
if provider == '360dialog':
endpoint = f'https://waba-v2.360dialog.io/{whatsapp_media_id}'
headers = {
'D360-API-KEY': config.get('api_key'),
}
resp = requests.get(endpoint, headers=headers, stream=True)
if resp.status_code != 200:
raise AppException(f"Failed to download media from 360 dialog: {resp.status_code} - {resp.text}")
json_resp = resp.json()
download_url = json_resp.get("url")
download_url = download_url.replace('https://lookaside.fbsbx.com', 'https://waba-v2.360dialog.io')
mime_type = json_resp.get("mime_type")
extension = mimetypes.guess_extension(mime_type) or ''
file_path = f"whataspp_360_{whatsapp_media_id}{extension}"
elif provider == 'meta':
endpoint = f'https://graph.facebook.com/v22.0/{whatsapp_media_id}'
access_token = config.get('access_token')
headers = {'Authorization': f'Bearer {access_token}'}
media_info_resp = requests.get(
endpoint,
params={"fields": "url", "access_token": access_token},
timeout=10
)
if media_info_resp.status_code != 200:
raise AppException(f"Failed to get url from meta for media: {whatsapp_media_id}")
json_resp = media_info_resp.json()
download_url = json_resp.get("url")
mime_type = json_resp.get("mime_type")
extension = mimetypes.guess_extension(mime_type) or ''
file_path = f"whataspp_meta_{whatsapp_media_id}{extension}"

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling for missing provider configuration

The code assumes that the required configuration values (api_key for 360dialog, access_token for Meta) will be present, but doesn't handle the case when they might be missing.

Add validation for required configuration values to provide clearer error messages when configurations are missing:

        provider = config.get("bsp_type", "meta")
        if provider == '360dialog':
+            api_key = config.get('api_key')
+            if not api_key:
+                raise AppException("Missing api_key in configuration for 360dialog provider")
            endpoint = f'https://waba-v2.360dialog.io/{whatsapp_media_id}'
            headers = {
-                'D360-API-KEY': config.get('api_key'),
+                'D360-API-KEY': api_key,
            }
            # rest of the 360dialog logic...
        elif provider == 'meta':
            endpoint = f'https://graph.facebook.com/v22.0/{whatsapp_media_id}'
            access_token = config.get('access_token')
+            if not access_token:
+                raise AppException("Missing access_token in configuration for Meta provider")
            headers = {'Authorization': f'Bearer {access_token}'}
            # rest of the Meta logic...
+        else:
+            raise AppException(f"Unsupported WhatsApp BSP provider: {provider}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
provider = config.get("bsp_type", "meta")
if provider == '360dialog':
endpoint = f'https://waba-v2.360dialog.io/{whatsapp_media_id}'
headers = {
'D360-API-KEY': config.get('api_key'),
}
resp = requests.get(endpoint, headers=headers, stream=True)
if resp.status_code != 200:
raise AppException(f"Failed to download media from 360 dialog: {resp.status_code} - {resp.text}")
json_resp = resp.json()
download_url = json_resp.get("url")
download_url = download_url.replace('https://lookaside.fbsbx.com', 'https://waba-v2.360dialog.io')
mime_type = json_resp.get("mime_type")
extension = mimetypes.guess_extension(mime_type) or ''
file_path = f"whataspp_360_{whatsapp_media_id}{extension}"
elif provider == 'meta':
endpoint = f'https://graph.facebook.com/v22.0/{whatsapp_media_id}'
access_token = config.get('access_token')
headers = {'Authorization': f'Bearer {access_token}'}
media_info_resp = requests.get(
endpoint,
params={"fields": "url", "access_token": access_token},
timeout=10
)
if media_info_resp.status_code != 200:
raise AppException(f"Failed to get url from meta for media: {whatsapp_media_id}")
json_resp = media_info_resp.json()
download_url = json_resp.get("url")
mime_type = json_resp.get("mime_type")
extension = mimetypes.guess_extension(mime_type) or ''
file_path = f"whataspp_meta_{whatsapp_media_id}{extension}"
provider = config.get("bsp_type", "meta")
if provider == '360dialog':
api_key = config.get('api_key')
if not api_key:
raise AppException("Missing api_key in configuration for 360dialog provider")
endpoint = f'https://waba-v2.360dialog.io/{whatsapp_media_id}'
headers = {
'D360-API-KEY': api_key,
}
resp = requests.get(endpoint, headers=headers, stream=True)
if resp.status_code != 200:
raise AppException(f"Failed to download media from 360 dialog: {resp.status_code} - {resp.text}")
json_resp = resp.json()
download_url = json_resp.get("url")
download_url = download_url.replace('https://lookaside.fbsbx.com', 'https://waba-v2.360dialog.io')
mime_type = json_resp.get("mime_type")
extension = mimetypes.guess_extension(mime_type) or ''
file_path = f"whataspp_360_{whatsapp_media_id}{extension}"
elif provider == 'meta':
endpoint = f'https://graph.facebook.com/v22.0/{whatsapp_media_id}'
access_token = config.get('access_token')
if not access_token:
raise AppException("Missing access_token in configuration for Meta provider")
headers = {'Authorization': f'Bearer {access_token}'}
media_info_resp = requests.get(
endpoint,
params={"fields": "url", "access_token": access_token},
timeout=10
)
if media_info_resp.status_code != 200:
raise AppException(f"Failed to get url from meta for media: {whatsapp_media_id}")
json_resp = media_info_resp.json()
download_url = json_resp.get("url")
mime_type = json_resp.get("mime_type")
extension = mimetypes.guess_extension(mime_type) or ''
file_path = f"whataspp_meta_{whatsapp_media_id}{extension}"
else:
raise AppException(f"Unsupported WhatsApp BSP provider: {provider}")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant