bytelyst-devops-tools/MERGED/slack_poster.py

219 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""
Slack Message Poster CLI Tool
A command-line tool that posts messages to Slack channels using the Slack Web API.
"""
import argparse
import sys
from datetime import datetime
from typing import Optional
try:
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError, SlackClientError
except ImportError:
print("Error: slack_sdk not installed. Run: pip install slack_sdk")
sys.exit(1)
class SlackPoster:
"""Main class for posting messages to Slack."""
def __init__(self, token: str):
"""Initialize the Slack client with the provided token."""
self.client = WebClient(token=token)
def post_message(self, channel: str, message: str) -> dict:
"""
Post a message to the specified Slack channel.
Args:
channel: Slack channel ID (e.g., C04ABC123)
message: Message text to post
Returns:
dict: Response from Slack API
Raises:
SlackApiError: If the API call fails
SlackClientError: If there's a client error
"""
try:
response = self.client.chat_postMessage(
channel=channel,
text=message
)
return response
except SlackApiError as e:
raise SlackApiError(f"Slack API Error: {e.response['error']}")
except SlackClientError as e:
raise SlackClientError(f"Slack Client Error: {e}")
def get_user_input(prompt: str, required: bool = True) -> str:
"""
Get user input with optional validation.
Args:
prompt: The prompt to display to the user
required: Whether the input is required
Returns:
str: User input
"""
while True:
value = input(prompt).strip()
if value or not required:
return value
print("This field is required. Please try again.")
def validate_token(token: str) -> bool:
"""
Basic validation for Slack token format.
Args:
token: The token to validate
Returns:
bool: True if token format looks valid
"""
if not token:
return False
# Basic format validation for Slack tokens
valid_prefixes = ['xoxb-', 'xoxp-', 'xoxa-', 'xoxr-']
return any(token.startswith(prefix) for prefix in valid_prefixes)
def validate_channel(channel: str) -> bool:
"""
Basic validation for Slack channel ID format.
Args:
channel: The channel ID to validate
Returns:
bool: True if channel format looks valid
"""
if not channel:
return False
# Slack channel IDs typically start with C, D, or G
return channel.startswith(('C', 'D', 'G')) and len(channel) > 1
def main():
"""Main function to handle CLI arguments and execute the message posting."""
parser = argparse.ArgumentParser(
description="Post a message to a Slack channel",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python slack_poster.py -t xoxb-1234567890-1234567890-abcdefghijklmnopqrstuvwx -c C04ABC123 -m "Hello World!"
python slack_poster.py --token xoxb-1234567890-1234567890-abcdefghijklmnopqrstuvwx --channel C04ABC123 --message "Hello World!"
python slack_poster.py # Interactive mode - will prompt for missing parameters
"""
)
parser.add_argument(
'--token', '-t',
type=str,
help='Slack Bot/User OAuth token (e.g., xoxb-...)'
)
parser.add_argument(
'--channel', '-c',
type=str,
help='Slack channel ID (e.g., C04ABC123)'
)
parser.add_argument(
'--message', '-m',
type=str,
help='Message text to post'
)
args = parser.parse_args()
# Get token
token = args.token
if not token:
print("Slack Token not provided. Please enter it interactively:")
token = get_user_input("Enter your Slack token: ")
# Validate token format
if not validate_token(token):
print("Error: Invalid token format. Slack tokens typically start with 'xoxb-', 'xoxp-', 'xoxa-', or 'xoxr-'")
sys.exit(1)
# Get channel
channel = args.channel
if not channel:
print("Channel ID not provided. Please enter it interactively:")
channel = get_user_input("Enter Slack channel ID (e.g., C04ABC123): ")
# Validate channel format
if not validate_channel(channel):
print("Error: Invalid channel format. Channel IDs typically start with 'C', 'D', or 'G'")
sys.exit(1)
# Get message
message = args.message
if not message:
print("Message not provided. Please enter it interactively:")
message = get_user_input("Enter message text: ")
# Validate message
if not message.strip():
print("Error: Message cannot be empty")
sys.exit(1)
# Initialize Slack poster and send message
try:
poster = SlackPoster(token)
response = poster.post_message(channel, message)
# Extract timestamp from response
timestamp = response.get('ts', 'unknown')
channel_name = response.get('channel', channel)
# Convert timestamp to readable format
try:
dt = datetime.fromtimestamp(float(timestamp))
formatted_time = dt.strftime('%Y-%m-%d %H:%M:%S UTC')
except (ValueError, TypeError):
formatted_time = timestamp
print(f"✅ Success! Message posted to channel {channel_name}")
print(f"📅 Timestamp: {formatted_time}")
print(f"💬 Message: {message}")
except SlackApiError as e:
error_msg = str(e)
if "invalid_auth" in error_msg.lower():
print("❌ Error: Invalid token. Please check your Slack token.")
elif "channel_not_found" in error_msg.lower():
print(f"❌ Error: Channel '{channel}' not found. Please check the channel ID.")
elif "not_in_channel" in error_msg.lower():
print(f"❌ Error: Bot is not a member of channel '{channel}'. Please add the bot to the channel.")
elif "rate_limited" in error_msg.lower():
print("❌ Error: Rate limited. Please wait before trying again.")
else:
print(f"❌ Slack API Error: {error_msg}")
sys.exit(1)
except SlackClientError as e:
print(f"❌ Slack Client Error: {e}")
sys.exit(1)
except Exception as e:
print(f"❌ Unexpected error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()