a digital person for bluesky
1"""Tool for creating standalone posts on X (Twitter).""" 2import os 3import json 4import logging 5import requests 6from typing import Optional 7from pydantic import BaseModel, Field, validator 8from requests_oauthlib import OAuth1 9 10logger = logging.getLogger(__name__) 11 12 13class PostToXArgs(BaseModel): 14 text: str = Field( 15 ..., 16 description="Text content for the X post (max 280 characters)" 17 ) 18 19 @validator('text') 20 def validate_text_length(cls, v): 21 if len(v) > 280: 22 raise ValueError(f"Text exceeds 280 character limit (current: {len(v)} characters)") 23 return v 24 25 26def post_to_x(text: str) -> str: 27 """ 28 Create a new standalone post on X (Twitter). This is not a reply to another post. 29 30 Use this tool when you want to share a thought, observation, or announcement 31 that isn't in response to anyone else's post. 32 33 Args: 34 text: Text content for the post (max 280 characters) 35 36 Returns: 37 Success message with post ID if successful 38 39 Raises: 40 Exception: If text exceeds character limit or posting fails 41 """ 42 # Validate input 43 if len(text) > 280: 44 raise Exception(f"Text exceeds 280 character limit (current: {len(text)} characters)") 45 46 try: 47 # Get credentials from environment variables 48 consumer_key = os.environ.get("X_CONSUMER_KEY") 49 consumer_secret = os.environ.get("X_CONSUMER_SECRET") 50 access_token = os.environ.get("X_ACCESS_TOKEN") 51 access_token_secret = os.environ.get("X_ACCESS_TOKEN_SECRET") 52 53 if not all([consumer_key, consumer_secret, access_token, access_token_secret]): 54 raise Exception("Missing X API credentials in environment variables") 55 56 # Create OAuth 1.0a authentication 57 auth = OAuth1( 58 consumer_key, 59 client_secret=consumer_secret, 60 resource_owner_key=access_token, 61 resource_owner_secret=access_token_secret 62 ) 63 64 # Prepare the request 65 url = "https://api.x.com/2/tweets" 66 headers = {"Content-Type": "application/json"} 67 payload = {"text": text} 68 69 # Make the POST request 70 response = requests.post( 71 url, 72 headers=headers, 73 json=payload, 74 auth=auth 75 ) 76 77 # Check response 78 if response.status_code == 201: 79 result = response.json() 80 if 'data' in result: 81 tweet_id = result['data'].get('id', 'unknown') 82 tweet_url = f"https://x.com/i/status/{tweet_id}" 83 logger.info(f"Successfully posted to X: {tweet_url}") 84 return f"Successfully posted to X. Tweet ID: {tweet_id}. URL: {tweet_url}" 85 else: 86 raise Exception(f"Unexpected response format: {result}") 87 else: 88 error_msg = f"X API error: {response.status_code} - {response.text}" 89 logger.error(error_msg) 90 raise Exception(error_msg) 91 92 except requests.exceptions.RequestException as e: 93 error_msg = f"Network error posting to X: {str(e)}" 94 logger.error(error_msg) 95 raise Exception(error_msg) 96 except Exception as e: 97 if "Missing X API credentials" in str(e) or "X API error" in str(e): 98 raise 99 error_msg = f"Unexpected error posting to X: {str(e)}" 100 logger.error(error_msg) 101 raise Exception(error_msg)