-
Notifications
You must be signed in to change notification settings - Fork 82
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
Conversation
WalkthroughWalkthroughThis 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
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? TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Additionally, you can add CodeRabbit Configration File (
|
There was a problem hiding this 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
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 callingreply_on_comment
is appropriate. Ensuremetadata
is always a dictionary to avoidAttributeError
when accessingget
.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.
|
||
@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 | ||
|
There was a problem hiding this comment.
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.
@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 | ||
|
There was a problem hiding this comment.
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.
kairon/shared/chat/data_objects.py
Outdated
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" |
There was a problem hiding this comment.
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.
@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 |
There was a problem hiding this comment.
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.
There was a problem hiding this 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
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 thefield
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 callingreply_on_comment
is a good practice. It ensures that the method is only invoked when necessary, which is efficient.
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 | ||
) |
There was a problem hiding this comment.
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.
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 | |
) |
There was a problem hiding this 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
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 thefield
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
inmetadata
before callingreply_on_comment
is appropriate. Ensure thatreply_on_comment
handles all edge cases and errors gracefully.
sender_id = self.last_message.get("sender", {}).get("id", "") | ||
if sender_id == '': | ||
sender_id = self.last_message.get("value", {}).get("from", {}).get("id", "") |
There was a problem hiding this comment.
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.
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", "") |
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) |
There was a problem hiding this comment.
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.
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) |
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 | ||
) | ||
|
There was a problem hiding this comment.
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.
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',{})}") |
Summary by CodeRabbit