|
|
|
|
@ -713,88 +713,164 @@ def get_wp_tag_id(tag_name, wp_base_url, wp_username, wp_password): |
|
|
|
|
|
|
|
|
|
def post_to_wp(post_data, category, link, author, image_url, original_source, image_source="Pixabay", uploader=None, page_url=None, interest_score=4, post_id=None, should_post_tweet=True, summary=None): |
|
|
|
|
""" |
|
|
|
|
Post an article to WordPress and optionally tweet about it. |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
post_data (dict): Contains the article title and content. |
|
|
|
|
category (str): Category for the WordPress post. |
|
|
|
|
link (str): Original article URL. |
|
|
|
|
author (dict): Author information. |
|
|
|
|
image_url (str): URL of the featured image. |
|
|
|
|
original_source (str): Source of the original article. |
|
|
|
|
image_source (str): Source of the image (default: "Pixabay"). |
|
|
|
|
uploader (callable): Function to upload the image (optional). |
|
|
|
|
page_url (str): URL of the page (optional). |
|
|
|
|
interest_score (int): Score indicating article interest (default: 4). |
|
|
|
|
post_id (int): ID of the post if updating (default: None). |
|
|
|
|
should_post_tweet (bool): Whether to post a tweet (default: True). |
|
|
|
|
summary (str): The article summary to pass to tweet generation (default: None). |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
tuple: (post_id, post_url) of the created/updated WordPress post. |
|
|
|
|
Post or update content to WordPress, optionally tweeting the post. |
|
|
|
|
""" |
|
|
|
|
try: |
|
|
|
|
# Prepare WordPress post data |
|
|
|
|
wp_post = { |
|
|
|
|
"title": post_data["title"], |
|
|
|
|
"content": post_data["content"], |
|
|
|
|
"categories": [category], |
|
|
|
|
"status": "publish", |
|
|
|
|
"featured_media": 0, # Will be updated if image is uploaded |
|
|
|
|
import logging |
|
|
|
|
import requests |
|
|
|
|
import base64 |
|
|
|
|
from foodie_config import X_API_CREDENTIALS |
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
|
# Extract WordPress credentials from author dictionary |
|
|
|
|
wp_url = author.get("url") |
|
|
|
|
wp_username = author.get("username") |
|
|
|
|
wp_password = author.get("password") |
|
|
|
|
|
|
|
|
|
if not all([wp_url, wp_username, wp_password]): |
|
|
|
|
logger.error(f"Missing WordPress credentials for author: {wp_username or 'unknown'}") |
|
|
|
|
return None, None |
|
|
|
|
|
|
|
|
|
# Ensure wp_url ends with '/wp-json/wp/v2' |
|
|
|
|
if not wp_url.endswith('/wp-json/wp/v2'): |
|
|
|
|
wp_base_url = f"{wp_url.rstrip('/')}/wp-json/wp/v2" |
|
|
|
|
else: |
|
|
|
|
wp_base_url = wp_url |
|
|
|
|
|
|
|
|
|
# Hardcoded author ID map from old working version |
|
|
|
|
author_id_map = { |
|
|
|
|
"owenjohnson": 10, |
|
|
|
|
"javiermorales": 2, |
|
|
|
|
"aishapatel": 3, |
|
|
|
|
"trangnguyen": 12, |
|
|
|
|
"keishareid": 13, |
|
|
|
|
"lilamoreau": 7 |
|
|
|
|
} |
|
|
|
|
author_id = author_id_map.get(wp_username, 5) # Default to ID 5 if username not found |
|
|
|
|
|
|
|
|
|
# Handle image upload if provided |
|
|
|
|
if image_url and uploader: |
|
|
|
|
try: |
|
|
|
|
media_id = uploader(image_url, post_data["title"]) |
|
|
|
|
if media_id: |
|
|
|
|
wp_post["featured_media"] = media_id |
|
|
|
|
logging.info(f"Successfully uploaded image for post: {post_data['title']}") |
|
|
|
|
except Exception as e: |
|
|
|
|
logging.error(f"Failed to upload image for post {post_data['title']}: {str(e)}") |
|
|
|
|
headers = { |
|
|
|
|
"Authorization": "Basic " + base64.b64encode(f"{wp_username}:{wp_password}".encode()).decode(), |
|
|
|
|
"Content-Type": "application/json" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Add source attribution to content |
|
|
|
|
wp_post["content"] += f"\n\n<p>Source: <a href='{link}'>{original_source}</a></p>" |
|
|
|
|
# Test authentication |
|
|
|
|
auth_test = requests.get(f"{wp_base_url}/users/me", headers=headers) |
|
|
|
|
auth_test.raise_for_status() |
|
|
|
|
logger.info(f"Auth test passed for {wp_username}: {auth_test.json()['id']}") |
|
|
|
|
|
|
|
|
|
# Get or create category ID |
|
|
|
|
category_id = get_wp_category_id(category, wp_base_url, wp_username, wp_password) |
|
|
|
|
if not category_id: |
|
|
|
|
category_id = create_wp_category(category, wp_base_url, wp_username, wp_password) |
|
|
|
|
if not category_id: |
|
|
|
|
logger.warning(f"Failed to get or create category '{category}', using default") |
|
|
|
|
category_id = 1 # Fallback to 'Uncategorized' |
|
|
|
|
else: |
|
|
|
|
logger.info(f"Created new category '{category}' with ID {category_id}") |
|
|
|
|
else: |
|
|
|
|
logger.info(f"Found existing category '{category}' with ID {category_id}") |
|
|
|
|
|
|
|
|
|
# Handle tags |
|
|
|
|
tags = [1] # Default tag ID (e.g., 'uncategorized') |
|
|
|
|
if interest_score >= 9: |
|
|
|
|
picks_tag_id = get_wp_tag_id("Picks", wp_base_url, wp_username, wp_password) |
|
|
|
|
if picks_tag_id and picks_tag_id not in tags: |
|
|
|
|
tags.append(picks_tag_id) |
|
|
|
|
logger.info(f"Added 'Picks' tag (ID: {picks_tag_id}) due to high interest score: {interest_score}") |
|
|
|
|
|
|
|
|
|
# Format content with <p> tags |
|
|
|
|
content = post_data["content"] |
|
|
|
|
if content is None: |
|
|
|
|
logger.error(f"Post content is None for title '{post_data['title']}' - using fallback") |
|
|
|
|
content = "Content unavailable. Check the original source for details." |
|
|
|
|
formatted_content = "\n".join(f"<p>{para}</p>" for para in content.split('\n') if para.strip()) |
|
|
|
|
|
|
|
|
|
# Upload image before posting |
|
|
|
|
image_id = None |
|
|
|
|
if image_url: |
|
|
|
|
logger.info(f"Attempting image upload for '{post_data['title']}', URL: {image_url}, source: {image_source}") |
|
|
|
|
image_id = upload_image_to_wp(image_url, post_data["title"], wp_base_url, wp_username, wp_password, image_source, uploader, page_url) |
|
|
|
|
if not image_id: |
|
|
|
|
logger.info(f"Flickr upload failed for '{post_data['title']}', falling back to Pixabay") |
|
|
|
|
pixabay_query = post_data["title"][:50] |
|
|
|
|
image_url, image_source, uploader, page_url = get_image(pixabay_query) |
|
|
|
|
if image_url: |
|
|
|
|
wp_post["content"] += f"\n<p>Image Source: {image_source}</p>" |
|
|
|
|
|
|
|
|
|
# Simulate WordPress posting (replace with your actual WordPress API call) |
|
|
|
|
# This is a placeholder; your actual implementation likely uses a WordPress API client |
|
|
|
|
if post_id: # Update existing post |
|
|
|
|
# Replace with actual WordPress API update call |
|
|
|
|
logging.info(f"Updated WordPress post: {post_id}") |
|
|
|
|
post_url = page_url if page_url else link |
|
|
|
|
else: # Create new post |
|
|
|
|
# Replace with actual WordPress API create call |
|
|
|
|
post_id = 123 # Simulated post ID (replace with actual ID from API response) |
|
|
|
|
post_url = f"https://example.com/post/{post_id}" # Simulated URL (replace with actual URL) |
|
|
|
|
logging.info(f"Created WordPress post: {post_id}") |
|
|
|
|
image_id = upload_image_to_wp(image_url, post_data["title"], wp_base_url, wp_username, wp_password, image_source, uploader, page_url) |
|
|
|
|
if not image_id: |
|
|
|
|
logger.warning(f"All image uploads failed for '{post_data['title']}' - posting without image") |
|
|
|
|
|
|
|
|
|
# Build payload |
|
|
|
|
payload = { |
|
|
|
|
"title": post_data["title"], |
|
|
|
|
"content": formatted_content, |
|
|
|
|
"status": post_data["status"], |
|
|
|
|
"categories": [category_id], |
|
|
|
|
"tags": tags, |
|
|
|
|
"author": author_id, |
|
|
|
|
"meta": { |
|
|
|
|
"original_link": link, |
|
|
|
|
"original_source": original_source, |
|
|
|
|
"interest_score": interest_score |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if image_id: |
|
|
|
|
payload["featured_media"] = image_id |
|
|
|
|
logger.info(f"Set featured image for post '{post_data['title']}': Media ID={image_id}") |
|
|
|
|
|
|
|
|
|
# Set endpoint for creating or updating post |
|
|
|
|
endpoint = f"{wp_base_url}/posts/{post_id}" if post_id else f"{wp_base_url}/posts" |
|
|
|
|
|
|
|
|
|
logger.debug(f"Sending POST to {endpoint} with payload: {json.dumps(payload, indent=2)}") |
|
|
|
|
response = requests.post(endpoint, headers=headers, json=payload) |
|
|
|
|
if response.status_code != 201 and response.status_code != 200: |
|
|
|
|
logger.error(f"WordPress API error: {response.status_code} - {response.text}") |
|
|
|
|
response.raise_for_status() |
|
|
|
|
|
|
|
|
|
post_info = response.json() |
|
|
|
|
if not isinstance(post_info, dict) or "id" not in post_info: |
|
|
|
|
raise ValueError(f"Invalid WP response: {post_info}") |
|
|
|
|
|
|
|
|
|
post_id = post_info["id"] |
|
|
|
|
post_url = post_info["link"] |
|
|
|
|
logger.info(f"{'Updated' if post_id else 'Posted'} WordPress post: {post_data['title']} (ID: {post_id})") |
|
|
|
|
|
|
|
|
|
# Save to recent posts |
|
|
|
|
timestamp = datetime.now(timezone.utc).isoformat() |
|
|
|
|
save_post_to_recent(post_data["title"], post_url, wp_username, timestamp) |
|
|
|
|
|
|
|
|
|
# Post tweet if enabled |
|
|
|
|
if should_post_tweet: |
|
|
|
|
credentials = X_API_CREDENTIALS.get(author["username"]) |
|
|
|
|
credentials = X_API_CREDENTIALS.get(post_data["author"]) |
|
|
|
|
if credentials: |
|
|
|
|
# Select persona for the tweet |
|
|
|
|
# Select persona for the tweet (same logic as used in summarize_with_gpt4o) |
|
|
|
|
persona = select_best_persona(interest_score, post_data["content"]) |
|
|
|
|
logging.info(f"Selected persona for tweet: {persona}") |
|
|
|
|
logger.info(f"Selected persona for tweet: {persona}") |
|
|
|
|
# Generate GPT-based tweet |
|
|
|
|
tweet_post = { |
|
|
|
|
"title": post_data["title"], |
|
|
|
|
"url": post_url |
|
|
|
|
} |
|
|
|
|
# Pass the summary to generate_article_tweet |
|
|
|
|
tweet_text = generate_article_tweet(author, tweet_post, persona, summary=summary if summary else post_data["content"]) |
|
|
|
|
# Use the provided summary if available, otherwise fall back to post_data["content"] |
|
|
|
|
tweet_summary = summary if summary is not None else post_data["content"] |
|
|
|
|
tweet_text = generate_article_tweet(author, tweet_post, persona, summary=tweet_summary) |
|
|
|
|
tweet_id, tweet_data = post_tweet(author, tweet_text, tweet_type="rss") |
|
|
|
|
if tweet_id: |
|
|
|
|
logging.info(f"Successfully tweeted for post: {post_data['title']} (Tweet ID: {tweet_id})") |
|
|
|
|
logger.info(f"Successfully tweeted for post: {post_data['title']} (Tweet ID: {tweet_id})") |
|
|
|
|
else: |
|
|
|
|
logging.warning(f"Failed to tweet for post: {post_data['title']}") |
|
|
|
|
logger.warning(f"Failed to tweet for post: {post_data['title']}") |
|
|
|
|
|
|
|
|
|
return post_id, post_url |
|
|
|
|
|
|
|
|
|
except requests.exceptions.HTTPError as e: |
|
|
|
|
logger.error(f"Failed to {'update' if post_id else 'post'} WordPress post: {post_data['title']}: {e} - Response: {e.response.text}", exc_info=True) |
|
|
|
|
return None, None |
|
|
|
|
except requests.exceptions.RequestException as e: |
|
|
|
|
logger.error(f"Failed to {'update' if post_id else 'post'} WordPress post: {post_data['title']}: {e}", exc_info=True) |
|
|
|
|
return None, None |
|
|
|
|
except Exception as e: |
|
|
|
|
logging.error(f"Failed to post to WordPress for '{post_data['title']}': {str(e)}") |
|
|
|
|
raise |
|
|
|
|
logger.error(f"Failed to {'update' if post_id else 'post'} WordPress post: {post_data['title']}: {e}", exc_info=True) |
|
|
|
|
return None, None |
|
|
|
|
|
|
|
|
|
# Configure Flickr API with credentials |
|
|
|
|
flickr_api.set_keys(api_key=FLICKR_API_KEY, api_secret=FLICKR_API_SECRET) |
|
|
|
|
|