Skip to content

Commit 203b560

Browse files
refactor: unify draft handling in twitter workflow
- Add find_draft helper with unified error handling - Update commands to use helper (approve, post, reject, edit) - Reduce code duplication and improve consistency - Simplify error messages Co-authored-by: Bob <[email protected]>
1 parent 7aa1d69 commit 203b560

File tree

1 file changed

+67
-42
lines changed

1 file changed

+67
-42
lines changed

scripts/twitter/workflow.py

Lines changed: 67 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import json
3939
import logging
4040
import os
41+
import sys
4142
import time
4243
from dataclasses import asdict
4344
from datetime import datetime
@@ -358,7 +359,7 @@ def get_conversation_thread(client, tweet_id_or_conversation_id, max_pages=3, ma
358359
"created_at": t.created_at.isoformat(),
359360
"depth": depth, # Add depth information for UI indentation
360361
"replied_to_id": reply_structure.get(t.id), # Which tweet this is replying to
361-
"public_metrics": t.public_metrics if hasattr(t, "public_metrics") else {},
362+
"public_metrics": (t.public_metrics if hasattr(t, "public_metrics") else {}),
362363
# Include referenced tweets if available
363364
"referenced_tweets": [],
364365
}
@@ -383,7 +384,9 @@ def get_conversation_thread(client, tweet_id_or_conversation_id, max_pages=3, ma
383384
"type": ref.type,
384385
"id": ref.id,
385386
"text": ref_tweet.text if ref_tweet else "Unavailable",
386-
"author": author.username if ref_tweet and ref_tweet.author_id in all_users else "Unknown",
387+
"author": (
388+
author.username if ref_tweet and ref_tweet.author_id in all_users else "Unknown"
389+
),
387390
}
388391
)
389392

@@ -426,6 +429,51 @@ def move_draft(path: Path, new_status: str) -> Path:
426429
return new_path
427430

428431

432+
def find_draft(draft_id: str, status: Optional[str] = None, show_error: bool = True) -> Optional[Path]:
433+
"""Find a draft by ID or path.
434+
435+
Args:
436+
draft_id: Either a simple ID, filename, or full path
437+
status: Optional status to look in specific directory
438+
If None, looks in all status directories
439+
show_error: Whether to print error message if not found
440+
441+
Returns:
442+
Path to draft if found, None otherwise
443+
"""
444+
status_dirs = {
445+
"new": NEW_DIR,
446+
"review": REVIEW_DIR,
447+
"approved": APPROVED_DIR,
448+
"posted": POSTED_DIR,
449+
"rejected": REJECTED_DIR,
450+
}
451+
452+
# Handle full paths
453+
if "/" in draft_id:
454+
draft_path = Path(draft_id)
455+
if not draft_path.exists() and not draft_path.suffix:
456+
draft_path = draft_path.with_suffix(".yml")
457+
if draft_path.exists():
458+
return draft_path
459+
else:
460+
# Search in specified directories
461+
search_dirs = [status_dirs[status]] if status else status_dirs.values()
462+
for dir in search_dirs:
463+
for path in [dir / draft_id, (dir / draft_id).with_suffix(".yml")] + list(dir.glob(f"*{draft_id}*.yml")):
464+
if path.exists():
465+
return path
466+
467+
if show_error:
468+
status_msg = f" in {status} directory" if status else ""
469+
console.print(f"[red]No draft found{status_msg}: {draft_id}")
470+
console.print(
471+
"[yellow]ID can be: simple ID, filename, or full path (e.g., tweet_20250419, reply_*.yml, tweets/new/*.yml)"
472+
)
473+
474+
return None
475+
476+
429477
def list_drafts(status: str) -> List[Path]:
430478
"""List all drafts in a status directory"""
431479
status_dirs = {
@@ -448,7 +496,7 @@ def list_drafts(status: str) -> List[Path]:
448496
default=os.getenv("MODEL", "anthropic/claude-3-5-sonnet-20241022"),
449497
help="Model to use for LLM operations",
450498
)
451-
def cli(model: str):
499+
def cli(model: str | None = None) -> None:
452500
"""Twitter Workflow Manager"""
453501
init_gptme(model=model, interactive=False, tool_allowlist=[])
454502

@@ -561,15 +609,11 @@ def review(auto_approve: bool, show_context: bool, dry_run: bool) -> None:
561609
@cli.command()
562610
@click.argument("draft_id")
563611
def approve(draft_id: str) -> None:
564-
"""Approve a draft tweet by ID"""
565-
# Find the draft by ID
566-
draft_files = list(NEW_DIR.glob(f"{draft_id}*.yml"))
567-
568-
if not draft_files:
569-
console.print(f"[red]No draft found with ID: {draft_id}")
612+
"""Approve a draft tweet by ID or path"""
613+
draft_path = find_draft(draft_id, "new")
614+
if not draft_path:
570615
return
571616

572-
draft_path = draft_files[0]
573617
new_path = move_draft(draft_path, "approved")
574618
console.print(f"[green]Draft approved: {draft_path.name}{new_path.name}")
575619

@@ -578,19 +622,11 @@ def approve(draft_id: str) -> None:
578622
@click.argument("draft_id")
579623
def reject(draft_id: str) -> None:
580624
"""Reject a draft tweet by ID (works on both new and approved drafts)"""
581-
# First try to find the draft in the NEW_DIR
582-
draft_files = list(NEW_DIR.glob(f"{draft_id}*.yml"))
583-
584-
# If not found, try APPROVED_DIR
585-
if not draft_files:
586-
draft_files = list(APPROVED_DIR.glob(f"{draft_id}*.yml"))
587-
588-
# Still not found
589-
if not draft_files:
590-
console.print(f"[red]No draft found with ID: {draft_id} in new or approved directories")
625+
# Try to find draft in either new or approved directories
626+
draft_path = find_draft(draft_id, "new", show_error=False) or find_draft(draft_id, "approved")
627+
if not draft_path:
591628
return
592629

593-
draft_path = draft_files[0]
594630
new_path = move_draft(draft_path, "rejected")
595631
console.print(f"[red]Draft rejected: {draft_path.name}{new_path.name}")
596632

@@ -600,21 +636,12 @@ def reject(draft_id: str) -> None:
600636
@click.argument("new_text")
601637
def edit(draft_id: str, new_text: str) -> None:
602638
"""Edit a draft tweet by ID (works on both new and approved drafts)"""
603-
# First try to find the draft in the NEW_DIR
604-
draft_files = list(NEW_DIR.glob(f"{draft_id}*.yml"))
605-
606-
# If not found, try APPROVED_DIR
607-
if not draft_files:
608-
draft_files = list(APPROVED_DIR.glob(f"{draft_id}*.yml"))
609-
610-
# Still not found
611-
if not draft_files:
612-
console.print(f"[red]No draft found with ID: {draft_id} in new or approved directories")
639+
# Try to find draft in either new or approved directories
640+
draft_path = find_draft(draft_id, "new", show_error=False) or find_draft(draft_id, "approved")
641+
if not draft_path:
613642
return
614643

615-
draft_path = draft_files[0]
616644
draft = TweetDraft.load(draft_path)
617-
618645
console.print(f"[cyan]Original text: {draft.text}")
619646
draft.text = new_text
620647
draft.save(draft_path)
@@ -625,17 +652,15 @@ def edit(draft_id: str, new_text: str) -> None:
625652
@cli.command()
626653
@click.option("--dry-run", is_flag=True, help="Don't actually post tweets")
627654
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
628-
@click.option("--draft-id", help="Post a specific draft by ID")
655+
@click.option("--draft-id", help="Post a specific draft by ID or path")
629656
def post(dry_run: bool, yes: bool, draft_id: Optional[str] = None) -> None:
630657
"""Post approved tweets"""
631-
632658
# If a specific draft ID is provided, find only that draft
633659
if draft_id:
634-
draft_files = list(APPROVED_DIR.glob(f"*{draft_id}*.yml"))
635-
if not draft_files:
636-
console.print(f"[red]No approved draft found with ID: {draft_id}")
660+
draft_path = find_draft(draft_id, "approved")
661+
if not draft_path:
637662
return
638-
drafts = draft_files
663+
drafts = [draft_path]
639664
else:
640665
drafts = list_drafts("approved")
641666

@@ -799,8 +824,8 @@ def process_timeline_tweets(
799824
in_reply_to=tweet.id if response.type == "reply" else None,
800825
context={
801826
"original_tweet": tweet_data,
802-
"evaluation": asdict(eval_result) if eval_result is not None else None, # Convert to dict
803-
"response_metadata": asdict(response) if response is not None else None, # Convert to dict
827+
"evaluation": (asdict(eval_result) if eval_result is not None else None), # Convert to dict
828+
"response_metadata": (asdict(response) if response is not None else None), # Convert to dict
804829
},
805830
)
806831

@@ -1155,7 +1180,7 @@ def auto(
11551180

11561181
if needs_review_count > 0:
11571182
console.print("\n[yellow]Run the following command to review pending drafts:[/yellow]")
1158-
console.print("[blue]./twitter-cli.py workflow review[/blue]")
1183+
console.print(f"[blue]{sys.argv[0]} review[/blue]")
11591184

11601185

11611186
if __name__ == "__main__":

0 commit comments

Comments
 (0)