FeedHireUI / linkedin_api.py
Rafii's picture
removed discord
d48cd2c
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)