{
  "id": "reddit",
  "name": "Reddit Automation",
  "description": "Battle-tested playbook for Reddit browser automation via CDP. Covers feed browsing, upvote/downvote, comment, create post, search, join subreddit, and profile viewing. Reddit uses shadow DOM extensively (shreddit-post, faceplate-textarea-input) and Lexical editor for text input.",
  "platform": "reddit",
  "version": "1.0.0",
  "urlPatterns": [
    "*reddit.com*"
  ],
  "tags": [
    "reddit",
    "social",
    "browser",
    "cdp",
    "shadow-dom"
  ],
  "successCount": 0,
  "failCount": 0,
  "urls": {
    "home": "https://www.reddit.com/",
    "popular": "https://www.reddit.com/r/popular/",
    "subreddit": "https://www.reddit.com/r/{subreddit}/",
    "post": "https://www.reddit.com/r/{subreddit}/comments/{postId}/{slug}/",
    "submit": "https://www.reddit.com/r/{subreddit}/submit/?type=TEXT",
    "submit_link": "https://www.reddit.com/r/{subreddit}/submit/?type=LINK",
    "submit_image": "https://www.reddit.com/r/{subreddit}/submit/?type=IMAGE",
    "search": "https://www.reddit.com/search/?q={query}&type=link",
    "search_subreddit": "https://www.reddit.com/r/{subreddit}/search/?q={query}",
    "profile": "https://www.reddit.com/user/{username}/",
    "settings": "https://www.reddit.com/settings/",
    "messages": "https://www.reddit.com/message/inbox/",
    "notifications": "https://www.reddit.com/notifications/"
  },
  "selectors": {
    "nav": {
      "home": "left-nav-top-section button:has-text('Home')",
      "popular": "left-nav-top-section button:has-text('Popular')",
      "search": "input[type='search'], #search-input, reddit-search-large input",
      "create_post": "a[href='/submit'], button:has-text('Create Post')",
      "user_menu": "button:has-text('Expand user menu')"
    },
    "post": {
      "container": "shreddit-post",
      "title_attr": "[post-title]",
      "author_attr": "[author]",
      "score_attr": "[score]",
      "permalink_attr": "[permalink]",
      "subreddit_attr": "[subreddit-prefixed-name]"
    },
    "post_actions_shadow": {
      "upvote": "shreddit-post >> shadowRoot >> button:has-text('Upvote')",
      "downvote": "shreddit-post >> shadowRoot >> button:has-text('Downvote')",
      "comments": "shreddit-post >> shadowRoot >> button:has-text('Go to comments')",
      "share": "shreddit-post >> shadowRoot >> button:has-text('Share')",
      "_note": "These selectors are conceptual — use JS to access shadow roots: post.shadowRoot.querySelectorAll('button')"
    },
    "comment": {
      "composer": "shreddit-composer",
      "textbox": "div[role='textbox'][contenteditable='true'][aria-placeholder='Join the conversation']",
      "submit_button": "button:has-text('Comment')",
      "comment_element": "shreddit-comment",
      "comment_author": "shreddit-comment[author]",
      "comment_text": "shreddit-comment [slot='comment']"
    },
    "create_post": {
      "title_wrapper": "faceplate-textarea-input[name='title']",
      "title_textarea": "faceplate-textarea-input[name='title'] >> shadowRoot >> textarea",
      "body_textbox": "div[role='textbox'][aria-placeholder='Body text (optional)']",
      "submit_button": "button:has-text('Post')",
      "flair_button": "button:has-text('Add flair')",
      "type_text": "[type='TEXT']",
      "type_link": "[type='LINK']",
      "type_image": "[type='IMAGE']",
      "_note": "Title textarea is inside shadow root of faceplate-textarea-input — use native value setter via JS"
    },
    "subreddit": {
      "header": "shreddit-subreddit-header",
      "join_button": "button:has-text('Join')",
      "joined_button": "button:has-text('Joined')",
      "sort_dropdown": "shreddit-sort-dropdown"
    },
    "search": {
      "input": "input[type='search']",
      "results": "shreddit-post, a[href*='/comments/']",
      "filter_posts": "button:has-text('Posts')",
      "filter_communities": "button:has-text('Communities')",
      "filter_comments": "button:has-text('Comments')",
      "filter_people": "button:has-text('People')"
    },
    "feed": {
      "post_list": "shreddit-feed, main",
      "post_item": "shreddit-post",
      "ad_post": "shreddit-ad-post"
    }
  },
  "detection": {
    "is_logged_in": "!!document.querySelector('button[aria-label*=\"Expand user menu\"], button:has-text(\"Expand user menu\")')",
    "is_home": "window.location.pathname === '/' || window.location.pathname === '/home/'",
    "is_subreddit": "/^\\/r\\/[a-zA-Z0-9_]+\\/?$/.test(window.location.pathname)",
    "is_post": "/\\/comments\\//.test(window.location.pathname)",
    "is_submit": "window.location.pathname.includes('/submit')",
    "is_search": "window.location.pathname === '/search/'",
    "is_profile": "window.location.pathname.startsWith('/user/')"
  },
  "flows": {
    "upvote_post": {
      "steps": [
        "Find post element: document.querySelector('shreddit-post')",
        "Access shadow root: post.shadowRoot",
        "Find Upvote button: post.shadowRoot.querySelectorAll('button') — find by textContent 'Upvote'",
        "Click via JS: button.click()",
        "Verify: button text or style changes (filled arrow)"
      ],
      "guards": [
        "Must be logged in",
        "Post must not already be upvoted"
      ],
      "why": "Reddit uses shadow DOM for post action buttons. Regular CSS selectors cannot reach them — must use element.shadowRoot.querySelectorAll()."
    },
    "downvote_post": {
      "steps": [
        "Find post element: document.querySelector('shreddit-post')",
        "Access shadow root: post.shadowRoot",
        "Find Downvote button by textContent",
        "Click via JS: button.click()"
      ],
      "guards": [
        "Must be logged in"
      ]
    },
    "comment_on_post": {
      "steps": [
        "Navigate to post page: reddit.com/r/{subreddit}/comments/{postId}/{slug}/",
        "Scroll down to find comment composer",
        "Click on 'Join the conversation' placeholder area using screen coordinates (CDP click at element center)",
        "Wait 1s for Lexical editor to activate and show Cancel/Comment buttons",
        "Use browser_fill_form with 80ms delay on div[role='textbox'][aria-placeholder='Join the conversation']",
        "Verify textbox content is set",
        "Click Comment button via JS: find visible button with textContent 'Comment'",
        "Wait 2s for comment to post — composer collapses when successful"
      ],
      "guards": [
        "Must be logged in",
        "Comment composer must be visible (scroll to it)"
      ],
      "why": "Reddit uses Lexical editor which rejects execCommand, clipboard paste, and fast CDP key events. browser_fill_form with 80ms delay works. The composer must be activated by clicking the placeholder area first — it starts collapsed with 0 dimensions until clicked."
    },
    "create_post": {
      "steps": [
        "Navigate to reddit.com/r/{subreddit}/submit/?type=TEXT",
        "Wait 2s for submit page to load",
        "Set title via JS: access faceplate-textarea-input[name='title'].shadowRoot.querySelector('textarea'), use native value setter (Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set), dispatch input event",
        "Click body textbox via browser_human_click: div[role='textbox'][aria-placeholder='Body text (optional)']",
        "Type body via browser_fill_form with 80ms delay",
        "Optionally add flair",
        "Click Post button"
      ],
      "guards": [
        "Must be logged in",
        "Must have sufficient karma for subreddit"
      ],
      "why": "Title is inside shadow root of faceplate-textarea-input custom element — cannot use CSS selectors directly. Must use native value setter. Body uses Lexical editor — same approach as comments."
    },
    "search": {
      "steps": [
        "Navigate to reddit.com/search/?q={query}&type=link",
        "Wait 2s for results to load",
        "Extract post data from shreddit-post elements using attributes: post-title, author, score, permalink, subreddit-prefixed-name",
        "Filter by type: Posts, Communities, Comments, People tabs"
      ],
      "guards": [],
      "why": "Direct URL navigation is more reliable than typing in Reddit's search input. Post metadata is stored as HTML attributes on shreddit-post elements."
    },
    "join_subreddit": {
      "steps": [
        "Navigate to reddit.com/r/{subreddit}/",
        "Wait 2s for subreddit to load",
        "Find Join button — may be in shreddit-subreddit-header shadow root or main DOM",
        "Click Join button",
        "Verify button changes to 'Joined'"
      ],
      "guards": [
        "Must be logged in",
        "Must not already be a member"
      ],
      "why": "Join button location varies — sometimes in shadow DOM of shreddit-subreddit-header, sometimes in sidebar. Check both."
    },
    "scroll_and_extract_feed": {
      "steps": [
        "Navigate to reddit.com or reddit.com/r/{subreddit}/",
        "Wait 2s for feed to load",
        "Extract posts from shreddit-post elements using attributes: post-title, author, score, permalink, subreddit-prefixed-name",
        "Access shadow roots for upvote/downvote/comment counts",
        "Scroll down for more posts",
        "Wait 1.5s for lazy-loaded content"
      ],
      "guards": []
    },
    "view_profile": {
      "steps": [
        "Navigate to reddit.com/user/{username}/",
        "Wait 2s for profile to load",
        "Extract karma, account age, recent posts and comments",
        "Scroll for more activity"
      ],
      "guards": [
        "Profile must not be private or suspended"
      ]
    },
    "extract_post_data": {
      "steps": [
        "Query shreddit-post elements",
        "Read attributes: post.getAttribute('post-title'), post.getAttribute('author'), post.getAttribute('score'), post.getAttribute('permalink'), post.getAttribute('subreddit-prefixed-name')",
        "For action buttons, access post.shadowRoot and find buttons by textContent",
        "For comment count, find button with 'Go to comments' text in shadow root"
      ],
      "guards": [],
      "why": "Reddit stores all post metadata as HTML attributes on the shreddit-post custom element. This is the most reliable way to extract data."
    }
  },
  "errors": [
    {
      "error": "Login required",
      "context": "Any action requiring authentication (upvote, comment, post)",
      "solution": "Log in manually first. Reddit uses email/password or Google/Apple SSO.",
      "severity": "high"
    },
    {
      "error": "Shadow DOM blocks CSS selectors for action buttons",
      "context": "Upvote, Downvote, Share, Comment buttons are inside shreddit-post.shadowRoot",
      "solution": "Access via JS: document.querySelector('shreddit-post').shadowRoot.querySelectorAll('button'). Find by textContent ('Upvote', 'Downvote', etc.).",
      "severity": "high"
    },
    {
      "error": "Lexical editor rejects execCommand, clipboard paste, and fast typing",
      "context": "Comment and post body use Lexical editor (contenteditable div with data-lexical-editor attribute)",
      "solution": "Use browser_fill_form with 80ms+ delay. The composer must be activated first by clicking the placeholder area — it starts collapsed with 0 dimensions.",
      "severity": "high"
    },
    {
      "error": "Title textarea inside shadow root of faceplate-textarea-input",
      "context": "Post creation title field is not accessible via regular CSS selectors",
      "solution": "Use JS: document.querySelector('faceplate-textarea-input[name=\"title\"]').shadowRoot.querySelector('textarea'). Set value with native setter: Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set.call(textarea, text). Dispatch input event.",
      "severity": "high"
    },
    {
      "error": "Comment composer has 0 dimensions until activated",
      "context": "The shreddit-composer and its textbox start with 0x0 dimensions",
      "solution": "Scroll to the comment area and click on the 'Join the conversation' placeholder using screen coordinates. Wait for composer to expand before typing.",
      "severity": "medium"
    },
    {
      "error": "browser_human_click returns (0,0) for shadow DOM elements",
      "context": "Elements inside shadow roots report 0,0 coordinates to CDP selectors",
      "solution": "Use browser_js to access shadow root, get bounding rect, then click by coordinates. Or use JS click: element.click().",
      "severity": "medium"
    },
    {
      "error": "Join button not found in expected location",
      "context": "Join/Joined button may be in shadow root of shreddit-subreddit-header or rendered lazily",
      "solution": "Search both main DOM and all shadow roots for button with 'Join' textContent. May need to scroll sidebar into view.",
      "severity": "medium"
    },
    {
      "error": "Ad posts mixed with regular posts",
      "context": "Reddit inserts shreddit-ad-post elements between regular shreddit-post elements",
      "solution": "Filter by tag name: use shreddit-post (not shreddit-ad-post) for real content.",
      "severity": "low"
    },
    {
      "error": "Reddit ads open new tabs on click",
      "context": "Clicking on promoted posts navigates to external URLs",
      "solution": "Skip shreddit-ad-post elements when interacting with feed.",
      "severity": "low"
    },
    {
      "error": "Frontmost app switches to VS Code when approving tool calls",
      "context": "AccessibilityAdapter attaches to frontmost app",
      "solution": "Use CDP browser tools — they work regardless of which app is frontmost.",
      "severity": "high"
    }
  ],
  "policyNotes": {
    "rate_limits": [
      "Posts: ~5/day for new accounts, more for established",
      "Comments: ~50/day",
      "Upvotes/Downvotes: no strict limit but don't spam",
      "New accounts have karma requirements for many subreddits",
      "Some subreddits require minimum account age"
    ],
    "safety": [
      "Never automate login",
      "Reddit aggressively detects automation — add 3-10s random delays",
      "Use browser_stealth before interacting",
      "Respect subreddit rules — many ban self-promotion",
      "New accounts have very limited posting ability",
      "Use browser_fill_form with 80ms+ delays for Lexical editor text input",
      "Reddit may require CAPTCHA for suspicious activity"
    ],
    "tool_preferences": [
      "browser_fill_form (80ms+ delay) — for Lexical editor text (comments, post body)",
      "browser_js with native value setter — for shadow DOM inputs (post title)",
      "browser_js with shadowRoot access — for upvote/downvote/action buttons",
      "browser_human_click — for activating collapsed composers (needs real coordinates)",
      "browser_navigate — for search (direct URL), subreddit/post navigation"
    ]
  },
  "steps": [
    {
      "action": "navigate",
      "url": "https://www.reddit.com/",
      "description": "Open Reddit home feed"
    },
    {
      "action": "wait",
      "ms": 2000,
      "description": "Wait for feed to load"
    },
    {
      "action": "extract",
      "target": "shreddit-post",
      "format": "attributes",
      "description": "Extract post titles, authors, scores from shreddit-post attributes"
    },
    {
      "action": "scroll",
      "direction": "down",
      "amount": 5,
      "description": "Scroll for more posts"
    },
    {
      "action": "wait",
      "ms": 1500,
      "description": "Wait for lazy-loaded posts"
    },
    {
      "action": "screenshot",
      "description": "Capture current feed state"
    }
  ]
}
