Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

- Adding instagram feature to reply on reel post #1132

Merged
merged 3 commits into from
Feb 13, 2024

Conversation

hiteshghuge
Copy link
Collaborator

@hiteshghuge hiteshghuge commented Feb 6, 2024

Summary by CodeRabbit

  • New Features
    • Introduced handling for Instagram comments, including the ability to check if a message is a comment, retrieve sender IDs for comments, reply to comments, and process user messages with additional metadata.
    • Added default static comment reply configuration for Instagram connectors if not set.
    • Expanded testing to cover Instagram comment functionality and channel configuration for default and custom replies.

Copy link

coderabbitai bot commented Feb 6, 2024

Walkthrough

Walkthrough

This update enhances the chat handling capabilities for Instagram comments within a chat application framework. It introduces methods to identify, process, and reply to comments, alongside expanding the configuration and testing for Instagram-specific functionalities. The changes collectively improve the framework's interaction with users on Instagram by enabling dynamic responses to comments and ensuring configurations support these new features.

Changes

Files Change Summary
kairon/chat/handlers/channels/messenger.py Added methods for comment identification, processing, and replying. Updated message handling.
kairon/shared/chat/data_objects.py Added default value setting for "static_comment_reply" in Instagram configurations.
kairon/shared/chat/processor.py Added method to retrieve static comment replies for Instagram.
tests/integration_test/chat_service_test.py
tests/unit_test/chat/chat_test.py
Expanded testing to include Instagram comment handling and configuration saving.

"In the realm of code, where thoughts intertwine, 🐇

A rabbit hopped forth, making comments shine. 🌟

With each leap and bound, features align,

Crafting conversations, one line at a time." 📜✨

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

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>.
    • Generate unit-tests for this file.
  • 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 tests 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 generate interesting stats about this repository from git and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

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 as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • 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/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

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.

Review Status

Actionable comments generated: 7

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 706e6e8 and ac112a0.
Files ignored due to path filters (1)
  • metadata/integrations.yml is excluded by: !**/*.yml
Files selected for processing (5)
  • kairon/chat/handlers/channels/messenger.py (6 hunks)
  • kairon/shared/chat/data_objects.py (1 hunks)
  • kairon/shared/chat/processor.py (1 hunks)
  • tests/integration_test/chat_service_test.py (3 hunks)
  • tests/unit_test/chat/chat_test.py (1 hunks)
Additional comments: 4
kairon/chat/handlers/channels/messenger.py (2)
  • 82-84: The method _is_comment correctly identifies comments based on the message structure. Ensure that the "field" key is always present in the message for this logic to be reliable.
  • 184-185: The check for metadata.get("comment_id") before calling reply_on_comment is appropriate. Ensure metadata is always a dictionary to avoid AttributeError when accessing get.
tests/unit_test/chat/chat_test.py (2)
  • 610-631: Ensure the test method test_save_channel_config_insta_with_default_comment_reply includes assertions for the expected behavior of the system when a default comment reply is not explicitly set in the configuration. This test should verify that the system correctly applies a default reply message.
  • 633-655: The test method test_save_channel_config_insta_with_custom_comment_reply correctly verifies that a custom static comment reply is saved and retrievable. This ensures that the system can handle custom replies for Instagram comments as intended.

Comment on lines +2911 to +2967

@responses.activate
def test_instagram_comment():
def _mock_validate_hub_signature(*args, **kwargs):
return True

message = "@kairon_user_123 Thanks for reaching us, please check your inbox"
access_token = "EAAGa50I7D7cBAJ4AmXOhYAeOOZAyJ9fxOclQmn52hBwrOJJWBOxuJNXqQ2uN667z4vLekSEqnCQf41hcxKVZAe2pAZBrZCTENEj1IBe1CHEcG7J33ZApED9Tj9hjO5tE13yckNa8lP3lw2IySFqeg6REJR3ZCJUvp2h03PQs4W5vNZBktWF3FjQYz5vMEXLPzAFIJcZApBtq9wZDZD"
responses.add(
"POST", f"https://graph.facebook.com/v2.12/18009764417219041/replies?message={message}&access_token={access_token}", json={}
)
responses.add(
"POST", f"https://graph.facebook.com/v2.12/me/messages?access_token={access_token}", json={}
)

with mock.patch.object(EndpointConfig, "request") as mock_action_execution:
mock_action_execution.return_value = {"responses": [{"response": "Welcome to kairon!"}], "events": []}

with patch.object(InstagramHandler, "validate_hub_signature", _mock_validate_hub_signature):
response = client.post(
f"/api/bot/instagram/{bot}/{token}",
headers={"hub.verify_token": "valid"},
json={
"entry": [
{
"id": "17841456706109718",
"time": 1707144192,
"changes": [
{
"value": {
"from": {
"id": "6489091794524304",
"username": "kairon_user_123"
},
"media": {
"id": "18013303267972611",
"media_product_type": "REELS"
},
"id": "18009764417219041",
"text": "Hi"
},
"field": "comments"
}
]
}
],
"object": "instagram"
})
time.sleep(5)
mock_action_execution.assert_awaited_once()

actual = response.json()
print(f"Actual response for instagram is {actual}")
assert actual == 'success'
assert MeteringProcessor.get_metric_count(user['account'], metric_type=MetricType.prod_chat,
channel_type="instagram") > 0

Copy link

Choose a reason for hiding this comment

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

The test test_instagram_comment correctly simulates receiving an Instagram comment and asserts the expected behavior. However, consider adding more detailed assertions to verify the content of the response or the actions taken by the system in response to the Instagram comment.

Comment on lines +2969 to +3014
@responses.activate
def test_instagram_comment_with_parent_comment():
def _mock_validate_hub_signature(*args, **kwargs):
return True

with mock.patch.object(EndpointConfig, "request") as mock_action_execution:
mock_action_execution.return_value = {"responses": [{"response": "Welcome to kairon!"}], "events": []}

with patch.object(InstagramHandler, "validate_hub_signature", _mock_validate_hub_signature):
response = client.post(
f"/api/bot/instagram/{bot}/{token}",
headers={"hub.verify_token": "valid"},
json={
"entry": [
{
"id": "17841456706109718",
"time": 1707144192,
"changes": [
{
"value": {
"from": {
"id": "6489091794524304",
"username": "_hdg_photography"
},
"media": {
"id": "18013303267972611",
"media_product_type": "REELS"
},
"id": "18009764417219042",
"parent_id": "18009764417219041",
"text": "Hi"
},
"field": "comments"
}
]
}
],
"object": "instagram"
})

actual = response.json()
print(f"Actual response for instagram is {actual}")
assert actual == 'success'
assert MeteringProcessor.get_metric_count(user['account'], metric_type=MetricType.prod_chat,
channel_type="instagram") > 0

Copy link

Choose a reason for hiding this comment

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

The test test_instagram_comment_with_parent_comment is similar to the previous test but includes a parent comment ID to simulate a reply to an existing comment. Like the previous comment, consider adding more detailed assertions to verify the system's response to the comment with a parent ID.

Comment on lines 40 to 41
if self.connector_type == "instagram" and self.config.get("static_comment_reply") is None:
self.config["static_comment_reply"] = "Thanks for reaching us, please check your inbox"
Copy link

Choose a reason for hiding this comment

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

Ensure the default value for "static_comment_reply" is appropriate and aligns with the intended user experience. Consider externalizing the default message to a configuration file or environment variable for easier management and customization.

Comment on lines +153 to +157
@staticmethod
def get_instagram_static_comment(bot: str) -> str:
channel = ChatDataProcessor.get_channel_config(bot=bot, connector_type="instagram", mask_characters=False)
comment_response = channel.get("config", {}).get("static_comment_reply")
return comment_response
Copy link

Choose a reason for hiding this comment

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

Validate the existence of the Instagram channel configuration before attempting to access the "static_comment_reply" key to avoid potential KeyError or NoneType errors.

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.

Review Status

Actionable comments generated: 3

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between ac112a0 and 4a04539.
Files selected for processing (2)
  • kairon/chat/handlers/channels/messenger.py (6 hunks)
  • kairon/shared/chat/processor.py (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • kairon/shared/chat/processor.py
Additional comments: 3
kairon/chat/handlers/channels/messenger.py (3)
  • 83-86: Method _is_comment correctly identifies if a message is a comment based on the field value. This is a straightforward and effective approach.
  • 107-116: The handle method now correctly handles Instagram comments by checking for changes in the payload. Ensure that the logic for distinguishing between different types of messages (standard messages, postbacks, and comments) is thoroughly tested, especially in scenarios where multiple types of interactions might be present in a single payload.
  • 185-186: The conditional check for comment_id in metadata before calling reply_on_comment is a good practice. It ensures that the method is only invoked when necessary, which is efficient.

Comment on lines 230 to 241
async def reply_on_comment(
self, comment_id: Text, bot: Text, **kwargs: Any
):
body = {}
r = self.messenger_client.session.post(
'{graph_url}/{comment_id}/replies?message={message}'.
format(graph_url=self.messenger_client.graph_url,
comment_id=comment_id,
message=kwargs.get("static_comment_reply")),
params=self.messenger_client.auth_args,
json=body
)
Copy link

Choose a reason for hiding this comment

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

Ensure the request to the Instagram API in reply_on_comment is correctly formatted and secure. Specifically, validate the construction of the URL and parameters to prevent injection vulnerabilities. Also, check the response of the request for success or failure and handle accordingly. The current implementation does not escape or sanitize the message parameter, which could lead to injection vulnerabilities.

-            '{graph_url}/{comment_id}/replies?message={message}'.
+            '{graph_url}/{comment_id}/replies',
+            params={'message': kwargs.get("static_comment_reply"), **self.messenger_client.auth_args},

This change ensures that the message parameter is properly encoded and prevents injection attacks.


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.

Suggested change
async def reply_on_comment(
self, comment_id: Text, bot: Text, **kwargs: Any
):
body = {}
r = self.messenger_client.session.post(
'{graph_url}/{comment_id}/replies?message={message}'.
format(graph_url=self.messenger_client.graph_url,
comment_id=comment_id,
message=kwargs.get("static_comment_reply")),
params=self.messenger_client.auth_args,
json=body
)
async def reply_on_comment(
self, comment_id: Text, bot: Text, **kwargs: Any
):
body = {}
r = self.messenger_client.session.post(
'{graph_url}/{comment_id}/replies'.
format(graph_url=self.messenger_client.graph_url,
comment_id=comment_id),
params={'message': kwargs.get("static_comment_reply"), **self.messenger_client.auth_args},
json=body
)

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.

Review Status

Actionable comments generated: 3

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 4a04539 and ad18071.
Files ignored due to path filters (2)
  • system.yaml is excluded by: !**/*.yaml
  • tests/testing_data/system.yaml is excluded by: !**/*.yaml
Files selected for processing (2)
  • kairon/chat/handlers/channels/messenger.py (6 hunks)
  • kairon/shared/chat/data_objects.py (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • kairon/shared/chat/data_objects.py
Additional comments: 3
kairon/chat/handlers/channels/messenger.py (3)
  • 83-86: Method _is_comment correctly identifies if a message is a comment based on the field value.
  • 107-116: The handle method now correctly processes both messages and comments. Ensure that the logic for distinguishing between message types is robust and accounts for all expected message formats.
  • 185-186: The check for comment_id in metadata before calling reply_on_comment is appropriate. Ensure that reply_on_comment handles all edge cases and errors gracefully.

Comment on lines +42 to +44
sender_id = self.last_message.get("sender", {}).get("id", "")
if sender_id == '':
sender_id = self.last_message.get("value", {}).get("from", {}).get("id", "")
Copy link

Choose a reason for hiding this comment

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

Refactor the approach for retrieving sender_id to enhance code readability and maintainability.

-        sender_id = self.last_message.get("sender", {}).get("id", "")
-        if sender_id == '':
-            sender_id = self.last_message.get("value", {}).get("from", {}).get("id", "")
+        sender_id = self.last_message.get("sender", {}).get("id") or self.last_message.get("value", {}).get("from", {}).get("id", "")

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.

Suggested change
sender_id = self.last_message.get("sender", {}).get("id", "")
if sender_id == '':
sender_id = self.last_message.get("value", {}).get("from", {}).get("id", "")
sender_id = self.last_message.get("sender", {}).get("id") or self.last_message.get("value", {}).get("from", {}).get("id", "")

Comment on lines +158 to +170
async def comment(
self, message: Dict[Text, Any], metadata: Optional[Dict[Text, Any]], bot: str
):
if self._is_comment(message):
static_comment_reply = ChatDataProcessor.get_instagram_static_comment(bot=bot)
text = message["value"]["text"]
parent_id = message.get("value", {}).get("parent_id", None)
comment_id = message["value"]["id"]
user = message["value"]["from"]["username"]
metadata["comment_id"] = comment_id
metadata["static_comment_reply"] = f"@{user} {static_comment_reply}"
if not parent_id:
await self._handle_user_message(text, self.get_user_id(), metadata, bot)
Copy link

Choose a reason for hiding this comment

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

Ensure the presence of expected keys in the message dictionary to prevent KeyError. Sanitize static_comment_reply to prevent injection attacks.

-            metadata["static_comment_reply"] = f"@{user} {static_comment_reply}"
+            # Ensure static_comment_reply is sanitized to prevent injection attacks
+            sanitized_reply = sanitize(static_comment_reply)
+            metadata["static_comment_reply"] = f"@{user} {sanitized_reply}"

Note: Implement a sanitize function to escape or remove any potentially harmful characters.


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.

Suggested change
async def comment(
self, message: Dict[Text, Any], metadata: Optional[Dict[Text, Any]], bot: str
):
if self._is_comment(message):
static_comment_reply = ChatDataProcessor.get_instagram_static_comment(bot=bot)
text = message["value"]["text"]
parent_id = message.get("value", {}).get("parent_id", None)
comment_id = message["value"]["id"]
user = message["value"]["from"]["username"]
metadata["comment_id"] = comment_id
metadata["static_comment_reply"] = f"@{user} {static_comment_reply}"
if not parent_id:
await self._handle_user_message(text, self.get_user_id(), metadata, bot)
async def comment(
self, message: Dict[Text, Any], metadata: Optional[Dict[Text, Any]], bot: str
):
if self._is_comment(message):
static_comment_reply = ChatDataProcessor.get_instagram_static_comment(bot=bot)
text = message["value"]["text"]
parent_id = message.get("value", {}).get("parent_id", None)
comment_id = message["value"]["id"]
user = message["value"]["from"]["username"]
metadata["comment_id"] = comment_id
# Ensure static_comment_reply is sanitized to prevent injection attacks
sanitized_reply = sanitize(static_comment_reply)
metadata["static_comment_reply"] = f"@{user} {sanitized_reply}"
if not parent_id:
await self._handle_user_message(text, self.get_user_id(), metadata, bot)

Comment on lines +230 to +242
async def reply_on_comment(
self, comment_id: Text, bot: Text, **kwargs: Any
):
body = {}
_r = self.messenger_client.session.post(
'{graph_url}/{comment_id}/replies?message={message}'.
format(graph_url=self.messenger_client.graph_url,
comment_id=comment_id,
message=kwargs.get("static_comment_reply")),
params=self.messenger_client.auth_args,
json=body
)

Copy link

Choose a reason for hiding this comment

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

In reply_on_comment, ensure that the response from the POST request is checked for success or failure. Handle potential errors and log them appropriately.

+        response = _r.json()
+        if _r.status_code != 200:
+            logger.error(f"Failed to reply on comment: {response.get('error',{})}")

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.

Suggested change
async def reply_on_comment(
self, comment_id: Text, bot: Text, **kwargs: Any
):
body = {}
_r = self.messenger_client.session.post(
'{graph_url}/{comment_id}/replies?message={message}'.
format(graph_url=self.messenger_client.graph_url,
comment_id=comment_id,
message=kwargs.get("static_comment_reply")),
params=self.messenger_client.auth_args,
json=body
)
async def reply_on_comment(
self, comment_id: Text, bot: Text, **kwargs: Any
):
body = {}
_r = self.messenger_client.session.post(
'{graph_url}/{comment_id}/replies?message={message}'.
format(graph_url=self.messenger_client.graph_url,
comment_id=comment_id,
message=kwargs.get("static_comment_reply")),
params=self.messenger_client.auth_args,
json=body
)
response = _r.json()
if _r.status_code != 200:
logger.error(f"Failed to reply on comment: {response.get('error',{})}")

@sfahad1414 sfahad1414 merged commit 17e2627 into digiteinfotech:master Feb 13, 2024
7 of 8 checks passed
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.

3 participants