1- import re
1+ import time
2+ from datetime import datetime
23from slack_sdk import WebClient
34from slack_sdk .errors import SlackApiError
45from hawk_scanner .internals import system
@@ -21,32 +22,54 @@ def connect_slack(args, token):
2122 system .print_error (args , f"Failed to connect to Slack with error: { e .response ['error' ]} " )
2223 return None
2324
24- def check_slack_messages (args , client , patterns , profile_name , channel_types , channel_names = None ):
25+ def check_slack_messages (args , client , patterns , profile_name , channel_types , channel_ids = None , limit_mins = 60 ):
2526 results = []
2627 try :
2728 team_info = client .team_info ()
2829 workspace_url = team_info ["team" ]["url" ].rstrip ('/' )
30+
31+ # Get the Unix timestamp for 'limit_mins' minutes ago
32+ current_time = time .time ()
33+ oldest_time = current_time - (limit_mins * 60 )
34+
35+ # Convert to human-readable time for debugging
36+ current_time_readable = datetime .fromtimestamp (current_time ).strftime ('%Y-%m-%d %H:%M:%S' )
37+ oldest_time_readable = datetime .fromtimestamp (oldest_time ).strftime ('%Y-%m-%d %H:%M:%S' )
38+
39+ system .print_info (args , f"Current Time: { current_time_readable } " )
40+ system .print_info (args , f"Fetching messages from the last { limit_mins } minutes (Oldest Time: { oldest_time_readable } , Unix: { int (oldest_time )} )" )
41+
2942 # Get all channels of specified types
30- channels = client .conversations_list (types = channel_types )["channels" ]
43+ channels = []
44+ if not channel_ids :
45+ system .print_info ("Getting all channels because no channel_ids provided" )
46+ channels = client .conversations_list (types = channel_types )["channels" ]
47+ else :
48+ system .print_info (args , "Getting channels by channel_ids" )
49+ for channel_id in channel_ids :
50+ try :
51+ channel = client .conversations_info (channel = channel_id )["channel" ]
52+ channels .append (channel )
53+ except SlackApiError as e :
54+ system .print_error (args , f"Failed to fetch channel with id { channel_id } with error: { e .response ['error' ]} " )
3155
32- # Filter channels by names if provided
33- if channel_names :
34- channels = [channel for channel in channels if channel ['name' ] in channel_names ]
35-
3656 system .print_info (args , f"Found { len (channels )} channels of type { channel_types } " )
3757 system .print_info (args , f"Checking messages in channels: { ', ' .join ([channel ['name' ] for channel in channels ])} " )
38-
58+
3959 for channel in channels :
4060 channel_name = channel ["name" ]
4161 channel_id = channel ["id" ]
4262
43- # Get messages from the channel
63+ # Get messages from the channel within the time range
4464 system .print_info (args , f"Checking messages in channel { channel_name } ({ channel_id } )" )
45- messages = client .conversations_history (channel = channel_id )["messages" ]
65+ messages = client .conversations_history (channel = channel_id , oldest = oldest_time )["messages" ]
4666
4767 for message in messages :
4868 user = message .get ("user" , "" )
4969 text = message .get ("text" )
70+ message_ts = message .get ("ts" )
71+
72+ # Check main message for matches
5073 if text :
5174 matches = system .match_strings (args , text )
5275 if matches :
@@ -59,10 +82,42 @@ def check_slack_messages(args, client, patterns, profile_name, channel_types, ch
5982 'matches' : list (set (match ['matches' ])),
6083 'sample_text' : match ['sample_text' ],
6184 'profile' : profile_name ,
62- 'message_link' : workspace_url + f"/archives/{ channel_id } /p{ message [ 'ts' ] .replace ('.' , '' )} " ,
85+ 'message_link' : workspace_url + f"/archives/{ channel_id } /p{ message_ts .replace ('.' , '' )} " ,
6386 'data_source' : 'slack'
6487 })
88+
89+ # Check for replies (threads)
90+ if "thread_ts" in message :
91+ thread_ts = message ["thread_ts" ]
92+
93+ # Fetch replies for the thread
94+ replies = client .conversations_replies (channel = channel_id , ts = thread_ts , oldest = oldest_time )["messages" ]
95+
96+ # Exclude parent message and check replies
97+ for reply in replies :
98+ if reply ["ts" ] != thread_ts : # Skip the parent message
99+ reply_user = reply .get ("user" , "" )
100+ reply_text = reply .get ("text" )
101+ reply_ts = reply .get ("ts" )
102+
103+ if reply_text :
104+ reply_matches = system .match_strings (args , reply_text )
105+ if reply_matches :
106+ for match in reply_matches :
107+ results .append ({
108+ 'channel_id' : channel_id ,
109+ 'channel_name' : channel_name ,
110+ 'user' : reply_user ,
111+ 'pattern_name' : match ['pattern_name' ],
112+ 'matches' : list (set (match ['matches' ])),
113+ 'sample_text' : match ['sample_text' ],
114+ 'profile' : profile_name ,
115+ 'message_link' : workspace_url + f"/archives/{ channel_id } /p{ reply_ts .replace ('.' , '' )} " ,
116+ 'data_source' : 'slack'
117+ })
118+
65119 return results
120+
66121 except SlackApiError as e :
67122 system .print_error (args , f"Failed to fetch messages from Slack with error: { e .response ['error' ]} " )
68123 return results
@@ -82,7 +137,8 @@ def execute(args):
82137 for key , config in slack_config .items ():
83138 token = config .get ('token' )
84139 channel_types = config .get ('channel_types' , "public_channel,private_channel" )
85- channel_names = config .get ('channel_names' , None )
140+ channel_ids = config .get ('channel_ids' , [])
141+ limit_mins = config .get ('limit_mins' , 60 )
86142
87143 if token :
88144 system .print_info (args , f"Checking Slack Profile { key } " )
@@ -92,7 +148,7 @@ def execute(args):
92148
93149 client = connect_slack (args , token )
94150 if client :
95- results += check_slack_messages (args , client , patterns , key , channel_types , channel_names )
151+ results += check_slack_messages (args , client , patterns , key , channel_types , channel_ids , limit_mins )
96152 else :
97153 system .print_error (args , "No Slack connection details found in connection.yml" )
98154 else :
0 commit comments