33import json
44import boto3
55import time
6+ from functools import wraps
7+ from typing import Callable
68from slack_bolt import App
79from slack_bolt .adapter .aws_lambda import SlackRequestHandler
810from slack_sdk import WebClient
@@ -75,6 +77,29 @@ def mark_event_processed(event_id):
7577 logger .error (f"Failed to mark event as processed: { e } " )
7678
7779
80+ def deduplicate_event (func : Callable ) -> Callable :
81+ """
82+ Decorator to deduplicate Slack events based on event_id from the request body.
83+ Prevents re-processing of already handled events.
84+ """
85+
86+ @wraps (func )
87+ def wrapper (event , ack , body , * args , ** kwargs ):
88+ event_id = body .get ("event_id" )
89+ if not event_id :
90+ logger .warning ("Missing event_id in Slack event body." )
91+ return func (event , ack , body , * args , ** kwargs )
92+
93+ if is_duplicate_event (event_id ):
94+ logger .info (f"Duplicate event detected, skipping: { event_id } " )
95+ return
96+
97+ mark_event_processed (event_id )
98+ return func (event , ack , body , * args , ** kwargs )
99+
100+ return wrapper
101+
102+
78103def trigger_async_processing (event_data ):
79104 """
80105 Trigger async processing of the Slack event to avoid timeout issues.
@@ -164,47 +189,26 @@ def process_async_slack_event(slack_event_data):
164189
165190# Handle @mentions in channels and DMs
166191@app .event ("app_mention" )
192+ @deduplicate_event
167193def handle_app_mention (event , ack , body ):
168194 """Handle when the bot is @mentioned"""
169195 event_id = body .get ("event_id" )
170-
171- # Check for duplicate
172- if is_duplicate_event (event_id ):
173- logger .info (f"Skipping duplicate event { event_id } " )
174- return
175-
176- # Mark as processed
177- mark_event_processed (event_id )
178-
179- # Log the event
180196 user_id = event .get ("user" , "unknown" )
181197 logger .info (f"Processing @mention from user { user_id } " , extra = {"event_id" : event_id })
182198
183- # Trigger async processing
184199 trigger_async_processing ({"event" : event , "event_id" : event_id , "bot_token" : bot_token })
185200
186201
187202# Handle direct messages
188203@app .event ("message" )
204+ @deduplicate_event
189205def handle_direct_message (event , ack , body ):
190206 """Handle direct messages to the bot"""
191- # Only respond to direct messages (not channel messages)
192207 if event .get ("channel_type" ) == "im" :
193208 event_id = body .get ("event_id" )
194-
195- # Check for duplicate
196- if is_duplicate_event (event_id ):
197- logger .info (f"Skipping duplicate DM event { event_id } " )
198- return
199-
200- # Mark as processed
201- mark_event_processed (event_id )
202-
203- # Log the event
204209 user_id = event .get ("user" , "unknown" )
205210 logger .info (f"Processing DM from user { user_id } " , extra = {"event_id" : event_id })
206211
207- # Trigger async processing
208212 trigger_async_processing ({"event" : event , "event_id" : event_id , "bot_token" : bot_token })
209213
210214
@@ -216,13 +220,5 @@ def handler(event: dict, context: LambdaContext) -> dict:
216220 process_async_slack_event (event ["slack_event" ])
217221 return {"statusCode" : 200 }
218222
219- # Handle Slack events with no-retry header
220- try :
221- slack_handler = SlackRequestHandler (app = app )
222- slack_handler .handle (event , context )
223-
224- # Return 202 with x-slack-no-retry header to prevent retries
225- return {"statusCode" : 202 , "headers" : {"x-slack-no-retry" : "1" }}
226- except Exception as e :
227- logger .error (f"Error handling Slack event: { e } " )
228- return {"statusCode" : 202 , "headers" : {"x-slack-no-retry" : "1" }}
223+ slack_handler = SlackRequestHandler (app = app )
224+ return slack_handler .handle (event , context )
0 commit comments