{"id":47825906,"url":"https://github.com/dante4rt/threads-smart-bot","last_synced_at":"2026-04-03T19:40:41.947Z","repository":{"id":344427259,"uuid":"1181780991","full_name":"dante4rt/threads-smart-bot","owner":"dante4rt","description":"Threads bot that crawls trending posts, generates original Bahasa Indonesia content with OpenRouter, and publishes on demand or on a schedule.","archived":false,"fork":false,"pushed_at":"2026-03-28T10:04:14.000Z","size":112,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-28T14:35:52.375Z","etag":null,"topics":["auto","bot","cli","post","threads"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dante4rt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-14T16:06:05.000Z","updated_at":"2026-03-28T10:04:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dante4rt/threads-smart-bot","commit_stats":null,"previous_names":["dante4rt/threads-smart-bot"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dante4rt/threads-smart-bot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dante4rt%2Fthreads-smart-bot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dante4rt%2Fthreads-smart-bot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dante4rt%2Fthreads-smart-bot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dante4rt%2Fthreads-smart-bot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dante4rt","download_url":"https://codeload.github.com/dante4rt/threads-smart-bot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dante4rt%2Fthreads-smart-bot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31373593,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T17:53:18.093Z","status":"ssl_error","status_checked_at":"2026-04-03T17:53:17.617Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["auto","bot","cli","post","threads"],"created_at":"2026-04-03T19:40:41.255Z","updated_at":"2026-04-03T19:40:41.935Z","avatar_url":"https://github.com/dante4rt.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# threads-smart-bot\n\nThreads bot for crawling trending posts, generating original Bahasa Indonesia content with OpenRouter, and publishing on demand or on a schedule.\n\nPipeline: `keyword search -\u003e prompt -\u003e optional image -\u003e publish -\u003e SQLite`\n\n## Quick Start\n\n```bash\ngit clone https://github.com/dante4rt/threads-smart-bot.git\ncd threads-smart-bot\nnpm install\ncp .env.example .env\nnpm run build\n```\n\n```bash\n# 1) authenticate once\nnpm run auth\n\n# 2) smoke-test publishing immediately\nnpm run dev:post:test -- --text \"Hello from my Threads bot\"\n\n# 3) test the full pipeline without posting\nnpm run dev:run:dry\n\n# 4) run the scheduler\nnpm start\n```\n\n\u003e [!IMPORTANT]\n\u003e Run `auth` in the same environment where the bot will run. Local auth writes to `data/state.db`. Docker auth writes to `/app/data/state.db` inside the Docker volume.\n\n\u003e [!TIP]\n\u003e `THREADS_USER_ID` can stay empty. The bot auto-discovers and stores it during `auth`.\n\n## Commands\n\n| Command | Purpose |\n| --- | --- |\n| `npm run auth` | OAuth flow, stores long-lived token in SQLite |\n| `npm run post:test` | Publish one manual test post immediately |\n| `npm run run:once` | Run full pipeline once |\n| `npm run run:dry` | Run full pipeline without publishing |\n| `npm start` | Start scheduler using `POST_TIMES` |\n| `npm run dev:*` | Same commands via `tsx` without building |\n\nCommon dev commands:\n\n```bash\nnpm run dev:auth\nnpm run dev:post:test -- --dry --text \"Smoke test\"\nnpm run dev:run\nnpm run dev:run:dry\n```\n\n## Docker\n\n```bash\ndocker compose up -d --build\ndocker compose run --rm bot node dist/index.js auth\ndocker compose run --rm bot node dist/index.js post-test --dry --text \"Docker smoke test\"\ndocker compose run --rm bot node dist/index.js run --dry\n```\n\nWhat Docker does:\n- loads env from `.env`\n- forces `DB_PATH=/app/data/state.db`\n- persists SQLite in the named volume `bot-data`\n- starts the scheduler by default with `docker compose up -d`\n\n\u003e [!NOTE]\n\u003e `.env` is injected at runtime by Compose. It is not copied into the image.\n\n\u003e [!WARNING]\n\u003e Do not mix `docker compose ... auth` with local `npm run ...` commands. If you auth in Docker, also run the bot in Docker.\n\n## Required Permissions\n\nYour Meta app needs:\n\n| Scope | Needed for |\n| --- | --- |\n| `threads_basic` | all Threads API calls |\n| `threads_content_publish` | publishing posts |\n| `threads_keyword_search` | crawl stage |\n\nMeta docs:\n- [Threads Get Started](https://developers.facebook.com/docs/threads/get-started/)\n- [Get Access Tokens](https://developers.facebook.com/docs/threads/get-started/get-access-tokens-and-permissions/)\n- [Long-Lived Tokens](https://developers.facebook.com/docs/threads/get-started/long-lived-tokens/)\n\n\u003e [!WARNING]\n\u003e Without `threads_keyword_search`, the bot can auth and publish manual tests, but the crawl-based pipeline will fail.\n\n## Environment\n\n\u003cdetails\u003e\n\u003csummary\u003eEnvironment Reference\u003c/summary\u003e\n\n| Variable | Default | Purpose |\n| --- | --- | --- |\n| `THREADS_APP_ID` | — | Meta Threads app ID |\n| `THREADS_APP_SECRET` | — | Meta Threads app secret |\n| `THREADS_USER_ID` | empty | Optional override; auto-stored after auth |\n| `THREADS_REDIRECT_URI` | `https://localhost/callback` | Must match Meta app config exactly |\n| `OPENROUTER_API_KEY` | — | Required for crawl + craft pipeline |\n| `THREADS_ACCESS_TOKEN` | empty | Optional bootstrap fallback before SQLite exists |\n| `OPENROUTER_MODEL` | `anthropic/claude-opus-4-6` | OpenRouter model |\n| `SEARCH_QUERIES` | `viral,tech,AI,trending` | Seed queries for crawl |\n| `MIN_SOURCE_POSTS` | `10` | Minimum unique source posts before crafting |\n| `MIN_SOURCE_QUERIES` | `3` | Minimum distinct queries that must contribute posts |\n| `MAX_SOURCE_POSTS_PER_QUERY` | `4` | Caps prompt dominance from one query |\n| `POST_TIMES` | `09:00,17:00` | Scheduler times |\n| `TIMEZONE` | `Asia/Jakarta` | IANA timezone |\n| `UNSPLASH_ACCESS_KEY` | empty | Optional image search |\n| `DRY_RUN` | `false` | Skip publishing globally |\n| `DB_PATH` | `data/state.db` | SQLite path |\n\n\u003c/details\u003e\n\n## Runtime Behavior\n\n- Auth exchanges a short-lived token for a long-lived token and stores it in SQLite.\n- Tokens auto-refresh near expiry.\n- If Unsplash fails, the bot posts text-only.\n- Crafting is skipped unless crawl coverage is good enough:\n  - at least `MIN_SOURCE_POSTS` unique posts\n  - at least `MIN_SOURCE_QUERIES` contributing queries\n- Prompt inputs are balanced by `MAX_SOURCE_POSTS_PER_QUERY` so one topic does not fully dominate.\n\n## Troubleshooting\n\n\u003cdetails\u003e\n\u003csummary\u003eCommon Issues\u003c/summary\u003e\n\n| Problem | Cause | Fix |\n| --- | --- | --- |\n| `No access token found — run auth first` | Auth was done in a different environment | Re-run `auth` in the same environment you use to run the bot |\n| `Threads API error 400` on publish | Bad stored user ID or invalid request | Re-run `auth`; current code also auto-repairs bad stored IDs |\n| Crawl returns too few posts | Query set is too thin | Broaden `SEARCH_QUERIES` or lower crawl thresholds |\n| Images never attach | Unsplash key missing/invalid | Set `UNSPLASH_ACCESS_KEY` or accept text-only posts |\n\n\u003c/details\u003e\n\n## Development\n\n```bash\nnpm run build\nnpm run typecheck\nnpm test\n```\n\n## License\n\nLicensed under the MIT License. See [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdante4rt%2Fthreads-smart-bot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdante4rt%2Fthreads-smart-bot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdante4rt%2Fthreads-smart-bot/lists"}