388 lines
9.2 KiB
Python
388 lines
9.2 KiB
Python
"""
|
|
Wix Integration for Alwrity
|
|
|
|
This module provides a high-level interface for integrating Wix blog functionality
|
|
with the Alwrity AI Writer platform.
|
|
"""
|
|
|
|
import os
|
|
import logging
|
|
import json
|
|
from typing import Dict, List, Optional, Union, Any, Tuple
|
|
from pathlib import Path
|
|
|
|
from .wix_api_client import WixAPIClient
|
|
from .wix_blog_manager import WixBlogManager
|
|
from .wix_seo_optimizer import WixSEOOptimizer
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
)
|
|
logger = logging.getLogger('wix_integration')
|
|
|
|
class WixIntegration:
|
|
"""
|
|
Main integration class for Wix blog functionality.
|
|
|
|
This class provides a simplified interface for common operations,
|
|
combining the functionality of the API client, blog manager, and SEO optimizer.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
api_key: Optional[str] = None,
|
|
refresh_token: Optional[str] = None,
|
|
site_id: Optional[str] = None
|
|
):
|
|
"""
|
|
Initialize the Wix Integration.
|
|
|
|
Args:
|
|
api_key: Wix API key (optional if using refresh token)
|
|
refresh_token: Wix refresh token for OAuth authentication
|
|
site_id: Wix site ID
|
|
"""
|
|
self.api_client = WixAPIClient(api_key, refresh_token, site_id)
|
|
self.blog_manager = WixBlogManager(api_key, refresh_token, site_id)
|
|
self.seo_optimizer = WixSEOOptimizer(api_key, refresh_token, site_id)
|
|
|
|
def publish_blog_post(
|
|
self,
|
|
title: str,
|
|
content: str,
|
|
is_markdown: bool = True,
|
|
featured_image_path: Optional[str] = None,
|
|
featured_image_url: Optional[str] = None,
|
|
excerpt: Optional[str] = None,
|
|
tags: Optional[List[str]] = None,
|
|
categories: Optional[List[str]] = None,
|
|
seo_title: Optional[str] = None,
|
|
seo_description: Optional[str] = None,
|
|
seo_keywords: Optional[List[str]] = None,
|
|
author_name: Optional[str] = None,
|
|
publisher_name: Optional[str] = None,
|
|
publisher_logo_url: Optional[str] = None,
|
|
publish: bool = True,
|
|
update_if_exists: bool = True
|
|
) -> Dict:
|
|
"""
|
|
Publish a blog post with comprehensive SEO optimization.
|
|
|
|
Args:
|
|
title: Post title
|
|
content: Post content (markdown or HTML)
|
|
is_markdown: Whether the content is in markdown format
|
|
featured_image_path: Local path to featured image (optional)
|
|
featured_image_url: URL of featured image to download (optional)
|
|
excerpt: Post excerpt/summary (optional)
|
|
tags: List of tags (optional)
|
|
categories: List of category names (optional)
|
|
seo_title: SEO title (optional)
|
|
seo_description: SEO description (optional)
|
|
seo_keywords: SEO keywords (optional)
|
|
author_name: Name of the author (optional)
|
|
publisher_name: Name of the publisher (optional)
|
|
publisher_logo_url: URL of the publisher's logo (optional)
|
|
publish: Whether to publish the post immediately (optional)
|
|
update_if_exists: Whether to update an existing post with the same title (optional)
|
|
|
|
Returns:
|
|
Published blog post data
|
|
"""
|
|
# Generate SEO data if not provided
|
|
if not seo_keywords and tags:
|
|
seo_keywords = tags
|
|
|
|
if not seo_title:
|
|
seo_title = title
|
|
|
|
if not seo_description and not excerpt:
|
|
if is_markdown:
|
|
# Generate description from markdown content
|
|
seo_description = self.blog_manager._generate_excerpt(content)
|
|
else:
|
|
# Generate description from HTML content
|
|
seo_description = self.seo_optimizer.generate_meta_description(content)
|
|
elif not seo_description:
|
|
seo_description = excerpt
|
|
|
|
# Publish or update the post
|
|
if is_markdown:
|
|
response = self.blog_manager.publish_or_update_markdown_post(
|
|
title=title,
|
|
markdown_content=content,
|
|
featured_image_path=featured_image_path,
|
|
featured_image_url=featured_image_url,
|
|
excerpt=excerpt,
|
|
tags=tags,
|
|
categories=categories,
|
|
seo_title=seo_title,
|
|
seo_description=seo_description,
|
|
seo_keywords=seo_keywords,
|
|
publish=publish,
|
|
update_if_exists=update_if_exists
|
|
)
|
|
else:
|
|
# Find existing post or create new one
|
|
existing_post = self.blog_manager.find_post_by_title(title)
|
|
|
|
if existing_post and update_if_exists:
|
|
# Update existing post
|
|
response = self.api_client.update_post(
|
|
post_id=existing_post["id"],
|
|
title=title,
|
|
content=content,
|
|
excerpt=excerpt,
|
|
tags=tags,
|
|
categories=[self.api_client.get_or_create_category(cat) for cat in categories] if categories else None,
|
|
seo_data={
|
|
"title": seo_title,
|
|
"description": seo_description,
|
|
"keywords": seo_keywords or []
|
|
},
|
|
publish=publish
|
|
)
|
|
else:
|
|
# Create new post
|
|
response = self.api_client.create_post(
|
|
title=title,
|
|
content=content,
|
|
excerpt=excerpt,
|
|
tags=tags,
|
|
categories=[self.api_client.get_or_create_category(cat) for cat in categories] if categories else None,
|
|
seo_data={
|
|
"title": seo_title,
|
|
"description": seo_description,
|
|
"keywords": seo_keywords or []
|
|
},
|
|
publish=publish
|
|
)
|
|
|
|
# Apply additional SEO optimization if the post was published
|
|
if publish and response.get("post", {}).get("id"):
|
|
post_id = response["post"]["id"]
|
|
|
|
# Apply structured data if author and publisher info is provided
|
|
if author_name and publisher_name and publisher_logo_url:
|
|
try:
|
|
self.seo_optimizer.apply_structured_data_to_post(
|
|
post_id=post_id,
|
|
author_name=author_name,
|
|
publisher_name=publisher_name,
|
|
publisher_logo_url=publisher_logo_url
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Failed to apply structured data: {str(e)}")
|
|
|
|
# Apply comprehensive SEO optimization
|
|
try:
|
|
self.seo_optimizer.apply_seo_optimization(
|
|
post_id=post_id,
|
|
title=seo_title,
|
|
description=seo_description,
|
|
keywords=seo_keywords,
|
|
author_name=author_name,
|
|
publisher_name=publisher_name,
|
|
publisher_logo_url=publisher_logo_url,
|
|
og_image_url=featured_image_url
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Failed to apply SEO optimization: {str(e)}")
|
|
|
|
return response
|
|
|
|
def upload_media(
|
|
self,
|
|
file_path: str,
|
|
title: Optional[str] = None,
|
|
alt_text: Optional[str] = None,
|
|
description: Optional[str] = None
|
|
) -> Dict:
|
|
"""
|
|
Upload a media file to Wix.
|
|
|
|
Args:
|
|
file_path: Path to the media file
|
|
title: Media title (optional)
|
|
alt_text: Media alt text (optional)
|
|
description: Media description (optional)
|
|
|
|
Returns:
|
|
Uploaded media data
|
|
"""
|
|
return self.api_client.upload_image(
|
|
file_path=file_path,
|
|
title=title,
|
|
alt_text=alt_text,
|
|
description=description
|
|
)
|
|
|
|
def get_seo_report(self, post_id: str, target_keywords: List[str]) -> Dict:
|
|
"""
|
|
Generate a comprehensive SEO report for a blog post.
|
|
|
|
Args:
|
|
post_id: ID of the blog post
|
|
target_keywords: List of target keywords
|
|
|
|
Returns:
|
|
Dictionary with SEO report data
|
|
"""
|
|
return self.seo_optimizer.generate_seo_report(post_id, target_keywords)
|
|
|
|
def list_blog_posts(
|
|
self,
|
|
limit: int = 50,
|
|
offset: int = 0,
|
|
sort_field: str = "lastPublishedDate",
|
|
sort_order: str = "desc"
|
|
) -> Dict:
|
|
"""
|
|
List blog posts with pagination and sorting.
|
|
|
|
Args:
|
|
limit: Maximum number of posts to return (default: 50)
|
|
offset: Pagination offset (default: 0)
|
|
sort_field: Field to sort by (default: lastPublishedDate)
|
|
sort_order: Sort order, 'asc' or 'desc' (default: desc)
|
|
|
|
Returns:
|
|
Dictionary containing blog posts and pagination info
|
|
"""
|
|
return self.api_client.list_posts(
|
|
limit=limit,
|
|
offset=offset,
|
|
sort_field=sort_field,
|
|
sort_order=sort_order
|
|
)
|
|
|
|
def list_categories(self) -> Dict:
|
|
"""
|
|
List all blog categories.
|
|
|
|
Returns:
|
|
Dictionary containing blog categories
|
|
"""
|
|
return self.api_client.list_categories()
|
|
|
|
def create_category(self, name: str, description: Optional[str] = None) -> str:
|
|
"""
|
|
Create a new blog category.
|
|
|
|
Args:
|
|
name: Category name
|
|
description: Category description (optional)
|
|
|
|
Returns:
|
|
ID of the created category
|
|
"""
|
|
response = self.api_client.create_category(
|
|
label=name,
|
|
description=description
|
|
)
|
|
return response.get("category", {}).get("id", "")
|
|
|
|
def get_post_by_id(self, post_id: str) -> Dict:
|
|
"""
|
|
Get a blog post by ID.
|
|
|
|
Args:
|
|
post_id: ID of the blog post
|
|
|
|
Returns:
|
|
Blog post data
|
|
"""
|
|
return self.api_client.get_post(post_id)
|
|
|
|
def get_post_by_title(self, title: str) -> Optional[Dict]:
|
|
"""
|
|
Get a blog post by title.
|
|
|
|
Args:
|
|
title: Title of the blog post
|
|
|
|
Returns:
|
|
Blog post data or None if not found
|
|
"""
|
|
return self.blog_manager.find_post_by_title(title)
|
|
|
|
def delete_post(self, post_id: str) -> Dict:
|
|
"""
|
|
Delete a blog post.
|
|
|
|
Args:
|
|
post_id: ID of the blog post
|
|
|
|
Returns:
|
|
Response data
|
|
"""
|
|
return self.api_client.delete_post(post_id)
|
|
|
|
def update_post_status(self, post_id: str, publish: bool = True) -> Dict:
|
|
"""
|
|
Update the publication status of a blog post.
|
|
|
|
Args:
|
|
post_id: ID of the blog post
|
|
publish: Whether to publish (True) or unpublish (False) the post
|
|
|
|
Returns:
|
|
Updated blog post data
|
|
"""
|
|
if publish:
|
|
return self.api_client.publish_post(post_id)
|
|
else:
|
|
return self.api_client.unpublish_post(post_id)
|
|
|
|
def search_posts(self, query: str, limit: int = 10) -> List[Dict]:
|
|
"""
|
|
Search for blog posts by content or title.
|
|
|
|
Args:
|
|
query: Search query
|
|
limit: Maximum number of results to return
|
|
|
|
Returns:
|
|
List of matching blog posts
|
|
"""
|
|
# First try to find by title
|
|
title_matches = []
|
|
try:
|
|
all_posts = self.list_blog_posts(limit=100)["posts"]
|
|
for post in all_posts:
|
|
if query.lower() in post.get("title", "").lower():
|
|
title_matches.append(post)
|
|
if len(title_matches) >= limit:
|
|
break
|
|
except Exception as e:
|
|
logger.error(f"Error searching posts by title: {str(e)}")
|
|
|
|
return title_matches[:limit]
|
|
|
|
def get_site_info(self) -> Dict:
|
|
"""
|
|
Get information about the Wix site.
|
|
|
|
Returns:
|
|
Dictionary with site information
|
|
"""
|
|
try:
|
|
# Make a simple API call to verify credentials and get site info
|
|
posts = self.list_blog_posts(limit=1)
|
|
categories = self.list_categories()
|
|
|
|
return {
|
|
"site_id": self.api_client.site_id,
|
|
"post_count": posts.get("totalCount", 0),
|
|
"category_count": len(categories.get("categories", [])),
|
|
"status": "connected"
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Error getting site info: {str(e)}")
|
|
return {
|
|
"site_id": self.api_client.site_id,
|
|
"status": "error",
|
|
"error": str(e)
|
|
} |