From 66ea25002a1604e4a49623f97fcf84ea2224c96f Mon Sep 17 00:00:00 2001 From: Kevin Bataille Date: Tue, 17 Feb 2026 00:10:20 +0100 Subject: [PATCH] Add author filter to update_meta command - Add --author-filter option to filter posts by author name - Resolve author names to IDs via WordPress API - Support partial matching for author names - Works with other filters (category, limit, post-ids) - Fix argparse conflict with existing --author flag Co-authored-by: Qwen-Coder --- src/seo/app.py | 3 + src/seo/cli.py | 8 +++ src/seo/meta_description_updater.py | 85 +++++++++++++++++++++++++++-- 3 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/seo/app.py b/src/seo/app.py index cb5778c..8042df9 100644 --- a/src/seo/app.py +++ b/src/seo/app.py @@ -324,6 +324,7 @@ class SEOApp: post_ids: Optional[List[int]] = None, category_names: Optional[List[str]] = None, category_ids: Optional[List[int]] = None, + author_names: Optional[List[str]] = None, limit: Optional[int] = None, dry_run: bool = False, skip_existing: bool = True, @@ -336,6 +337,7 @@ class SEOApp: post_ids: Specific post IDs to update category_names: Filter by category names category_ids: Filter by category IDs + author_names: Filter by author names limit: Maximum number of posts to process dry_run: If True, preview changes without updating skip_existing: If True, skip posts with existing good quality meta descriptions @@ -354,6 +356,7 @@ class SEOApp: post_ids=post_ids, category_ids=category_ids, category_names=category_names, + author_names=author_names, limit=limit, dry_run=dry_run, skip_existing=skip_existing, diff --git a/src/seo/cli.py b/src/seo/cli.py index bea7246..588ca57 100644 --- a/src/seo/cli.py +++ b/src/seo/cli.py @@ -78,6 +78,7 @@ Examples: parser.add_argument('--post-ids', type=int, nargs='+', help='Specific post IDs to update') parser.add_argument('--category', nargs='+', help='Filter by category name(s)') parser.add_argument('--category-id', type=int, nargs='+', help='Filter by category ID(s)') + parser.add_argument('--author-filter', nargs='+', dest='author_filter', help='Filter by author name(s)') parser.add_argument('--force', action='store_true', help='Force regenerate even for good quality meta descriptions') args = parser.parse_args() @@ -446,6 +447,8 @@ def cmd_update_meta(app, args): print(f" Post IDs: {args.post_ids}") if args.category: print(f" Categories: {args.category}") + if args.author_filter: + print(f" Authors: {args.author_filter}") if args.limit: print(f" Limit: {args.limit} posts") return 0 @@ -460,6 +463,8 @@ def cmd_update_meta(app, args): print(f" Post IDs: {args.post_ids}") if args.category: print(f" Categories: {args.category}") + if args.author_filter: + print(f" Authors: {args.author_filter}") if args.category_id: print(f" Category IDs: {args.category_id}") if args.limit: @@ -472,6 +477,7 @@ def cmd_update_meta(app, args): post_ids=args.post_ids, category_names=args.category, category_ids=args.category_id, + author_names=args.author_filter, limit=args.limit, dry_run=args.dry_run, skip_existing=not args.force, @@ -562,6 +568,7 @@ Update Meta Options: --post-ids Specific post IDs to update --category Filter by category name(s) --category-id Filter by category ID(s) + --author-filter Filter by author name(s) --force Force regenerate even for good quality meta descriptions Migration Options: @@ -609,6 +616,7 @@ Examples: seo update_meta --site mistergeek.net # Update all posts on site seo update_meta --site A --post-ids 1 2 3 # Update specific posts seo update_meta --site A --category "VPN" --limit 10 # Update 10 posts in category + seo update_meta --site A --author-filter "john" --limit 10 # Update 10 posts by author seo update_meta --site A --dry-run # Preview changes seo status """) diff --git a/src/seo/meta_description_updater.py b/src/seo/meta_description_updater.py index 4d6af99..e0a49fe 100644 --- a/src/seo/meta_description_updater.py +++ b/src/seo/meta_description_updater.py @@ -57,6 +57,7 @@ class MetaDescriptionUpdater: def fetch_posts(self, post_ids: Optional[List[int]] = None, category_ids: Optional[List[int]] = None, category_names: Optional[List[str]] = None, + author_names: Optional[List[str]] = None, limit: Optional[int] = None, status: Optional[List[str]] = None) -> List[Dict]: """ @@ -66,6 +67,7 @@ class MetaDescriptionUpdater: post_ids: Specific post IDs to fetch category_ids: Filter by category IDs category_names: Filter by category names (will be resolved to IDs) + author_names: Filter by author names limit: Maximum number of posts to fetch status: Post statuses to fetch (default: ['publish']) @@ -80,6 +82,8 @@ class MetaDescriptionUpdater: logger.info(f" Category IDs: {category_ids}") if category_names: logger.info(f" Category names: {category_names}") + if author_names: + logger.info(f" Authors: {author_names}") if limit: logger.info(f" Limit: {limit}") @@ -87,6 +91,11 @@ class MetaDescriptionUpdater: if category_names and not category_ids: category_ids = self._get_category_ids_by_names(category_names) + # Resolve author names to IDs if needed + author_ids = None + if author_names: + author_ids = self._get_author_ids_by_names(author_names) + # Build API parameters params = { 'per_page': 100, @@ -94,7 +103,7 @@ class MetaDescriptionUpdater: 'status': ','.join(status) if status else 'publish', '_embed': True } - + if post_ids: # Fetch specific posts posts = [] @@ -117,6 +126,9 @@ class MetaDescriptionUpdater: if category_ids: params['categories'] = ','.join(map(str, category_ids)) + if author_ids: + params['author'] = ','.join(map(str, author_ids)) + posts = [] while True: try: @@ -200,7 +212,65 @@ class MetaDescriptionUpdater: except Exception as e: logger.error(f"Error fetching categories: {e}") return [] - + + def _get_author_ids_by_names(self, author_names: List[str]) -> List[int]: + """ + Get author/user IDs by author names. + + Args: + author_names: List of author names + + Returns: + List of author IDs + """ + logger.info(f"Resolving author names to IDs...") + + try: + response = requests.get( + f"{self.base_url}/wp-json/wp/v2/users", + params={'per_page': 100}, + auth=self.auth, + timeout=10 + ) + response.raise_for_status() + + users = response.json() + author_map = {} + + # Build map of name/slug to ID + for user in users: + name = user.get('name', '').lower() + slug = user.get('slug', '').lower() + author_map[name] = user['id'] + author_map[slug] = user['id'] + + author_ids = [] + for name in author_names: + name_lower = name.lower() + + # Try exact match + if name_lower in author_map: + author_ids.append(author_map[name_lower]) + logger.info(f" ✓ '{name}' -> ID {author_map[name_lower]}") + else: + # Try partial match + found = False + for author_name, author_id in author_map.items(): + if name_lower in author_name or author_name in name_lower: + author_ids.append(author_id) + logger.info(f" ✓ '{name}' -> ID {author_id} (partial match: '{author_name}')") + found = True + break + + if not found: + logger.warning(f" ✗ Author '{name}' not found") + + return author_ids + + except Exception as e: + logger.error(f"Error fetching authors: {e}") + return [] + def _generate_meta_description(self, post: Dict) -> Optional[str]: """ Generate meta description for a post using AI. @@ -519,22 +589,24 @@ Return ONLY the meta description text, nothing else. No quotes, no explanations. def run(self, post_ids: Optional[List[int]] = None, category_ids: Optional[List[int]] = None, category_names: Optional[List[str]] = None, + author_names: Optional[List[str]] = None, limit: Optional[int] = None, dry_run: bool = False, skip_existing: bool = False, force_regenerate: bool = False) -> Dict: """ Run complete meta description update process. - + Args: post_ids: Specific post IDs to update category_ids: Filter by category IDs category_names: Filter by category names + author_names: Filter by author names limit: Maximum number of posts to process dry_run: If True, preview changes without updating skip_existing: If True, skip posts with existing meta descriptions force_regenerate: If True, regenerate even for good quality metas - + Returns: Statistics dict """ @@ -543,13 +615,14 @@ Return ONLY the meta description text, nothing else. No quotes, no explanations. post_ids=post_ids, category_ids=category_ids, category_names=category_names, + author_names=author_names, limit=limit ) - + if not self.posts: logger.warning("No posts found matching criteria") return self.stats - + # Update posts return self.update_posts( dry_run=dry_run,