# Tiktok Replier

> Scrape TikTok comments on @codewithtyler videos via Apify, then post replies via Playwright using a saved logged-in session. Trigger phrases - "tiktok comments", "tiktok replies", "reply to tiktok", "check tiktok comments", "tiktok unreplied", "respond on tiktok".

- Skill: `tylerprogramming/tiktok-replier` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add tylerprogramming/tiktok-replier`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tylerprogramming/tiktok-replier/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tylerprogramming (https://skillmd.com/u/tylerprogramming)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tylerprogramming/tiktok-replier

---


# TikTok Comment Replier

Two-stage pipeline for managing TikTok comments on @codewithtyler:

- **Read side**: Apify (`clockworks/tiktok-comments-scraper`) — clean structured JSON, $0.001/comment
- **Write side**: Playwright with persistent Chromium profile — needed because TikTok has no official reply API

## Files

- `auth.py` — one-time login, saves persistent profile to `data/profile/`
- `apify_fetch.py` — pulls comments via Apify, writes `data/unreplied.json`
- `reply.py` — reads `data/replies_queue.json`, posts each reply, tracks done in `data/posted.json`
- `data/replies_queue.json` — the queue of {cid, author, comment_text, video_url, reply_text}
- `data/posted.json` — comment IDs already replied to (prevents double-posting)

## The hourly cron + manual draft workflow (PRIMARY pattern)

A cron runs `monitor.py` every hour at :05. It produces three artifacts in `data/`:
- **`inbox.json`** — comments needing your manual reply (with full metadata: cid, author, text, video_url, etc). This is what you ask Claude to help draft.
- **`drafts_queue.json`** — auto-drafted Skool-link replies for keyword CTAs (System / Plan / Skill / Email / Routine / Tools / VFX / schedule / video / workflow). Already ready to post.
- **`drafts.md`** — human-readable running log.

### When the user says "any new comments?" / "check my tiktok inbox"

1. Read `data/inbox.json` (manual-needed) AND `data/drafts_queue.json` (auto-drafts pending).
2. Show the user the breakdown — count of each plus the actual text of inbox items.
3. He picks one or all to draft replies for.

### When the user asks "draft a reply for the @username one"

1. Read the inbox entry for that comment.
2. Compose a reply that fits your tone (casual, helpful, drives to skool.com/the-ai-agency when relevant). Use `/harut` for conversion-sensitive wording.
3. Show the user the draft, get approval.
4. When approved, run:
   ```
   python3 ~/.claude/skills/tiktok-replier/queue_draft.py --cid <cid> --reply "<text>"
   ```
   This appends to `replies_queue.json` AND removes from `inbox.json`.

### When the user says "post them"

```
python3 ~/.claude/skills/tiktok-replier/reply.py --post --headless
```

For auto-drafted Skool-link CTAs:
```
python3 ~/.claude/skills/tiktok-replier/reply.py --post --headless --queue ~/.claude/skills/tiktok-replier/data/drafts_queue.json
```

## Standard workflow (manual / first-time)

### 1. Fetch unreplied comments

Use Apify via MCP (recommended — no token maintenance needed):
```
mcp__apify__call-actor with actor=clockworks/tiktok-comments-scraper
input: {"profiles": ["codewithtyler"], "resultsPerPage": 30, "commentsPerPost": 50, "maxRepliesPerComment": 10, "profileSorting": "latest"}
```
Then `mcp__apify__get-actor-output` with the dataset ID, fields=`cid,uniqueId,text,diggCount,replyCommentTotal,likedByAuthor,repliesToId,createTimeISO,videoWebUrl`.

Filter: top-level (repliesToId is null) AND no reply from @codewithtyler in same thread AND author != codewithtyler.

OR run the local script (needs APIFY_API_TOKEN in `~/.claude/.env`):
```
python3 ~/.claude/skills/tiktok-replier/apify_fetch.py --videos 30 --per 100
```

### 2. Build reply queue

Show the user the unreplied list, confirm wording, and write `data/replies_queue.json`. Each entry needs:
- `cid` — comment ID (used to skip already-posted)
- `author` — TikTok username (used to find the row)
- `comment_text` — original comment text (used to verify the right row)
- `video_url` — full video URL
- `reply_text` — what to reply with

For keyword-style CTAs (System / Plan / Skill / Email / Routine / Tools / VFX / schedule), the standard reply is:
```
https://www.skool.com/the-ai-agency is the link! It is in the Social Media Classroom (free of course!)
```

### 3. Post the replies

Login first if `data/profile/` is empty: `python3 auth.py` (interactive, opens browser, auto-detects sessionid cookie).

**Always test on 1 first, then batch:**
```
python3 reply.py --post --limit 1 --headless    # smoke test
python3 reply.py --post --headless              # batch (30s pause between each)
```

For first run or debugging, drop `--headless` so the browser is visible. Default to `--headless` once the flow is confirmed working.

`--post` is required to actually publish. Without it the script does dry-run (composes but doesn't click submit).

## Critical implementation notes (so future sessions don't re-debug TikTok's DOM)

- **Auth** must use `launch_persistent_context(user_data_dir=...)`, NOT `storage_state`. TikTok's session is bound to fingerprint + localStorage + IndexedDB; cookies alone aren't enough.
- **Comment-icon click** opens the proper comments view: `[data-e2e="comment-icon"]` on the video player. The right-side "Comments" tab is a different sidebar preview that doesn't expose proper Reply controls.
- **Per-comment Reply button** is `div[class*="DivReplyTriggerWrapper"]` inside `div[class*="DivCommentItemWrapper"]`. Click it via real Playwright `page.mouse.click(x, y)` at its center — JS `.click()` does NOT trigger TikTok's React handler.
- **Reply composer** is a contenteditable Draft.js editor where the placeholder div intercepts pointer events. Focus it via JS (`el.focus()`), then `page.keyboard.type()`. Don't use Playwright's `.click()` on the contenteditable.
- **Submit button** is `[data-e2e="comment-post"]` (an arrow icon, NOT text "Post"). Same trick: real mouse click at pixel coords.
- **Verification**: composer disappearing OR clearing OR reply text appearing in DOM = success. Don't trust any single signal.
- **First-time tutorial**: TikTok shows an "Introducing keyboard shortcuts" modal that overlays the comments panel. Strip it surgically — DON'T blanket-remove all `[class*="Modal"]` elements (that nukes the legitimate panel).

## Safety rules

- **Default to `--limit 1` on first run** of any new queue
- **Always show drafts to you** before bulk posting (matches your memory: "Always confirm before scheduling, even if approved earlier in session")
- **30s spacing between posts** to stay under TikTok's spam threshold
- **`posted.json` tracks done IDs** so re-runs after a crash skip what already worked
- **No automatic re-fetch + re-post loops** — you initiate each batch

## "Keep checking" - the recurring use case

Two paths you can pick:
1. **Apify scheduler** (recommended): set up in their web console, runs in their cloud, emails him the dataset link. No local laptop dependency.
2. **Local cron** via `/schedule` skill: re-runs `apify_fetch.py` on a schedule, then prompts to review unreplied list.

Both options drop the post step into manual review — you approve before any reply gets published.

