alwrity chatbot assistant, content scheduler, and content repurposing
This commit is contained in:
283
lib/integrations/platform_adapters/README.md
Normal file
283
lib/integrations/platform_adapters/README.md
Normal file
@@ -0,0 +1,283 @@
|
||||
# Platform Adapters
|
||||
|
||||
A flexible and extensible system for managing content across different social media platforms and content management systems.
|
||||
|
||||
## Overview
|
||||
|
||||
The platform adapters system provides a unified interface for publishing, managing, and analyzing content across multiple platforms. It follows a modular architecture where each platform has its own adapter implementation while maintaining a consistent interface.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
1. **Base Platform Adapter (`base.py`)**
|
||||
- Abstract base class defining the interface for all platform adapters
|
||||
- Common functionality and error handling
|
||||
- Standardized response formatting
|
||||
|
||||
2. **Platform Manager (`manager.py`)**
|
||||
- Central manager for handling multiple platform adapters
|
||||
- Platform initialization and configuration
|
||||
- Unified content publishing and management
|
||||
|
||||
3. **Unified Platform Adapter (`unified.py`)**
|
||||
- Content adaptation across different platforms
|
||||
- Platform-specific content generation
|
||||
- Performance analytics and recommendations
|
||||
|
||||
### Current Implementations
|
||||
|
||||
#### Twitter Adapter (`twitter.py`)
|
||||
- Full implementation of Twitter API integration
|
||||
- Features:
|
||||
- Tweet publishing with media support
|
||||
- Content validation
|
||||
- Analytics and engagement metrics
|
||||
- Media upload handling
|
||||
- Rate limit management
|
||||
|
||||
#### WordPress Adapter (TBD)
|
||||
- Planned implementation of WordPress REST API integration
|
||||
- Features:
|
||||
- ⏳ Post creation and management
|
||||
- ⏳ Page management
|
||||
- ⏳ Media library integration
|
||||
- ⏳ Category and tag management
|
||||
- ⏳ Custom post type support
|
||||
- ⏳ SEO metadata management
|
||||
- ⏳ Comment moderation
|
||||
- ⏳ User management
|
||||
|
||||
#### Wix Adapter (TBD)
|
||||
- Planned implementation of Wix API integration
|
||||
- Features:
|
||||
- ⏳ Blog post management
|
||||
- ⏳ Page content management
|
||||
- ⏳ Media upload and management
|
||||
- ⏳ SEO settings
|
||||
- ⏳ Collection management
|
||||
- ⏳ Form submissions handling
|
||||
- ⏳ Site settings management
|
||||
- ⏳ Analytics integration
|
||||
|
||||
## Features
|
||||
|
||||
### Core Features
|
||||
- ✅ Multi-platform content publishing
|
||||
- ✅ Content validation and optimization
|
||||
- ✅ Analytics and performance tracking
|
||||
- ✅ Media handling
|
||||
- ✅ Error handling and logging
|
||||
- ✅ Platform-specific content adaptation
|
||||
|
||||
### Platform-Specific Features
|
||||
|
||||
#### Twitter
|
||||
- ✅ Tweet publishing
|
||||
- ✅ Media attachments
|
||||
- ✅ Analytics tracking
|
||||
- ✅ Content validation
|
||||
- ✅ Rate limit handling
|
||||
|
||||
#### Instagram (TBD)
|
||||
- ⏳ Post creation
|
||||
- ⏳ Story publishing
|
||||
- ⏳ Hashtag optimization
|
||||
- ⏳ Media handling
|
||||
|
||||
#### LinkedIn (TBD)
|
||||
- ⏳ Post creation
|
||||
- ⏳ Article publishing
|
||||
- ⏳ Professional content optimization
|
||||
- ⏳ Company page integration
|
||||
|
||||
#### Facebook (TBD)
|
||||
- ⏳ Post creation
|
||||
- ⏳ Page management
|
||||
- ⏳ Audience targeting
|
||||
- ⏳ Analytics integration
|
||||
|
||||
#### WordPress (TBD)
|
||||
- ⏳ REST API integration
|
||||
- ⏳ Content synchronization
|
||||
- ⏳ Media management
|
||||
- ⏳ SEO optimization
|
||||
- ⏳ Custom post types
|
||||
- ⏳ Plugin integration
|
||||
|
||||
#### Wix (TBD)
|
||||
- ⏳ API integration
|
||||
- ⏳ Content management
|
||||
- ⏳ Media handling
|
||||
- ⏳ SEO settings
|
||||
- ⏳ Collection management
|
||||
- ⏳ Analytics integration
|
||||
|
||||
## Configuration
|
||||
|
||||
Each platform adapter requires specific configuration parameters:
|
||||
|
||||
### Twitter Configuration
|
||||
```python
|
||||
{
|
||||
'api_key': 'your_api_key',
|
||||
'api_secret': 'your_api_secret',
|
||||
'access_token': 'your_access_token',
|
||||
'access_token_secret': 'your_access_token_secret'
|
||||
}
|
||||
```
|
||||
|
||||
### WordPress Configuration
|
||||
```python
|
||||
{
|
||||
'site_url': 'https://your-wordpress-site.com',
|
||||
'username': 'your_username',
|
||||
'application_password': 'your_application_password',
|
||||
'api_version': 'v2'
|
||||
}
|
||||
```
|
||||
|
||||
### Wix Configuration
|
||||
```python
|
||||
{
|
||||
'site_id': 'your_site_id',
|
||||
'api_key': 'your_api_key',
|
||||
'access_token': 'your_access_token'
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
```python
|
||||
from lib.integrations.platform_adapters.manager import PlatformManager
|
||||
|
||||
# Initialize platform manager
|
||||
config = {
|
||||
'platforms': {
|
||||
'twitter': {
|
||||
'api_key': 'your_api_key',
|
||||
'api_secret': 'your_api_secret',
|
||||
'access_token': 'your_access_token',
|
||||
'access_token_secret': 'your_access_token_secret'
|
||||
},
|
||||
'wordpress': {
|
||||
'site_url': 'https://your-wordpress-site.com',
|
||||
'username': 'your_username',
|
||||
'application_password': 'your_application_password'
|
||||
},
|
||||
'wix': {
|
||||
'site_id': 'your_site_id',
|
||||
'api_key': 'your_api_key',
|
||||
'access_token': 'your_access_token'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
manager = PlatformManager(config)
|
||||
|
||||
# Publish content
|
||||
content = {
|
||||
'text': 'Hello, World!',
|
||||
'media': [
|
||||
{
|
||||
'url': 'https://example.com/image.jpg',
|
||||
'type': 'image'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
result = await manager.publish_content(content, platforms=['twitter', 'wordpress', 'wix'])
|
||||
```
|
||||
|
||||
## TBD Features
|
||||
|
||||
### Platform Support
|
||||
- [ ] Instagram adapter implementation
|
||||
- [ ] LinkedIn adapter implementation
|
||||
- [ ] Facebook adapter implementation
|
||||
- [ ] YouTube adapter implementation
|
||||
- [ ] TikTok adapter implementation
|
||||
- [ ] WordPress adapter implementation
|
||||
- [ ] Wix adapter implementation
|
||||
|
||||
### Content Management
|
||||
- [ ] Bulk content publishing
|
||||
- [ ] Content scheduling
|
||||
- [ ] Content templates
|
||||
- [ ] A/B testing support
|
||||
- [ ] Content versioning
|
||||
- [ ] Cross-platform content synchronization
|
||||
- [ ] CMS-specific content optimization
|
||||
|
||||
### Analytics
|
||||
- [ ] Cross-platform analytics
|
||||
- [ ] Custom metric tracking
|
||||
- [ ] Automated reporting
|
||||
- [ ] Performance optimization suggestions
|
||||
- [ ] ROI tracking
|
||||
- [ ] CMS-specific analytics integration
|
||||
|
||||
### Media Handling
|
||||
- [ ] Advanced media optimization
|
||||
- [ ] Media library management
|
||||
- [ ] Automatic media resizing
|
||||
- [ ] Media format conversion
|
||||
- [ ] Media metadata management
|
||||
- [ ] Cross-platform media synchronization
|
||||
|
||||
### Security
|
||||
- [ ] OAuth2 implementation
|
||||
- [ ] API key rotation
|
||||
- [ ] Rate limit handling
|
||||
- [ ] Error recovery
|
||||
- [ ] Audit logging
|
||||
- [ ] CMS-specific security features
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Implement your changes
|
||||
4. Add tests
|
||||
5. Submit a pull request
|
||||
|
||||
## Testing
|
||||
|
||||
Each platform adapter should include:
|
||||
- Unit tests
|
||||
- Integration tests
|
||||
- Mock API responses
|
||||
- Error handling tests
|
||||
- Rate limit tests
|
||||
- CMS-specific test cases
|
||||
|
||||
## Error Handling
|
||||
|
||||
The system implements standardized error handling:
|
||||
- Platform-specific error mapping
|
||||
- Retry mechanisms
|
||||
- Error logging
|
||||
- User-friendly error messages
|
||||
- CMS-specific error handling
|
||||
|
||||
## Logging
|
||||
|
||||
Comprehensive logging system:
|
||||
- Platform operations
|
||||
- API calls
|
||||
- Error tracking
|
||||
- Performance metrics
|
||||
- Debug information
|
||||
- CMS-specific logging
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Python 3.11+
|
||||
- tweepy (for Twitter integration)
|
||||
- requests
|
||||
- loguru
|
||||
- typing
|
||||
- datetime
|
||||
- wordpress-xmlrpc (for WordPress integration)
|
||||
- wix-api-client (for Wix integration)
|
||||
15
lib/integrations/platform_adapters/__init__.py
Normal file
15
lib/integrations/platform_adapters/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Platform adapters for content publishing and management.
|
||||
"""
|
||||
|
||||
from .base import PlatformAdapter
|
||||
from .manager import PlatformManager
|
||||
from .twitter import TwitterAdapter
|
||||
from .unified import UnifiedPlatformAdapter
|
||||
|
||||
__all__ = [
|
||||
'PlatformAdapter',
|
||||
'PlatformManager',
|
||||
'TwitterAdapter',
|
||||
'UnifiedPlatformAdapter'
|
||||
]
|
||||
157
lib/integrations/platform_adapters/base.py
Normal file
157
lib/integrations/platform_adapters/base.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
Base platform adapter class.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Dict, Any, Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
class PlatformAdapter(ABC):
|
||||
"""Base class for platform-specific adapters."""
|
||||
|
||||
def __init__(self, config: Dict[str, Any]):
|
||||
"""Initialize platform adapter with configuration."""
|
||||
self.config = config
|
||||
self.platform_name = self.__class__.__name__.replace('Adapter', '').upper()
|
||||
|
||||
@abstractmethod
|
||||
async def publish_content(
|
||||
self,
|
||||
content: Dict[str, Any],
|
||||
schedule_time: Optional[datetime] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Publish content to the platform."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_content_status(
|
||||
self,
|
||||
content_id: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Get the status of published content."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def delete_content(
|
||||
self,
|
||||
content_id: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Delete published content."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def update_content(
|
||||
self,
|
||||
content_id: str,
|
||||
updates: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""Update published content."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_analytics(
|
||||
self,
|
||||
content_id: str,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Get analytics for published content."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def validate_content(
|
||||
self,
|
||||
content: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""Validate content before publishing."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_optimal_publish_time(
|
||||
self,
|
||||
content_type: str,
|
||||
target_audience: Optional[Dict[str, Any]] = None
|
||||
) -> datetime:
|
||||
"""Get optimal publish time for content."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_platform_limits(
|
||||
self
|
||||
) -> Dict[str, Any]:
|
||||
"""Get platform-specific limits and constraints."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_supported_content_types(
|
||||
self
|
||||
) -> List[str]:
|
||||
"""Get list of supported content types."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_platform_metrics(
|
||||
self
|
||||
) -> Dict[str, Any]:
|
||||
"""Get platform-specific metrics and statistics."""
|
||||
pass
|
||||
|
||||
def _format_error_response(
|
||||
self,
|
||||
error: Exception,
|
||||
context: Optional[Dict[str, Any]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Format error response."""
|
||||
return {
|
||||
'success': False,
|
||||
'platform': self.platform_name,
|
||||
'error': str(error),
|
||||
'error_type': error.__class__.__name__,
|
||||
'context': context or {}
|
||||
}
|
||||
|
||||
def _format_success_response(
|
||||
self,
|
||||
data: Dict[str, Any],
|
||||
context: Optional[Dict[str, Any]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Format success response."""
|
||||
return {
|
||||
'success': True,
|
||||
'platform': self.platform_name,
|
||||
'data': data,
|
||||
'context': context or {}
|
||||
}
|
||||
|
||||
def _validate_config(self) -> None:
|
||||
"""Validate platform configuration."""
|
||||
required_fields = self.get_required_config_fields()
|
||||
missing_fields = [
|
||||
field for field in required_fields
|
||||
if field not in self.config
|
||||
]
|
||||
|
||||
if missing_fields:
|
||||
raise ValueError(
|
||||
f"Missing required configuration fields: {', '.join(missing_fields)}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_required_config_fields(cls) -> List[str]:
|
||||
"""Get list of required configuration fields."""
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def get_platform_name(cls) -> str:
|
||||
"""Get platform name."""
|
||||
return cls.__name__.replace('Adapter', '').upper()
|
||||
|
||||
@classmethod
|
||||
def get_platform_description(cls) -> str:
|
||||
"""Get platform description."""
|
||||
return "Base platform adapter"
|
||||
|
||||
@classmethod
|
||||
def get_platform_version(cls) -> str:
|
||||
"""Get platform adapter version."""
|
||||
return "1.0.0"
|
||||
284
lib/integrations/platform_adapters/manager.py
Normal file
284
lib/integrations/platform_adapters/manager.py
Normal file
@@ -0,0 +1,284 @@
|
||||
"""
|
||||
Platform manager for handling multiple platform adapters.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, List, Optional, Type
|
||||
from datetime import datetime
|
||||
|
||||
from .base import PlatformAdapter
|
||||
from .twitter import TwitterAdapter
|
||||
from .wix import WixAdapter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class PlatformManager:
|
||||
"""Manages multiple platform adapters."""
|
||||
|
||||
def __init__(self, config: Dict[str, Any]):
|
||||
"""Initialize platform manager with configuration."""
|
||||
self.config = config
|
||||
self.adapters: Dict[str, PlatformAdapter] = {}
|
||||
self._initialize_adapters()
|
||||
|
||||
def _initialize_adapters(self) -> None:
|
||||
"""Initialize platform adapters based on configuration."""
|
||||
platform_configs = self.config.get('platforms', {})
|
||||
|
||||
for platform, config in platform_configs.items():
|
||||
try:
|
||||
adapter = self._create_adapter(platform, config)
|
||||
if adapter:
|
||||
self.adapters[platform] = adapter
|
||||
logger.info(f"Initialized {platform} adapter")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize {platform} adapter: {str(e)}")
|
||||
|
||||
def _create_adapter(
|
||||
self,
|
||||
platform: str,
|
||||
config: Dict[str, Any]
|
||||
) -> Optional[PlatformAdapter]:
|
||||
"""Create platform adapter instance."""
|
||||
adapter_map: Dict[str, Type[PlatformAdapter]] = {
|
||||
'TWITTER': TwitterAdapter,
|
||||
'WIX': WixAdapter,
|
||||
# Add other platform adapters here
|
||||
}
|
||||
|
||||
adapter_class = adapter_map.get(platform.upper())
|
||||
if not adapter_class:
|
||||
logger.warning(f"Unsupported platform: {platform}")
|
||||
return None
|
||||
|
||||
try:
|
||||
return adapter_class(config)
|
||||
except Exception as e:
|
||||
raise Exception(
|
||||
f"Failed to create {platform} adapter: {str(e)}"
|
||||
)
|
||||
|
||||
async def publish_content(
|
||||
self,
|
||||
content: Dict[str, Any],
|
||||
platforms: List[str],
|
||||
schedule_time: Optional[datetime] = None
|
||||
) -> Dict[str, Dict[str, Any]]:
|
||||
"""Publish content to multiple platforms."""
|
||||
results = {}
|
||||
|
||||
for platform in platforms:
|
||||
if platform not in self.adapters:
|
||||
results[platform] = {
|
||||
'success': False,
|
||||
'error': f"Platform adapter not found: {platform}"
|
||||
}
|
||||
continue
|
||||
|
||||
try:
|
||||
result = await self.adapters[platform].publish_content(
|
||||
content,
|
||||
schedule_time
|
||||
)
|
||||
results[platform] = result
|
||||
except Exception as e:
|
||||
results[platform] = {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
async def get_content_status(
|
||||
self,
|
||||
content_id: str,
|
||||
platform: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Get content status from a specific platform."""
|
||||
if platform not in self.adapters:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Platform adapter not found: {platform}"
|
||||
}
|
||||
|
||||
try:
|
||||
return await self.adapters[platform].get_content_status(content_id)
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
async def delete_content(
|
||||
self,
|
||||
content_id: str,
|
||||
platform: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Delete content from a specific platform."""
|
||||
if platform not in self.adapters:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Platform adapter not found: {platform}"
|
||||
}
|
||||
|
||||
try:
|
||||
return await self.adapters[platform].delete_content(content_id)
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
async def update_content(
|
||||
self,
|
||||
content_id: str,
|
||||
updates: Dict[str, Any],
|
||||
platform: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Update content on a specific platform."""
|
||||
if platform not in self.adapters:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Platform adapter not found: {platform}"
|
||||
}
|
||||
|
||||
try:
|
||||
return await self.adapters[platform].update_content(
|
||||
content_id,
|
||||
updates
|
||||
)
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
async def get_analytics(
|
||||
self,
|
||||
content_id: str,
|
||||
platform: str,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Get analytics from a specific platform."""
|
||||
if platform not in self.adapters:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Platform adapter not found: {platform}"
|
||||
}
|
||||
|
||||
try:
|
||||
return await self.adapters[platform].get_analytics(
|
||||
content_id,
|
||||
start_date,
|
||||
end_date
|
||||
)
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
async def validate_content(
|
||||
self,
|
||||
content: Dict[str, Any],
|
||||
platform: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Validate content for a specific platform."""
|
||||
if platform not in self.adapters:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Platform adapter not found: {platform}"
|
||||
}
|
||||
|
||||
try:
|
||||
return await self.adapters[platform].validate_content(content)
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
async def get_optimal_publish_time(
|
||||
self,
|
||||
content_type: str,
|
||||
platform: str,
|
||||
target_audience: Optional[Dict[str, Any]] = None
|
||||
) -> datetime:
|
||||
"""Get optimal publish time for a specific platform."""
|
||||
if platform not in self.adapters:
|
||||
raise Exception(f"Platform adapter not found: {platform}")
|
||||
|
||||
return await self.adapters[platform].get_optimal_publish_time(
|
||||
content_type,
|
||||
target_audience
|
||||
)
|
||||
|
||||
async def get_platform_limits(
|
||||
self,
|
||||
platform: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Get platform limits for a specific platform."""
|
||||
if platform not in self.adapters:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Platform adapter not found: {platform}"
|
||||
}
|
||||
|
||||
try:
|
||||
return await self.adapters[platform].get_platform_limits()
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
async def get_supported_content_types(
|
||||
self,
|
||||
platform: str
|
||||
) -> List[str]:
|
||||
"""Get supported content types for a specific platform."""
|
||||
if platform not in self.adapters:
|
||||
raise Exception(f"Platform adapter not found: {platform}")
|
||||
|
||||
return await self.adapters[platform].get_supported_content_types()
|
||||
|
||||
async def get_platform_metrics(
|
||||
self,
|
||||
platform: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Get platform metrics for a specific platform."""
|
||||
if platform not in self.adapters:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Platform adapter not found: {platform}"
|
||||
}
|
||||
|
||||
try:
|
||||
return await self.adapters[platform].get_platform_metrics()
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def get_available_platforms(self) -> List[str]:
|
||||
"""Get list of available platform adapters."""
|
||||
return list(self.adapters.keys())
|
||||
|
||||
def get_platform_info(self, platform: str) -> Dict[str, Any]:
|
||||
"""Get information about a specific platform."""
|
||||
if platform not in self.adapters:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Platform adapter not found: {platform}"
|
||||
}
|
||||
|
||||
adapter = self.adapters[platform]
|
||||
return {
|
||||
'success': True,
|
||||
'name': adapter.get_platform_name(),
|
||||
'description': adapter.get_platform_description(),
|
||||
'version': adapter.get_platform_version(),
|
||||
'required_config': adapter.get_required_config_fields()
|
||||
}
|
||||
303
lib/integrations/platform_adapters/twitter.py
Normal file
303
lib/integrations/platform_adapters/twitter.py
Normal file
@@ -0,0 +1,303 @@
|
||||
"""
|
||||
Twitter platform adapter implementation.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Optional, List
|
||||
from datetime import datetime
|
||||
import tweepy
|
||||
from tweepy.models import Status
|
||||
|
||||
from .base import PlatformAdapter
|
||||
|
||||
class TwitterAdapter(PlatformAdapter):
|
||||
"""Twitter platform adapter."""
|
||||
|
||||
def __init__(self, config: Dict[str, Any]):
|
||||
"""Initialize Twitter adapter with configuration."""
|
||||
super().__init__(config)
|
||||
self._validate_config()
|
||||
self._initialize_client()
|
||||
|
||||
def _initialize_client(self) -> None:
|
||||
"""Initialize Twitter API client."""
|
||||
try:
|
||||
auth = tweepy.OAuthHandler(
|
||||
self.config['api_key'],
|
||||
self.config['api_secret']
|
||||
)
|
||||
auth.set_access_token(
|
||||
self.config['access_token'],
|
||||
self.config['access_token_secret']
|
||||
)
|
||||
self.client = tweepy.API(auth)
|
||||
self.client.verify_credentials()
|
||||
except Exception as e:
|
||||
raise Exception(
|
||||
f"Failed to initialize Twitter client: {str(e)}"
|
||||
)
|
||||
|
||||
async def publish_content(
|
||||
self,
|
||||
content: Dict[str, Any],
|
||||
schedule_time: Optional[datetime] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Publish content to Twitter."""
|
||||
try:
|
||||
# Validate content
|
||||
validation = await self.validate_content(content)
|
||||
if not validation.get('success'):
|
||||
return validation
|
||||
|
||||
# Prepare tweet content
|
||||
tweet_text = content.get('text', '')
|
||||
media_ids = []
|
||||
|
||||
# Handle media attachments if present
|
||||
if 'media' in content:
|
||||
for media in content['media']:
|
||||
media_id = self._upload_media(media)
|
||||
if media_id:
|
||||
media_ids.append(media_id)
|
||||
|
||||
# Create tweet
|
||||
tweet = self.client.update_status(
|
||||
status=tweet_text,
|
||||
media_ids=media_ids if media_ids else None
|
||||
)
|
||||
|
||||
return self._format_success_response({
|
||||
'id': tweet.id_str,
|
||||
'text': tweet.text,
|
||||
'created_at': tweet.created_at.isoformat()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return self._format_error_response(
|
||||
e,
|
||||
{'content': content, 'schedule_time': schedule_time}
|
||||
)
|
||||
|
||||
async def get_content_status(
|
||||
self,
|
||||
content_id: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Get status of a tweet."""
|
||||
try:
|
||||
tweet = self.client.get_status(content_id)
|
||||
return self._format_success_response({
|
||||
'id': tweet.id_str,
|
||||
'text': tweet.text,
|
||||
'created_at': tweet.created_at.isoformat(),
|
||||
'favorite_count': tweet.favorite_count,
|
||||
'retweet_count': tweet.retweet_count
|
||||
})
|
||||
except Exception as e:
|
||||
return self._format_error_response(
|
||||
e,
|
||||
{'content_id': content_id}
|
||||
)
|
||||
|
||||
async def delete_content(
|
||||
self,
|
||||
content_id: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Delete a tweet."""
|
||||
try:
|
||||
self.client.destroy_status(content_id)
|
||||
return self._format_success_response({
|
||||
'id': content_id,
|
||||
'deleted': True
|
||||
})
|
||||
except Exception as e:
|
||||
return self._format_error_response(
|
||||
e,
|
||||
{'content_id': content_id}
|
||||
)
|
||||
|
||||
async def update_content(
|
||||
self,
|
||||
content_id: str,
|
||||
updates: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""Update a tweet."""
|
||||
try:
|
||||
# Twitter doesn't support updating tweets
|
||||
# We'll delete the old one and create a new one
|
||||
await self.delete_content(content_id)
|
||||
return await self.publish_content(updates)
|
||||
except Exception as e:
|
||||
return self._format_error_response(
|
||||
e,
|
||||
{
|
||||
'content_id': content_id,
|
||||
'updates': updates
|
||||
}
|
||||
)
|
||||
|
||||
async def get_analytics(
|
||||
self,
|
||||
content_id: str,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Get analytics for a tweet."""
|
||||
try:
|
||||
tweet = self.client.get_status(content_id)
|
||||
return self._format_success_response({
|
||||
'id': tweet.id_str,
|
||||
'metrics': {
|
||||
'favorites': tweet.favorite_count,
|
||||
'retweets': tweet.retweet_count,
|
||||
'replies': tweet.reply_count if hasattr(tweet, 'reply_count') else 0,
|
||||
'impressions': tweet.impression_count if hasattr(tweet, 'impression_count') else 0
|
||||
},
|
||||
'engagement_rate': self._calculate_engagement_rate(tweet)
|
||||
})
|
||||
except Exception as e:
|
||||
return self._format_error_response(
|
||||
e,
|
||||
{
|
||||
'content_id': content_id,
|
||||
'start_date': start_date,
|
||||
'end_date': end_date
|
||||
}
|
||||
)
|
||||
|
||||
async def validate_content(
|
||||
self,
|
||||
content: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""Validate content before publishing."""
|
||||
try:
|
||||
# Check text length
|
||||
text = content.get('text', '')
|
||||
if len(text) > 280:
|
||||
return self._format_error_response(
|
||||
ValueError("Tweet text exceeds 280 characters"),
|
||||
{'content': content}
|
||||
)
|
||||
|
||||
# Check media attachments
|
||||
media = content.get('media', [])
|
||||
if len(media) > 4:
|
||||
return self._format_error_response(
|
||||
ValueError("Maximum 4 media attachments allowed"),
|
||||
{'content': content}
|
||||
)
|
||||
|
||||
return self._format_success_response({
|
||||
'valid': True,
|
||||
'content': content
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return self._format_error_response(
|
||||
e,
|
||||
{'content': content}
|
||||
)
|
||||
|
||||
async def get_optimal_publish_time(
|
||||
self,
|
||||
content_type: str,
|
||||
target_audience: Optional[Dict[str, Any]] = None
|
||||
) -> datetime:
|
||||
"""Get optimal publish time for content."""
|
||||
# Implement optimal time calculation based on:
|
||||
# - Content type
|
||||
# - Target audience timezone
|
||||
# - Historical engagement data
|
||||
# For now, return current time
|
||||
return datetime.now()
|
||||
|
||||
async def get_platform_limits(
|
||||
self
|
||||
) -> Dict[str, Any]:
|
||||
"""Get Twitter platform limits."""
|
||||
return self._format_success_response({
|
||||
'tweet_length': 280,
|
||||
'media_attachments': 4,
|
||||
'poll_options': 4,
|
||||
'poll_duration': 10080, # 7 days in minutes
|
||||
'rate_limits': {
|
||||
'tweets_per_day': 2000,
|
||||
'tweets_per_hour': 100
|
||||
}
|
||||
})
|
||||
|
||||
async def get_supported_content_types(
|
||||
self
|
||||
) -> List[str]:
|
||||
"""Get list of supported content types."""
|
||||
return ['TWEET', 'THREAD', 'POLL']
|
||||
|
||||
async def get_platform_metrics(
|
||||
self
|
||||
) -> Dict[str, Any]:
|
||||
"""Get Twitter platform metrics."""
|
||||
try:
|
||||
account = self.client.verify_credentials()
|
||||
return self._format_success_response({
|
||||
'followers_count': account.followers_count,
|
||||
'following_count': account.friends_count,
|
||||
'tweets_count': account.statuses_count,
|
||||
'account_created_at': account.created_at.isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
return self._format_error_response(e)
|
||||
|
||||
def _calculate_engagement_rate(self, tweet: Status) -> float:
|
||||
"""Calculate engagement rate for a tweet."""
|
||||
try:
|
||||
total_engagement = (
|
||||
tweet.favorite_count +
|
||||
tweet.retweet_count +
|
||||
(tweet.reply_count if hasattr(tweet, 'reply_count') else 0)
|
||||
)
|
||||
followers = tweet.user.followers_count
|
||||
return (total_engagement / followers * 100) if followers > 0 else 0.0
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
def _upload_media(self, media: Dict[str, Any]) -> Optional[str]:
|
||||
"""Upload media to Twitter."""
|
||||
try:
|
||||
if 'url' in media:
|
||||
# Download media from URL
|
||||
response = requests.get(media['url'])
|
||||
media_file = BytesIO(response.content)
|
||||
elif 'file' in media:
|
||||
# Use local file
|
||||
media_file = open(media['file'], 'rb')
|
||||
else:
|
||||
return None
|
||||
|
||||
# Upload media
|
||||
media_upload = self.client.media_upload(
|
||||
filename=media.get('filename', 'media'),
|
||||
file=media_file
|
||||
)
|
||||
return media_upload.media_id_string
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to upload media: {str(e)}")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_required_config_fields(cls) -> List[str]:
|
||||
"""Get list of required configuration fields."""
|
||||
return [
|
||||
'api_key',
|
||||
'api_secret',
|
||||
'access_token',
|
||||
'access_token_secret'
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_platform_description(cls) -> str:
|
||||
"""Get platform description."""
|
||||
return "Twitter platform adapter for posting and managing tweets"
|
||||
|
||||
@classmethod
|
||||
def get_platform_version(cls) -> str:
|
||||
"""Get platform adapter version."""
|
||||
return "1.0.0"
|
||||
290
lib/integrations/platform_adapters/unified.py
Normal file
290
lib/integrations/platform_adapters/unified.py
Normal file
@@ -0,0 +1,290 @@
|
||||
"""
|
||||
Unified platform adapter for content adaptation across different platforms.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
|
||||
from lib.utils.website_analyzer.analyzer import WebsiteAnalyzer
|
||||
from lib.ai_seo_tools.content_gap_analysis.main import ContentGapAnalysis
|
||||
from lib.ai_seo_tools.content_title_generator import ai_title_generator
|
||||
from lib.ai_seo_tools.meta_desc_generator import metadesc_generator_main
|
||||
from lib.ai_seo_tools.seo_structured_data import ai_structured_data
|
||||
|
||||
class UnifiedPlatformAdapter:
|
||||
"""Unified adapter for different social media platforms."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the platform adapter."""
|
||||
self.platform_handlers = {
|
||||
'instagram': self._handle_instagram,
|
||||
'linkedin': self._handle_linkedin,
|
||||
'twitter': self._handle_twitter,
|
||||
'facebook': self._handle_facebook
|
||||
}
|
||||
logger.info("UnifiedPlatformAdapter initialized")
|
||||
|
||||
def generate_content(self, platform: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate content for a specific platform.
|
||||
|
||||
Args:
|
||||
platform: Target platform
|
||||
data: Content data
|
||||
|
||||
Returns:
|
||||
Dictionary containing generated content
|
||||
"""
|
||||
try:
|
||||
handler = self.platform_handlers.get(platform.lower())
|
||||
if not handler:
|
||||
raise ValueError(f"Unsupported platform: {platform}")
|
||||
|
||||
return handler(data)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error generating content for {platform}: {str(e)}"
|
||||
logger.error(error_msg, exc_info=True)
|
||||
return {
|
||||
'error': error_msg,
|
||||
'content': None
|
||||
}
|
||||
|
||||
def get_content_performance(self, content_item: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Get performance metrics for content across platforms."""
|
||||
try:
|
||||
logger.info(f"Getting performance metrics for content: {content_item.get('title', 'Untitled')}")
|
||||
|
||||
# Get platform from content item
|
||||
platform = content_item.get('platforms', ['Unknown'])[0]
|
||||
|
||||
# Initialize performance metrics
|
||||
performance = {
|
||||
'engagement_metrics': {
|
||||
'likes': 0,
|
||||
'comments': 0,
|
||||
'shares': 0,
|
||||
'reach': 0
|
||||
},
|
||||
'seo_metrics': {
|
||||
'impressions': 0,
|
||||
'clicks': 0,
|
||||
'ctr': 0,
|
||||
'position': 0
|
||||
},
|
||||
'conversion_metrics': {
|
||||
'conversions': 0,
|
||||
'conversion_rate': 0,
|
||||
'revenue': 0
|
||||
},
|
||||
'platform_specific': {},
|
||||
'performance_trends': [],
|
||||
'recommendations': []
|
||||
}
|
||||
|
||||
# Add platform-specific metrics
|
||||
if platform == 'WEBSITE':
|
||||
performance['platform_specific'] = {
|
||||
'bounce_rate': 0,
|
||||
'time_on_page': 0,
|
||||
'page_views': 0
|
||||
}
|
||||
|
||||
return performance
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error getting content performance: {str(e)}"
|
||||
logger.error(error_msg, exc_info=True)
|
||||
return {
|
||||
'error': error_msg,
|
||||
'metrics': {},
|
||||
'trends': {},
|
||||
'recommendations': []
|
||||
}
|
||||
|
||||
def _handle_instagram(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Handle Instagram content generation."""
|
||||
try:
|
||||
# Generate Instagram-specific content
|
||||
caption = metadesc_generator_main(data)
|
||||
hashtags = self._generate_hashtags(data)
|
||||
|
||||
return {
|
||||
'platform': 'instagram',
|
||||
'content': {
|
||||
'caption': caption,
|
||||
'hashtags': hashtags,
|
||||
'media_suggestions': self._get_media_suggestions(data)
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating Instagram content: {str(e)}")
|
||||
return {
|
||||
'platform': 'instagram',
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def _handle_linkedin(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Handle LinkedIn content generation."""
|
||||
try:
|
||||
# Generate LinkedIn-specific content
|
||||
post = metadesc_generator_main(data)
|
||||
|
||||
return {
|
||||
'platform': 'linkedin',
|
||||
'content': {
|
||||
'post': post,
|
||||
'engagement_optimization': self._get_engagement_suggestions(data),
|
||||
'media_suggestions': self._get_media_suggestions(data)
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating LinkedIn content: {str(e)}")
|
||||
return {
|
||||
'platform': 'linkedin',
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def _handle_twitter(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Handle Twitter content generation."""
|
||||
try:
|
||||
# Generate Twitter-specific content
|
||||
tweet = metadesc_generator_main(data)
|
||||
hashtags = self._generate_hashtags(data)
|
||||
|
||||
return {
|
||||
'platform': 'twitter',
|
||||
'content': {
|
||||
'tweet': tweet,
|
||||
'hashtags': hashtags,
|
||||
'thread_structure': self._get_thread_structure(data),
|
||||
'media_suggestions': self._get_media_suggestions(data)
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating Twitter content: {str(e)}")
|
||||
return {
|
||||
'platform': 'twitter',
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def _handle_facebook(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Handle Facebook content generation."""
|
||||
try:
|
||||
# Generate Facebook-specific content
|
||||
post = metadesc_generator_main(data)
|
||||
|
||||
return {
|
||||
'platform': 'facebook',
|
||||
'content': {
|
||||
'post': post,
|
||||
'engagement_optimization': self._get_engagement_suggestions(data),
|
||||
'media_suggestions': self._get_media_suggestions(data)
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating Facebook content: {str(e)}")
|
||||
return {
|
||||
'platform': 'facebook',
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def _generate_hashtags(self, data: Dict[str, Any]) -> List[str]:
|
||||
"""Generate relevant hashtags for content."""
|
||||
try:
|
||||
# Extract keywords from content
|
||||
keywords = data.get('keywords', [])
|
||||
|
||||
# Add platform-specific hashtags
|
||||
platform = data.get('platform', '').lower()
|
||||
platform_hashtags = {
|
||||
'instagram': ['#instagood', '#photooftheday'],
|
||||
'twitter': ['#trending', '#followme'],
|
||||
'linkedin': ['#business', '#professional'],
|
||||
'facebook': ['#social', '#community']
|
||||
}.get(platform, [])
|
||||
|
||||
return keywords + platform_hashtags
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating hashtags: {str(e)}")
|
||||
return []
|
||||
|
||||
def _get_media_suggestions(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Get media suggestions for content."""
|
||||
try:
|
||||
# Generate media suggestions based on content type
|
||||
content_type = data.get('type', 'post')
|
||||
|
||||
suggestions = []
|
||||
if content_type == 'blog':
|
||||
suggestions.append({
|
||||
'type': 'featured_image',
|
||||
'description': 'Main blog post image',
|
||||
'dimensions': '1200x630'
|
||||
})
|
||||
elif content_type == 'social':
|
||||
suggestions.append({
|
||||
'type': 'post_image',
|
||||
'description': 'Social media post image',
|
||||
'dimensions': '1080x1080'
|
||||
})
|
||||
|
||||
return suggestions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting media suggestions: {str(e)}")
|
||||
return []
|
||||
|
||||
def _get_engagement_suggestions(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Get engagement optimization suggestions."""
|
||||
try:
|
||||
return {
|
||||
'best_posting_times': ['9:00 AM', '5:00 PM'],
|
||||
'engagement_tips': [
|
||||
'Ask questions to encourage comments',
|
||||
'Use relevant hashtags',
|
||||
'Include a clear call-to-action'
|
||||
],
|
||||
'content_length': {
|
||||
'optimal': '150-200 characters',
|
||||
'maximum': '300 characters'
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting engagement suggestions: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _get_thread_structure(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Get thread structure for Twitter threads."""
|
||||
try:
|
||||
content = data.get('content', '')
|
||||
sentences = content.split('.')
|
||||
|
||||
thread = []
|
||||
current_tweet = ''
|
||||
|
||||
for sentence in sentences:
|
||||
if len(current_tweet + sentence) <= 280:
|
||||
current_tweet += sentence + '.'
|
||||
else:
|
||||
if current_tweet:
|
||||
thread.append({
|
||||
'content': current_tweet.strip(),
|
||||
'type': 'tweet'
|
||||
})
|
||||
current_tweet = sentence + '.'
|
||||
|
||||
if current_tweet:
|
||||
thread.append({
|
||||
'content': current_tweet.strip(),
|
||||
'type': 'tweet'
|
||||
})
|
||||
|
||||
return thread
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating thread structure: {str(e)}")
|
||||
return []
|
||||
327
lib/integrations/platform_adapters/wix.py
Normal file
327
lib/integrations/platform_adapters/wix.py
Normal file
@@ -0,0 +1,327 @@
|
||||
"""
|
||||
Wix platform adapter implementation.
|
||||
"""
|
||||
|
||||
from io import BytesIO
|
||||
from typing import Dict, Any, Optional, List
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
from .base import PlatformAdapter
|
||||
from lib.integrations.wix.wix_api_client import WixAPIClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class WixAdapter(PlatformAdapter):
|
||||
"""Wix platform adapter."""
|
||||
|
||||
def __init__(self, config: Dict[str, Any]):
|
||||
"""Initialize Wix adapter with configuration."""
|
||||
super().__init__(config)
|
||||
self._validate_config()
|
||||
self._initialize_client()
|
||||
|
||||
def _initialize_client(self) -> None:
|
||||
"""Initialize Wix API client."""
|
||||
try:
|
||||
self.client = WixAPIClient(
|
||||
api_key=self.config.get('api_key'),
|
||||
refresh_token=self.config.get('refresh_token'),
|
||||
site_id=self.config.get('site_id')
|
||||
)
|
||||
logger.info("Successfully initialized Wix API client")
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to initialize Wix client: {str(e)}")
|
||||
|
||||
async def publish_content(
|
||||
self,
|
||||
content: Dict[str, Any],
|
||||
schedule_time: Optional[datetime] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Publish content to Wix blog."""
|
||||
try:
|
||||
# Validate content
|
||||
validation = await self.validate_content(content)
|
||||
if not validation.get('success'):
|
||||
return validation
|
||||
|
||||
# Prepare blog post data
|
||||
post_data = {
|
||||
'title': content.get('title', ''),
|
||||
'content': content.get('content', ''),
|
||||
'excerpt': content.get('excerpt', ''),
|
||||
'slug': content.get('slug', ''),
|
||||
'tags': content.get('tags', []),
|
||||
'categories': content.get('categories', []),
|
||||
'seo': content.get('seo', {}),
|
||||
'publish_date': schedule_time.isoformat() if schedule_time else None
|
||||
}
|
||||
|
||||
# Handle media attachments
|
||||
media_ids = []
|
||||
if 'media' in content:
|
||||
for media in content['media']:
|
||||
media_id = await self._upload_media(media)
|
||||
if media_id:
|
||||
media_ids.append(media_id)
|
||||
|
||||
# Create blog post
|
||||
post = self.client.create_post(post_data)
|
||||
|
||||
# Add media to post if any
|
||||
if media_ids:
|
||||
self.client.add_media_to_post(post['id'], media_ids)
|
||||
|
||||
return self._format_success_response({
|
||||
'id': post['id'],
|
||||
'title': post['title'],
|
||||
'url': post['url'],
|
||||
'created_at': post['created_at']
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return self._format_error_response(
|
||||
e,
|
||||
{'content': content, 'schedule_time': schedule_time}
|
||||
)
|
||||
|
||||
async def get_content_status(
|
||||
self,
|
||||
content_id: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Get status of a blog post."""
|
||||
try:
|
||||
post = self.client.get_post(content_id)
|
||||
return self._format_success_response({
|
||||
'id': post['id'],
|
||||
'title': post['title'],
|
||||
'status': post['status'],
|
||||
'url': post['url'],
|
||||
'created_at': post['created_at'],
|
||||
'updated_at': post['updated_at'],
|
||||
'published_at': post.get('published_at')
|
||||
})
|
||||
except Exception as e:
|
||||
return self._format_error_response(
|
||||
e,
|
||||
{'content_id': content_id}
|
||||
)
|
||||
|
||||
async def delete_content(
|
||||
self,
|
||||
content_id: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Delete a blog post."""
|
||||
try:
|
||||
self.client.delete_post(content_id)
|
||||
return self._format_success_response({
|
||||
'id': content_id,
|
||||
'deleted': True
|
||||
})
|
||||
except Exception as e:
|
||||
return self._format_error_response(
|
||||
e,
|
||||
{'content_id': content_id}
|
||||
)
|
||||
|
||||
async def update_content(
|
||||
self,
|
||||
content_id: str,
|
||||
updates: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""Update a blog post."""
|
||||
try:
|
||||
post = self.client.update_post(content_id, updates)
|
||||
return self._format_success_response({
|
||||
'id': post['id'],
|
||||
'title': post['title'],
|
||||
'url': post['url'],
|
||||
'updated_at': post['updated_at']
|
||||
})
|
||||
except Exception as e:
|
||||
return self._format_error_response(
|
||||
e,
|
||||
{
|
||||
'content_id': content_id,
|
||||
'updates': updates
|
||||
}
|
||||
)
|
||||
|
||||
async def get_analytics(
|
||||
self,
|
||||
content_id: str,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Get analytics for a blog post."""
|
||||
try:
|
||||
analytics = self.client.get_post_analytics(
|
||||
content_id,
|
||||
start_date,
|
||||
end_date
|
||||
)
|
||||
return self._format_success_response({
|
||||
'id': content_id,
|
||||
'metrics': {
|
||||
'views': analytics.get('views', 0),
|
||||
'unique_visitors': analytics.get('unique_visitors', 0),
|
||||
'average_time_on_page': analytics.get('average_time_on_page', 0),
|
||||
'bounce_rate': analytics.get('bounce_rate', 0)
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
return self._format_error_response(
|
||||
e,
|
||||
{
|
||||
'content_id': content_id,
|
||||
'start_date': start_date,
|
||||
'end_date': end_date
|
||||
}
|
||||
)
|
||||
|
||||
async def validate_content(
|
||||
self,
|
||||
content: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""Validate content before publishing."""
|
||||
try:
|
||||
# Check required fields
|
||||
required_fields = ['title', 'content']
|
||||
missing_fields = [
|
||||
field for field in required_fields
|
||||
if field not in content
|
||||
]
|
||||
|
||||
if missing_fields:
|
||||
return self._format_error_response(
|
||||
ValueError(f"Missing required fields: {', '.join(missing_fields)}"),
|
||||
{'content': content}
|
||||
)
|
||||
|
||||
# Check content length
|
||||
if len(content['content']) > 100000: # Wix limit
|
||||
return self._format_error_response(
|
||||
ValueError("Content exceeds maximum length of 100,000 characters"),
|
||||
{'content': content}
|
||||
)
|
||||
|
||||
# Check media attachments
|
||||
media = content.get('media', [])
|
||||
if len(media) > 20: # Wix limit
|
||||
return self._format_error_response(
|
||||
ValueError("Maximum 20 media attachments allowed"),
|
||||
{'content': content}
|
||||
)
|
||||
|
||||
return self._format_success_response({
|
||||
'valid': True,
|
||||
'content': content
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return self._format_error_response(
|
||||
e,
|
||||
{'content': content}
|
||||
)
|
||||
|
||||
async def get_optimal_publish_time(
|
||||
self,
|
||||
content_type: str,
|
||||
target_audience: Optional[Dict[str, Any]] = None
|
||||
) -> datetime:
|
||||
"""Get optimal publish time for content."""
|
||||
# Implement optimal time calculation based on:
|
||||
# - Content type
|
||||
# - Target audience timezone
|
||||
# - Historical engagement data
|
||||
# For now, return current time
|
||||
return datetime.now()
|
||||
|
||||
async def get_platform_limits(
|
||||
self
|
||||
) -> Dict[str, Any]:
|
||||
"""Get Wix platform limits."""
|
||||
return self._format_success_response({
|
||||
'content_length': 100000,
|
||||
'media_attachments': 20,
|
||||
'tags_per_post': 50,
|
||||
'categories_per_post': 10,
|
||||
'rate_limits': {
|
||||
'posts_per_day': 100,
|
||||
'media_uploads_per_day': 1000
|
||||
}
|
||||
})
|
||||
|
||||
async def get_supported_content_types(
|
||||
self
|
||||
) -> List[str]:
|
||||
"""Get list of supported content types."""
|
||||
return ['BLOG_POST', 'PAGE', 'COLLECTION_ITEM']
|
||||
|
||||
async def get_platform_metrics(
|
||||
self
|
||||
) -> Dict[str, Any]:
|
||||
"""Get Wix platform metrics."""
|
||||
try:
|
||||
site_stats = self.client.get_site_statistics()
|
||||
return self._format_success_response({
|
||||
'total_posts': site_stats.get('total_posts', 0),
|
||||
'total_views': site_stats.get('total_views', 0),
|
||||
'total_comments': site_stats.get('total_comments', 0),
|
||||
'average_engagement': site_stats.get('average_engagement', 0)
|
||||
})
|
||||
except Exception as e:
|
||||
return self._format_error_response(e)
|
||||
|
||||
async def _upload_media(
|
||||
self,
|
||||
media: Dict[str, Any]
|
||||
) -> Optional[str]:
|
||||
"""Upload media to Wix."""
|
||||
try:
|
||||
if 'url' in media:
|
||||
# Download media from URL
|
||||
response = requests.get(media['url'])
|
||||
media_file = BytesIO(response.content)
|
||||
filename = media.get('filename', 'media')
|
||||
elif 'file' in media:
|
||||
# Use local file
|
||||
file_path = Path(media['file'])
|
||||
media_file = open(file_path, 'rb')
|
||||
filename = file_path.name
|
||||
else:
|
||||
return None
|
||||
|
||||
# Upload media
|
||||
media_id = self.client.upload_media(
|
||||
file=media_file,
|
||||
filename=filename,
|
||||
mime_type=media.get('mime_type')
|
||||
)
|
||||
return media_id
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to upload media: {str(e)}")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_required_config_fields(cls) -> List[str]:
|
||||
"""Get list of required configuration fields."""
|
||||
return [
|
||||
'api_key',
|
||||
'refresh_token',
|
||||
'site_id'
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_platform_description(cls) -> str:
|
||||
"""Get platform description."""
|
||||
return "Wix platform adapter for managing blog posts and content"
|
||||
|
||||
@classmethod
|
||||
def get_platform_version(cls) -> str:
|
||||
"""Get platform adapter version."""
|
||||
return "1.0.0"
|
||||
Reference in New Issue
Block a user