文件预览

content.py

查看 Shopify Manager 技能包中的文件内容。

文件内容

src/operations/content.py

"""Content operations (pages, blogs, product descriptions)."""

from typing import Dict, List, Optional
from .client import ShopifyClient
from .safety import SafetyManager, AuditLogger


class ContentOperations:
    """Content management operations."""
    
    def __init__(self, client: ShopifyClient, safety: SafetyManager, audit: AuditLogger):
        self.client = client
        self.safety = safety
        self.audit = audit
    
    # === Pages ===
    
    def list_pages(self, limit: int = 50) -> List[Dict]:
        """List store pages."""
        return self.client.get_pages(limit=limit)
    
    def get_page(self, identifier: str) -> Optional[Dict]:
        """Get page by ID or handle."""
        try:
            page_id = int(identifier)
            return self.client.get_page(page_id=page_id)
        except ValueError:
            return self.client.get_page(handle=identifier)
    
    def update_page(self, identifier: str, title: Optional[str] = None,
                   body_html: Optional[str] = None, generate_content: Optional[str] = None,
                   force: bool = False) -> Dict:
        """Update a page."""
        page = self.get_page(identifier)
        if not page:
            raise ValueError(f"Page not found: {identifier}")
        
        page_id = page['id']
        
        # Build update data
        update_data = {}
        if title:
            update_data['title'] = title
        if body_html:
            update_data['body_html'] = body_html
        
        # If generate_content specified, that would trigger AI generation
        # For now, we just store the prompt for external processing
        if generate_content:
            update_data['body_html'] = f"[AI GENERATED: {generate_content}]"
        
        if not update_data:
            return {'error': 'No updates specified'}
        
        # Validate
        is_valid, error = self.safety.validate_operation('content_update', {'id': page_id, **update_data})
        if not is_valid:
            raise ValueError(error)
        
        # Backup
        self.safety.backup_state('page', page_id, page)
        
        # Preview
        changes = [{
            'action': f"Update page: {page.get('title')}",
            'before': {k: page.get(k) for k in update_data.keys()},
            'after': update_data,
        }]
        self.safety.preview_changes("Page Update", changes)
        
        if self.safety.is_dry_run():
            return {'dry_run': True, 'page_id': page_id, 'changes': update_data}
        
        # Update
        updated = self.client.update_page(page_id, update_data)
        
        # Audit log
        self.audit.log_change('page', page_id, 'update', page, updated)
        
        return updated
    
    # === Product Descriptions ===
    
    def update_product_description(self, product_identifier: str, 
                                   description: Optional[str] = None,
                                   generate: bool = False,
                                   force: bool = False) -> Dict:
        """Update product description."""
        from .products import ProductOperations
        
        products = ProductOperations(self.client, self.safety, self.audit)
        product = products.get_product(product_identifier)
        
        if not product:
            raise ValueError(f"Product not found: {product_identifier}")
        
        product_id = product['id']
        
        if generate:
            # Placeholder for AI generation
            description = f"[AI GENERATED DESCRIPTION for {product.get('title')}]"
        
        if not description:
            return {'error': 'No description provided'}
        
        # Preview
        changes = [{
            'action': f"Update description for: {product.get('title')}",
            'before': product.get('body_html', '')[:100] + "..." if len(product.get('body_html', '')) > 100 else product.get('body_html', ''),
            'after': description[:100] + "..." if len(description) > 100 else description,
        }]
        self.safety.preview_changes("Product Description Update", changes)
        
        if self.safety.is_dry_run():
            return {'dry_run': True, 'product_id': product_id, 'new_description': description}
        
        # Update
        updated = self.client.update_product(product_id, {'body_html': description})
        
        # Audit log
        self.audit.log_change('product_description', product_id, 'update', 
                             {'body_html': product.get('body_html')}, 
                             {'body_html': description})
        
        return updated
    
    def format_page_list(self, pages: List[Dict]) -> str:
        """Format page list for display."""
        if not pages:
            return "No pages found."
        
        lines = [f"{'ID':<12} {'Handle':<25} {'Title':<40}"]
        lines.append("-" * 77)
        
        for p in pages:
            title = p.get('title', '')[:38]
            handle = p.get('handle', '')[:23]
            lines.append(f"{p.get('id', 'N/A'):<12} {handle:<25} {title:<40}")
        
        return "\n".join(lines)