228 lines
6.9 KiB
Python
228 lines
6.9 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 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()
|