import os import requests from requests.auth import HTTPBasicAuth import logger as flog class WordPressClient: def __init__(self): self.base_url = os.environ.get('WP_URL', 'http://wordpress').rstrip('/') self.api_url = f"{self.base_url}/wp-json/wp/v2" self.username = os.environ.get('WP_USERNAME', 'admin') self.app_password = os.environ.get('WP_APP_PASSWORD', '') self.auth = HTTPBasicAuth(self.username, self.app_password) def _get(self, endpoint, params=None): r = requests.get(f"{self.api_url}/{endpoint}", auth=self.auth, params=params or {}, timeout=15) r.raise_for_status() return r.json() def _post(self, endpoint, data): r = requests.post(f"{self.api_url}/{endpoint}", auth=self.auth, json=data, timeout=30) r.raise_for_status() return r.json() def _put(self, endpoint, data): r = requests.put(f"{self.api_url}/{endpoint}", auth=self.auth, json=data, timeout=30) r.raise_for_status() return r.json() def is_reachable(self) -> bool: try: requests.get(f"{self.base_url}/wp-json/", timeout=5) return True except Exception: return False def get_categories(self) -> list: try: return self._get('categories', {'per_page': 100, 'hide_empty': False}) except Exception as e: flog.error('wp_get_categories_failed', error=str(e)) return [] def get_tags(self) -> list: try: return self._get('tags', {'per_page': 100}) except Exception as e: flog.error('wp_get_tags_failed', error=str(e)) return [] def upload_media(self, image_url: str, filename: str = 'featured.jpg') -> int | None: """Download image from URL and upload to WordPress Media Library.""" try: img_response = requests.get(image_url, timeout=15) img_response.raise_for_status() content_type = img_response.headers.get('Content-Type', 'image/jpeg') r = requests.post( f"{self.api_url}/media", auth=self.auth, headers={ 'Content-Disposition': f'attachment; filename="{filename}"', 'Content-Type': content_type, }, data=img_response.content, timeout=30 ) r.raise_for_status() media_id = r.json()['id'] flog.info('wp_media_uploaded', media_id=media_id, url=image_url) return media_id except Exception as e: flog.error('wp_media_upload_failed', error=str(e), url=image_url) return None def create_post(self, title: str, content: str, status: str = 'publish', scheduled_at: str = None, category_ids: list = None, tag_ids: list = None, featured_media_id: int = None, seo_title: str = None, seo_description: str = None, focus_keyword: str = None) -> dict: """ Create a WordPress post. status: 'publish' | 'draft' | 'future' (requires scheduled_at) Returns: {'id': int, 'url': str} """ data = { 'title': title, 'content': content, 'status': status, } if scheduled_at and status == 'future': data['date'] = scheduled_at if category_ids: data['categories'] = category_ids if tag_ids: data['tags'] = tag_ids if featured_media_id: data['featured_media'] = featured_media_id # Yoast SEO meta fields if any([seo_title, seo_description, focus_keyword]): data['meta'] = {} if seo_title: data['meta']['_yoast_wpseo_title'] = seo_title if seo_description: data['meta']['_yoast_wpseo_metadesc'] = seo_description if focus_keyword: data['meta']['_yoast_wpseo_focuskw'] = focus_keyword result = self._post('posts', data) return {'id': result['id'], 'url': result['link']} def update_post(self, wp_post_id: int, **kwargs) -> dict: result = self._put(f'posts/{wp_post_id}', kwargs) return {'id': result['id'], 'url': result['link']} def get_post(self, wp_post_id: int) -> dict: return self._get(f'posts/{wp_post_id}')