AI Backlinker, Google Ads Generator, Letter Writer - WIP
This commit is contained in:
388
lib/integrations/wix/wix_integration.py
Normal file
388
lib/integrations/wix/wix_integration.py
Normal file
@@ -0,0 +1,388 @@
|
||||
"""
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user