import requests import time import logging import gradio as gr from config import config logger = logging.getLogger(__name__) def post_to_linkedin(name, job_post): """ Post to LinkedIn via their API with improved UX feedback Args: name (str): Name of the person posting job_post (str): Content of the job post Yields: tuple: (status_message, success_link_update) """ logger.info(f"Starting LinkedIn post process for user: {name}") yield "Verifying inputs...", gr.update(value="", visible=False) # Validate configuration if not config.organisation_number or not config.token or not config.post_api_url: error_msg = "Configuration Error: Missing LinkedIn API variables. Please check Spaces Secrets." logger.error(error_msg) yield f"❌ {error_msg}", gr.update(visible=False) return # Check for ServiceTrigger if not config.service_trigger: warning_msg = "Configuration Error: Missing ServiceTrigger variable. FeedHire update will be manual." logger.warning(warning_msg) yield f"❌ {warning_msg}", gr.update(visible=False) # Validate inputs if not name.strip() or not job_post.strip(): error_msg = "Please fill in all fields (Name and Job Post)." logger.warning(f"Validation failed: {error_msg}") yield f"❌ {error_msg}", gr.update(visible=False) return # Prepare API request headers_dict = { 'Authorization': f'Bearer {config.token}', 'Content-Type': 'application/json', 'X-Restli-Protocol-Version': '2.0.0', 'LinkedIn-Version': '202510' } data = { "author": f"urn:li:organization:{config.organisation_number}", "commentary": f"{name}\n\n{job_post}", "visibility": "PUBLIC", "lifecycleState": "PUBLISHED", "distribution": { "feedDistribution": "MAIN_FEED", "targetEntities": [], "thirdPartyDistributionChannels": [] }, "isReshareDisabledByAuthor": False } try: logger.info("Sending post to LinkedIn API") yield "🚀 Posting to LinkedIn...", gr.update(visible=False) response = requests.post( config.post_api_url, headers=headers_dict, json=data, timeout=30 ) if response.status_code == 201: logger.info("LinkedIn post created successfully") # Wait for LinkedIn API to update for i in range(10, 0, -1): yield f"✅ Post successful! Waiting for LinkedIn API update... ({i} seconds remaining)", gr.update(visible=False) time.sleep(1) # Trigger FeedHire backend update if config.service_trigger: try: logger.info("Triggering FeedHire backend update") trigger_resp = requests.post(f"{config.service_trigger}", timeout=10) yield "🔄 FeedHire website triggered, waiting for job listing update...", gr.update(visible=False) if trigger_resp.status_code == 200: logger.info("FeedHire backend triggered successfully") yield "✅ Backend triggered successfully! FeedHire will update the listing shortly.", gr.update(visible=False) else: logger.warning(f"Backend trigger failed with status {trigger_resp.status_code}") yield f"⚠️ Backend trigger failed (Status: {trigger_resp.status_code}). FeedHire update may be delayed.", gr.update(visible=False) except Exception as trigger_error: logger.error(f"Error triggering async fetch: {trigger_error}") yield f"⚠️ Error triggering FeedHire backend: {trigger_error}", gr.update(visible=False) yield "✅ Post live! 🔍 Retrieving your post link...", gr.update(visible=False) # Fetch post URL try: logger.info("Fetching LinkedIn post URL") post_api_url = "https://api.linkedin.com/rest/posts" headers = { "Authorization": f"Bearer {config.token}", "LinkedIn-Version": "202510", "X-Restli-Protocol-Version": "2.0.0" } params = { "q": "author", "author": f"urn:li:organization:{config.organisation_number}", "count": 1 } fetch_response = requests.get(post_api_url, headers=headers, params=params, timeout=30) if fetch_response.status_code == 200: fetch_data = fetch_response.json() if fetch_data.get("elements") and len(fetch_data["elements"]) > 0: post_id_urn = fetch_data["elements"][0]["id"] if "share" in post_id_urn: post_id = post_id_urn.split(":")[-1] post_url = f"https://www.linkedin.com/feed/update/urn:li:share:{post_id}/" elif "activity" in post_id_urn: post_id = post_id_urn.split(":")[-1] post_url = f"https://www.linkedin.com/feed/update/urn:li:activity:{post_id}/" else: raise Exception("Unknown post URN format") logger.info(f"Post URL retrieved: {post_url}") print(f"Post URL: {post_url}") yield "✅ Post published successfully!", gr.update(value=f"### 🎉 [Click here to view your post on LinkedIn →]({post_url})", visible=True) return # Fallback if URL retrieval fails logger.warning("Could not retrieve post URL, using fallback") yield "✅ Post published! (Could not auto-retrieve post link. Please check the LinkedIn page.)", gr.update(visible=False) except Exception as fetch_error: logger.error(f"Error fetching post: {fetch_error}") yield f"✅ Post published! (Error retrieving link: {fetch_error})", gr.update(visible=False) else: # Handle API errors try: error_data = response.json() error_message = error_data.get('message', response.text) except requests.exceptions.JSONDecodeError: error_message = response.text error_msg = f"Error {response.status_code}: {error_message}" logger.error(f"LinkedIn API error: {error_msg}") print(f"❌ {error_msg}") yield f"❌ {error_msg}", gr.update(visible=False) except requests.exceptions.Timeout: error_msg = "Request timed out. Please try again." logger.error(error_msg) yield f"❌ Error: {error_msg}", gr.update(visible=False) except requests.exceptions.RequestException as e: error_msg = f"Network Error: {str(e)}" logger.error(error_msg) yield f"❌ {error_msg}", gr.update(visible=False) except Exception as e: error_msg = f"Error: {str(e)}" logger.exception("Unexpected error in post_to_linkedin") yield f"❌ {error_msg}", gr.update(visible=False)