|
| 1 | +""" |
| 2 | +Slack event handlers - handles @mentions and direct messages to the bot |
| 3 | +""" |
| 4 | + |
1 | 5 | import time |
2 | | -from slack_bolt import App |
3 | | -from aws_lambda_powertools import Logger |
| 6 | +import json |
| 7 | +import os |
| 8 | +import boto3 |
4 | 9 | from botocore.exceptions import ClientError |
5 | | -from app.slack.slack_events import trigger_async_processing |
| 10 | +from aws_lambda_powertools import Logger |
6 | 11 |
|
7 | 12 | logger = Logger(service="slackBotFunction") |
8 | 13 |
|
9 | | -# Lazy initialization of Slack app |
10 | | -_app = None |
11 | | - |
12 | | - |
13 | | -def get_app(): |
14 | | - """Get or create the Slack app instance""" |
15 | | - global _app |
16 | | - if _app is None: |
17 | | - from app.config.config import bot_token, signing_secret |
18 | | - |
19 | | - _app = App( |
20 | | - process_before_response=True, |
21 | | - token=bot_token, |
22 | | - signing_secret=signing_secret, |
23 | | - ) |
24 | | - _setup_handlers(_app) |
25 | | - return _app |
26 | | - |
27 | 14 |
|
28 | | -def _setup_handlers(app_instance): |
29 | | - """Setup event handlers for the app""" |
| 15 | +def setup_handlers(app): |
| 16 | + """ |
| 17 | + Register all event handlers with the Slack app |
| 18 | + """ |
30 | 19 | from app.config.config import bot_token |
31 | 20 |
|
32 | | - @app_instance.middleware |
| 21 | + @app.middleware |
33 | 22 | def log_request(slack_logger, body, next): |
34 | | - """Middleware to log incoming Slack requests using AWS Lambda Powertools logger.""" |
35 | 23 | logger.debug("Slack request received", extra={"body": body}) |
36 | 24 | return next() |
37 | 25 |
|
38 | | - @app_instance.event("app_mention") |
| 26 | + @app.event("app_mention") |
39 | 27 | def handle_app_mention(event, ack, body): |
40 | | - """Handle when the bot is @mentioned""" |
| 28 | + """ |
| 29 | + Handle @mentions in channels |
| 30 | + """ |
41 | 31 | ack() |
42 | 32 |
|
43 | 33 | event_id = body.get("event_id") |
44 | | - if not event_id: |
45 | | - logger.warning("Missing event_id in Slack event body.") |
46 | | - elif is_duplicate_event(event_id): |
47 | | - logger.info(f"Duplicate event detected, skipping: {event_id}") |
| 34 | + if not event_id or is_duplicate_event(event_id): |
| 35 | + logger.info(f"Skipping duplicate or missing event: {event_id}") |
48 | 36 | return |
49 | 37 |
|
50 | 38 | user_id = event.get("user", "unknown") |
51 | 39 | logger.info(f"Processing @mention from user {user_id}", extra={"event_id": event_id}) |
52 | 40 |
|
53 | 41 | trigger_async_processing({"event": event, "event_id": event_id, "bot_token": bot_token}) |
54 | 42 |
|
55 | | - @app_instance.event("message") |
| 43 | + @app.event("message") |
56 | 44 | def handle_direct_message(event, ack, body): |
57 | | - """Handle direct messages to the bot""" |
| 45 | + """ |
| 46 | + Handle direct messages to the bot |
| 47 | + """ |
58 | 48 | ack() |
59 | 49 |
|
60 | | - if event.get("channel_type") == "im": |
61 | | - event_id = body.get("event_id") |
62 | | - if not event_id: |
63 | | - logger.warning("Missing event_id in Slack event body.") |
64 | | - elif is_duplicate_event(event_id): |
65 | | - logger.info(f"Duplicate event detected, skipping: {event_id}") |
66 | | - return |
| 50 | + # Only handle DMs, ignore channel messages |
| 51 | + if event.get("channel_type") != "im": |
| 52 | + return |
67 | 53 |
|
68 | | - user_id = event.get("user", "unknown") |
69 | | - logger.info(f"Processing DM from user {user_id}", extra={"event_id": event_id}) |
| 54 | + event_id = body.get("event_id") |
| 55 | + if not event_id or is_duplicate_event(event_id): |
| 56 | + logger.info(f"Skipping duplicate or missing event: {event_id}") |
| 57 | + return |
70 | 58 |
|
71 | | - trigger_async_processing({"event": event, "event_id": event_id, "bot_token": bot_token}) |
| 59 | + user_id = event.get("user", "unknown") |
| 60 | + logger.info(f"Processing DM from user {user_id}", extra={"event_id": event_id}) |
| 61 | + |
| 62 | + trigger_async_processing({"event": event, "event_id": event_id, "bot_token": bot_token}) |
72 | 63 |
|
73 | 64 |
|
74 | 65 | def is_duplicate_event(event_id): |
75 | | - """Check if event has already been processed using conditional put""" |
| 66 | + """ |
| 67 | + Check if we've already processed this event |
| 68 | + """ |
76 | 69 | from app.config.config import table |
77 | 70 |
|
78 | 71 | try: |
79 | 72 | ttl = int(time.time()) + 3600 # 1 hour TTL |
80 | 73 | table.put_item( |
81 | | - Item={"eventId": event_id, "ttl": ttl, "timestamp": int(time.time())}, |
82 | | - ConditionExpression="attribute_not_exists(eventId)", |
| 74 | + Item={"pk": f"event#{event_id}", "sk": "dedup", "ttl": ttl, "timestamp": int(time.time())}, |
| 75 | + ConditionExpression="attribute_not_exists(pk)", |
83 | 76 | ) |
84 | | - return False # Item didn't exist, so not a duplicate |
| 77 | + return False # Not a duplicate |
85 | 78 | except ClientError as e: |
86 | 79 | if e.response["Error"]["Code"] == "ConditionalCheckFailedException": |
87 | | - return True # Item already exists, so it's a duplicate |
| 80 | + return True # Duplicate |
| 81 | + logger.error(f"Error checking event duplication: {e}") |
88 | 82 | return False |
89 | 83 |
|
90 | 84 |
|
91 | | -# Create a module-level app instance that uses lazy loading |
92 | | -class AppProxy: |
93 | | - def __getattr__(self, name): |
94 | | - return getattr(get_app(), name) |
95 | | - |
| 85 | +def trigger_async_processing(event_data): |
| 86 | + """Fire off async processing to avoid timeout.""" |
| 87 | + lambda_client = boto3.client("lambda") |
96 | 88 |
|
97 | | -app = AppProxy() |
| 89 | + lambda_client.invoke( |
| 90 | + FunctionName=os.environ["AWS_LAMBDA_FUNCTION_NAME"], |
| 91 | + InvocationType="Event", |
| 92 | + Payload=json.dumps({"async_processing": True, "slack_event": event_data}), |
| 93 | + ) |
0 commit comments