#!/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 os 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) # Try to load environment variables from .env file try: from dotenv import load_dotenv load_dotenv() except ImportError: # dotenv not installed, continue without it pass 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 e except SlackClientError as e: raise 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 (try environment variable first, then command line, then interactive) token = args.token or os.getenv('SLACK_BOT_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 (try environment variable first, then command line, then interactive) channel = args.channel or os.getenv('SLACK_CHANNEL_ID') 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"[ERROR] Slack API Error: {error_msg}") sys.exit(1) except SlackClientError as e: print(f"[ERROR] Slack Client Error: {e}") sys.exit(1) except Exception as e: print(f"[ERROR] Unexpected error: {e}") sys.exit(1) if __name__ == "__main__": main()