2025-02-28 17:34:28 +00:00
import os , sys
from slack_bolt import App
from slack_bolt . adapter . socket_mode import SocketModeHandler
from dotenv import load_dotenv
import firepup650 as fp
from traceback import format_exc
input = fp . replitInput
fp . replitCursor = (
fp . bcolors . REPLIT + " >>> " + fp . bcolors . RESET
) # Totally not hijacking one of my functions to use ;P
load_dotenv ( )
for requiredVar in [ " SLACK_BOT_TOKEN " , " SLACK_APP_TOKEN " ] :
if not os . environ . get ( requiredVar ) :
raise ValueError (
f ' Missing required environment variable " { requiredVar } " . Please create a .env file in the same directory as this script and define the missing variable. '
)
print ( " [INFO] Establishing a connection to slack... " )
app = App ( token = os . environ . get ( " SLACK_BOT_TOKEN " ) )
client = app . client
2025-02-28 18:10:56 +00:00
def buildThreadedMessages ( messages : dict ) - > dict :
print ( " [INFO] Building messages, this might take a little bit... " )
texts = { }
for i in range ( len ( messages ) ) :
if not messages [ i ] . get ( " user " ) and messages [ i ] . get (
" username "
) : # Workflows don't have a userid, obviously
messages [ i ] [ " user " ] = f ' { messages [ i ] . get ( " username " ) } |WORKFLOW '
if not messages [ i ] . get ( " user " ) and messages [ i ] . get (
" subtype "
) : # Apps sending to channel also don't...
messages [ i ] [ " user " ] = messages [ i ] [ " root " ] [
" user "
] # This is probably technically wrong, but I don't care.
label = f ' [ { messages [ i ] [ " ts " ] } ] <@ { messages [ i ] [ " user " ] } >: { messages [ i ] [ " text " ] } '
for user in userMappings :
label = label . replace ( user , userMappings [ user ] )
texts [ label ] = i
return texts
2025-02-28 18:26:20 +00:00
def buildMessages ( messages : dict ) - > str :
2025-02-28 18:10:56 +00:00
print ( " [INFO] Building messages, this might take a little bit... " )
2025-02-28 18:26:20 +00:00
for i in range ( len ( messages ) - 1 , - 1 , - 1 ) :
2025-02-28 18:10:56 +00:00
if not messages [ i ] . get ( " user " ) and messages [ i ] . get (
" username "
) : # Workflows don't have a userid, obviously
messages [ i ] [ " user " ] = f ' { messages [ i ] . get ( " username " ) } |WORKFLOW '
if not messages [ i ] . get ( " user " ) and messages [ i ] . get (
" subtype "
) : # Apps sending to channel also don't...
messages [ i ] [ " user " ] = messages [ i ] [ " root " ] [
" user "
] # This is probably technically wrong, but I don't care.
msg = f ' [MSGS] [ { messages [ i ] [ " ts " ] } ] <@ { messages [ i ] [ " user " ] } >: { messages [ i ] [ " text " ] } '
for user in userMappings :
msg = msg . replace ( user , userMappings [ user ] )
print ( msg )
2025-02-28 18:26:20 +00:00
return messages [ 0 ] [ " ts " ]
2025-02-28 18:10:56 +00:00
2025-02-28 17:34:28 +00:00
userMappings = { }
try :
if " --no-cache " in sys . argv :
print ( " [INFO] Skipping cache on user request " )
raise ImportError ( " User requested to skip cache " )
print ( " [INFO] Trying to load user mappings from cache... " )
from cache import userMappings
print (
""" [INFO] Cache load OK.
[ INFO ] Reminder : If you need to regenerate the cache , call the script with ` - - no - cache ` """
)
except ImportError :
users_list = [ ]
print ( " [WARN] Cache load failed, falling back to full load from slack... " )
cursor = " N/A "
pages = 0
while (
cursor
) : # If slack gives us a cursor, then we ain't done loading user data yet
data = " "
if cursor != " N/A " :
data = client . users_list ( cursor = cursor , limit = 1000 )
else :
data = client . users_list ( limit = 1000 )
cursor = data [ " response_metadata " ] [ " next_cursor " ]
users_list . extend ( data [ " members " ] )
pages + = 1
print (
f " [INFO] Pages of users loaded: { pages } (Estimated user count: { pages } 000) "
)
del pages
print ( " [INFO] Building user mappings now, this shouldn ' t take long... " )
# print(users_list[38])
for (
user
) in (
users_list
) : # Map user ID mentions to user name mentions, it's nicer when printing messages for thread selection.
userMappings [ f " <@ { user [ ' id ' ] } > " ] = (
2025-02-28 18:26:20 +00:00
f " <@ { user [ ' id ' ] } | { user [ ' profile ' ] [ ' display_name_normalized ' ] } > "
2025-02-28 17:34:28 +00:00
if user [ " profile " ] [ " display_name_normalized " ]
else ( # User is missing a display name for some reason, fallback to real names
2025-02-28 18:26:20 +00:00
f " <@ { user [ ' id ' ] } | { user [ ' profile ' ] [ ' real_name_normalized ' ] } > "
2025-02-28 17:34:28 +00:00
if user [ " profile " ] [ " real_name_normalized " ]
else f " <@ { user [ ' id ' ] } > " # User is missing a real name too... Fallback to ID
)
)
print ( " [INFO] All mappings generated, writing cache file now... " )
with open (
" cache.py " , " w "
) as cacheFile : # It is many times faster to load from a local file instead of from slack
cacheFile . write ( f " userMappings = { userMappings } " )
print ( " [INFO] Cache saved. " )
print ( " [INFO] User mappings loaded. User count: " , len ( userMappings ) )
2025-02-28 18:10:56 +00:00
global inChannel
inChannel = False
2025-02-28 17:34:28 +00:00
if __name__ == " __main__ " :
print ( " [INFO] ^D at any time to terminate program " )
while 1 :
chan = input ( " Channel ID " )
try :
2025-03-07 14:58:32 +00:00
oldest_ts = " "
2025-02-28 18:10:56 +00:00
try :
print (
" [INFO] Trying to load the last 50 messages sent in this channel... "
)
res = client . conversations_history (
channel = chan , inclusive = True , limit = 50
)
buildMessages ( res [ " messages " ] )
2025-03-07 14:58:32 +00:00
oldest_ts = res [ " messages " ] [ 0 ] [ " ts " ]
2025-02-28 18:10:56 +00:00
del res
except Exception as E :
print ( " [WARN] Exception " )
for line in format_exc ( ) . split ( " \n " ) [ : - 1 ] :
print ( f " [WARN] { line } " )
print (
" [HELP] The bot probably isn ' t in this channel. If it ' s public you can likely send anyways, but this will fail otherwise. "
)
2025-02-28 17:34:28 +00:00
print ( " [INFO] ^C to change channel " )
while 1 :
thread = input ( " Reply to a thread? (y|N) " ) . lower ( ) . startswith ( " y " )
ts = None
if thread :
hasID = (
input ( " Do you have the TS ID? (y|N) " ) . lower ( ) . startswith ( " y " )
)
if not hasID :
try :
print (
" [INFO] Getting the last 50 messages for threading options... "
)
res = client . conversations_history (
channel = chan , inclusive = True , limit = 50
)
messages = res [ " messages " ]
2025-02-28 18:10:56 +00:00
texts = buildThreadedMessages ( messages )
2025-02-28 17:34:28 +00:00
found = messages [
fp . menu (
texts ,
" Please select the message to reply to as a thread " ,
)
]
ts = found [ " ts " ]
except Exception as E :
print ( " [WARN] Exception: " )
for line in format_exc ( ) . split ( " \n " ) [ : - 1 ] :
print ( f " [WARN] { line } " )
2025-02-28 18:10:56 +00:00
print (
" [HELP] Does the bot have access to the channel you ' re trying to see? "
)
2025-02-28 17:34:28 +00:00
break
else :
ts = input ( " TS ID " )
print (
" [INFO] ^C to change/exit thread (^C twice if you want to change channel) "
)
try :
while 1 :
msg = input (
2025-03-07 14:58:32 +00:00
" [THRD] Message (Raw text, not blocks) "
2025-02-28 17:34:28 +00:00
) . replace ( " \\ n " , " \n " )
try :
client . chat_postMessage (
channel = chan , text = msg , thread_ts = ts
)
print ( " [INFO] Message sent (to the thread)! " )
except Exception as E :
print ( " [WARN] Exception: " )
for line in format_exc ( ) . split ( " \n " ) [ : - 1 ] :
print ( f " [WARN] { line } " )
break
except KeyboardInterrupt :
print ( )
if ts :
2025-03-07 14:58:32 +00:00
try :
print ( f " [INFO] Trying to load messages since { oldest_ts } ... " )
res = client . conversations_history (
channel = chan , inclusive = True , limit = 200 , oldest = oldest_ts
)
if len ( res [ " messages " ] ) > 1 :
buildMessages ( res [ " messages " ] [ : - 1 ] )
oldest_ts = res [ " messages " ] [ 0 ] [ " ts " ]
else :
print ( " [INFO] No new messages " )
del res
except Exception as E :
print ( " [WARN] Exception " )
for line in format_exc ( ) . split ( " \n " ) [ : - 1 ] :
print ( f " [WARN] { line } " )
print (
" [HELP] Does the bot have access to the channel you ' re trying to see? "
)
2025-02-28 17:34:28 +00:00
continue
2025-03-07 14:58:32 +00:00
msg = input ( " [CHAN] Message (Raw text, not blocks) " ) . replace (
2025-02-28 17:34:28 +00:00
" \\ n " , " \n "
)
try :
2025-03-07 14:58:32 +00:00
if msg != " " :
ts = client . chat_postMessage ( channel = chan , text = msg ) [ " ts " ]
print ( f " [INFO] Message sent (to the channel)! (TS ID: { ts } ) " )
try :
print ( f " [INFO] Trying to load messages since { oldest_ts } ... " )
res = client . conversations_history (
channel = chan , inclusive = True , limit = 200 , oldest = oldest_ts
)
if len ( res [ " messages " ] ) > 1 :
buildMessages ( res [ " messages " ] [ : - 1 ] )
oldest_ts = res [ " messages " ] [ 0 ] [ " ts " ]
else :
print ( " [INFO] No new messages " )
del res
except Exception as E :
print ( " [WARN] Exception " )
for line in format_exc ( ) . split ( " \n " ) [ : - 1 ] :
print ( f " [WARN] { line } " )
print (
" [HELP] Does the bot have access to the channel you ' re trying to see? "
)
2025-02-28 17:34:28 +00:00
except Exception as E :
print ( " [WARN] Exception: " )
for line in format_exc ( ) . split ( " \n " ) [ : - 1 ] :
print ( f " [WARN] { line } " )
break
except KeyboardInterrupt :
print ( )