██████╗ ███████╗████████╗███████╗██╗ ██╗████████╗
██╔══██╗██╔════╝╚══██╔══╝██╔════╝██║ ██╔╝╚══██╔══╝
██║ ██║█████╗ ██║ █████╗ █████╔╝ ██║
██║ ██║██╔══╝ ██║ ██╔══╝ ██╔═██╗ ██║
██████╔╝███████╗ ██║ ███████╗██║ ██╗ ██║
╚═════╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
TikTok AI & deepfake detection bot. Mention it on a video or slideshow, get a verdict.
┌──────────────────────────────────────────────────────────────────────────┐
│ TikTok mention ──► Worker polls ──► Download media ──► Sightengine │
│ │
│ @orkavilabs in comments polled via TikTok notification API │
│ Videos downloaded via yt-dlp, uploaded to GCS │
│ Slideshows images extracted, uploaded to GCS │
│ AI + deepfake scan Sightengine genai + deepfake models │
│ Reply Camoufox browser posts result as comment │
│ Rate limiting per-user via Upstash Redis │
│ Caching Firestore dedup + result cache │
└──────────────────────────────────────────────────────────────────────────┘
A TikTok bot that detects AI-generated content and deepfakes. Someone mentions the bot in a comment on any TikTok video or slideshow. The bot downloads the media, runs it through Sightengine's AI generation and deepfake detection models, and replies with a verdict — "AI (92%)", "real (97%)", "deepfake detected", or "not sure".
Supports videos and multi-image slideshows (carousels). Each image in a carousel is scanned individually and results are reported per-slide.
┌─────────────────┐ ┌─────────────────┐
│ detekt_worker │◄── Temporal workflows ──►│ detekt_replier │
│ (GCE VM) │ │ (GCE VM) │
│ │ │ │
│ Poll mentions │ │ Receive reply │
│ Download media │ │ task via │
│ Upload to GCS │ │ Temporal │
│ Scan via │ │ │
│ Sightengine │ │ Open TikTok in │
│ Dispatch reply │ │ Camoufox │
│ │ │ Type & post │
└────────┬─────────┘ │ comment reply │
│ └─────────────────┘
│
┌────┴────────────────────────────────┐
│ Firestore GCS bucket Upstash │
│ (scan cache (media Redis │
│ + dedup) staging) (rate lim)│
└─────────────────────────────────────┘
Two services, both Docker containers on GCE VMs running Container-Optimized OS:
detekt_worker — Temporal worker that runs two workflows. PollerWorkflow polls TikTok's notification API on a loop, filters for trigger-word mentions, then spawns a ProcessMentionWorkflow per mention. That workflow downloads the media (yt-dlp for videos, httpx for slideshow images), uploads to GCS, scans via Sightengine (AI generation + deepfake detection), caches results in Firestore, and dispatches a reply task to the replier's Temporal queue.
detekt_replier — Temporal worker that receives reply tasks. Uses Camoufox (anti-fingerprint Firefox) with Playwright to open the TikTok video page, click "Reply" on the original comment, @mention the user, type the result message with human-like typing delays, and post. Handles session rotation (12h TTL), status-8 detection, and automatic reboots.
| Content type | Download | Scan |
|---|---|---|
| Video | yt-dlp → bytes → GCS vids/{id}/video.mp4 |
Sightengine genai + deepfake video sync (frame-by-frame, averaged) |
| Slideshow | httpx per image → GCS pics/{id}/{n}.{ext} |
Sightengine genai + deepfake per image, max score across carousel |
Results are cached in Firestore by comment ID. Duplicate mentions are deduped by mention:{cid} documents.
The reply message is randomized from a pool of templates based on confidence level:
- High confidence AI → "yep, that's AI (92% sure)"
- High confidence deepfake → "real video but the face is swapped (87% sure)"
- Low confidence → "not sure on this one (54% AI generated/manipulated)"
- Real → "looks real to me (96% sure)"
All secrets are managed via Doppler. Both containers run with doppler run -- as the entrypoint, which injects secrets as environment variables. A background thread refreshes the secret cache every 30 seconds with a 120-second TTL.
Terraform manages all GCP resources:
| Resource | What |
|---|---|
google_compute_instance.dtkt_worker |
GCE VM (c2-standard-4), COS image, runs worker container |
google_compute_instance.dtkt_replier |
GCE VM (c2-standard-4, 30GB disk), COS image, runs replier container |
google_storage_bucket.dtkt_media |
GCS bucket for downloaded media, 7-day lifecycle delete |
google_firestore_database.dtkt_default |
Firestore Native database for scan results + dedup |
| Service accounts | Separate SAs for worker (storage admin, firestore, logging) and replier (storage read/write, logging) |
Region defaults to europe-west2 (London).
# set up .env from example
cp .env.example .env
# edit with your GCP project and Doppler tokens
# deploy everything (builds both images, pushes to GCR, runs terraform)
deploy.bat
# or deploy a single service
deploy.bat worker
deploy.bat replierPowerShell alternative:
.\deploy.ps1 -Target all
.\deploy.ps1 -Target worker
.\deploy.ps1 -Target replierThis builds Docker images, pushes to GCR, and runs terraform apply with auto-approve.
Root (.env):
| Var | What |
|---|---|
DTKT_GCP_PROJECT |
GCP project ID |
DTKT_WORKER_DOPPLER_TOKEN |
Doppler service token for the worker |
DTKT_REPLIER_DOPPLER_TOKEN |
Doppler service token for the replier |
Worker secrets (via Doppler):
| Var | What |
|---|---|
DTKT_TEMPORAL_HOST |
Temporal Cloud host |
DTKT_TEMPORAL_NAMESPACE |
Temporal namespace |
DTKT_TEMPORAL_API_KEY |
Temporal API key |
DTKT_TEMPORAL_TASK_QUEUE |
Replier's Temporal task queue name |
DTKT_WORKER_TASK_QUEUE |
Worker's own Temporal task queue name |
DTKT_POLL_INTERVAL_SECONDS |
Polling interval |
DTKT_TRIGGER_WORD |
Word that triggers the bot in comments |
DTKT_USER_BLACKLIST |
Comma-separated usernames to ignore |
DTKT_TT_SESSIONID |
TikTok session cookie |
DTKT_SENTRY_DSN |
Sentry DSN |
DTKT_SENTRY_FLUSH_TIMEOUT |
Sentry flush timeout in seconds |
DTKT_BUCKET_NAME |
GCS bucket name |
DTKT_FIRESTORE_DATABASE |
Firestore database name |
DTKT_FIRESTORE_SCANS_COLLECTION |
Firestore collection name |
DTKT_SIGHTENGINE_API_USER |
Sightengine API user |
DTKT_SIGHTENGINE_API_SECRET |
Sightengine API secret |
DTKT_SIGHTENGINE_REFRESH_INTERVAL |
Sightengine client refresh interval in seconds |
DTKT_SIGHTENGINE_MIN_INTERVAL |
Min seconds between Sightengine API calls (rate limit) |
DTKT_AI_THRESHOLD |
Score threshold for AI/deepfake classification |
DTKT_LOW_CONFIDENCE_MIN |
Lower bound for "unsure" range |
DTKT_LOW_CONFIDENCE_MAX |
Upper bound for "unsure" range |
DTKT_VIDEO_ENA |
Enable video scanning |
DTKT_PHOTO_ENA |
Enable photo/slideshow scanning |
DTKT_MAX_CAROUSEL_PHOTOS |
Max photos per carousel to scan |
DTKT_SUPPORTED_TYPES |
Comma-separated TikTok aweme types to process |
DTKT_UPSTASH_REDIS_URL |
Upstash Redis URL |
DTKT_UPSTASH_REDIS_TOKEN |
Upstash Redis token |
DTKT_RATE_LIMIT_WINDOW |
Rate limit window in seconds |
DTKT_RATE_LIMIT_MAX |
Max scans per user per window |
DTKT_REDIS_REFRESH_INTERVAL |
Redis client refresh interval in seconds |
DTKT_MAX_POLLS_BEFORE_CAN |
Polls before Temporal continue-as-new |
DTKT_MAX_SESSION_RETRIES |
TikTok session creation retry count |
DTKT_SESSION_MAX_AGE_SECONDS |
TikTok API session max age before rotation |
DTKT_SECRETS_CACHE_TTL |
Secret cache TTL in seconds |
DTKT_SECRETS_REFRESH_INTERVAL |
Secret background refresh interval in seconds |
DTKT_WEBSHARE_API_KEY |
Webshare proxy API key |
DTKT_WEBSHARE_COUNTRY |
Proxy country code |
DTKT_WEBSHARE_PROXY_COUNT |
Number of proxy slots |
DTKT_PROXY_ENABLED |
Enable/disable proxy |
Replier secrets (via Doppler):
| Var | What |
|---|---|
DTKT_TEMPORAL_HOST |
Temporal Cloud host |
DTKT_TEMPORAL_NAMESPACE |
Temporal namespace |
DTKT_TEMPORAL_API_KEY |
Temporal API key |
DTKT_TEMPORAL_TASK_QUEUE |
Task queue to listen on |
DTKT_SENTRY_DSN |
Sentry DSN |
DTKT_BUCKET_NAME |
GCS bucket (for cookies + debug screenshots) |
DTKT_GCS_COOKIES_PATH |
GCS path to Netscape cookie file |
DTKT_GCP_SERVICE_ACCOUNT_JSON |
GCP SA JSON for GCS access |
DTKT_DBG_ENA |
Enable debug screenshots |
DTKT_GCS_DBGSC_PATH |
GCS path prefix for debug screenshots |
DTKT_REPLIER_SESSION_TTL |
Camoufox browser session TTL in seconds |
DTKT_REPLIER_BOOT_MAX_RETRIES |
Browser boot retry count |
DTKT_GEO_LATITUDE |
Browser geolocation latitude |
DTKT_GEO_LONGITUDE |
Browser geolocation longitude |
DTKT_GEO_LANGUAGE |
Browser locale language |
DTKT_GEO_REGION |
Browser locale region |
DTKT_GEO_TIMEZONE |
Browser timezone |
DTKT_SECRETS_CACHE_TTL |
Secret cache TTL in seconds |
DTKT_SECRETS_REFRESH_INTERVAL |
Secret background refresh interval in seconds |
| Proxy vars | Same as worker |
See SECURITY.md.
See MAINTAINING.md.
See CONTRIBUTING.md.
MIT — see LICENSE.