viruagent-cli 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +22 -9
- package/README.md +23 -10
- package/bin/index.js +3 -2
- package/package.json +1 -1
- package/skills/va-naver/SKILL.md +1 -1
- package/skills/va-naver-cafe-join/SKILL.md +12 -7
- package/skills/va-shared/SKILL.md +6 -1
- package/skills/va-threads/SKILL.md +67 -0
- package/skills/va-threads-publish/SKILL.md +76 -0
- package/src/providers/naver/cafeApiClient.js +0 -34
- package/src/providers/naver/index.js +28 -47
- package/src/providers/threads/apiClient.js +487 -0
- package/src/providers/threads/auth.js +142 -0
- package/src/providers/threads/index.js +248 -0
- package/src/providers/threads/session.js +109 -0
- package/src/providers/threads/utils.js +33 -0
- package/src/runner.js +3 -2
- package/src/services/providerManager.js +4 -2
package/README.ko.md
CHANGED
|
@@ -24,13 +24,14 @@
|
|
|
24
24
|
|
|
25
25
|
## 지원 플랫폼
|
|
26
26
|
|
|
27
|
-
| 플랫폼 |
|
|
28
|
-
|
|
29
|
-
| **Tistory** |
|
|
30
|
-
| **Naver Blog** |
|
|
31
|
-
| **Naver Cafe** |
|
|
32
|
-
| **Instagram** |
|
|
33
|
-
| **X (Twitter)** |
|
|
27
|
+
| 플랫폼 | 주요 기능 | 가이드 |
|
|
28
|
+
|--------|----------|--------|
|
|
29
|
+
| **Tistory** | 글 발행, 임시저장, 카테고리, 이미지 업로드 | [가이드](docs/ko/guide-tistory.md) |
|
|
30
|
+
| **Naver Blog** | 글 발행, 카테고리, SE Editor, 이미지 업로드 | [가이드](docs/ko/guide-naver.md) |
|
|
31
|
+
| **Naver Cafe** | 카페 가입 (모바일 5회 캡차 면제), 글쓰기, 게시판 조회, 이미지 업로드 (슬라이드/콜라주) | [가이드](docs/ko/guide-naver-cafe.md) |
|
|
32
|
+
| **Instagram** | 좋아요, 댓글, 팔로우, 포스팅, 프로필, 피드 | [가이드](docs/ko/guide-instagram.md) |
|
|
33
|
+
| **X (Twitter)** | 트윗, 좋아요, 리트윗, 팔로우, 검색, 타임라인, 미디어 업로드 | [가이드](docs/ko/guide-x.md) |
|
|
34
|
+
| **Threads** | 글쓰기, 답글, 좋아요, 팔로우, 이미지 업로드, 검색, 피드 | [가이드](docs/ko/guide-threads.md) |
|
|
34
35
|
|
|
35
36
|
## 동작 방식
|
|
36
37
|
|
|
@@ -119,6 +120,13 @@ npx viruagent-cli login --provider x --auth-token <토큰> --ct0 <ct0>
|
|
|
119
120
|
>
|
|
120
121
|
> 전체 API 레퍼런스, GraphQL 동기화, rate limit 규칙은 [X 가이드](docs/ko/guide-x.md)를 참고하세요.
|
|
121
122
|
|
|
123
|
+
### Threads
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npx viruagent-cli login --provider threads --username <인스타 ID> --password <비밀번호>
|
|
127
|
+
```
|
|
128
|
+
> Instagram 계정으로 로그인합니다. 쓰레드는 별도 계정이 없습니다.
|
|
129
|
+
|
|
122
130
|
## 사용법
|
|
123
131
|
|
|
124
132
|
| 이렇게 말하면 | 에이전트가 알아서 |
|
|
@@ -135,15 +143,19 @@ npx viruagent-cli login --provider x --auth-token <토큰> --ct0 <ct0>
|
|
|
135
143
|
| "X에서 AI 도구 검색해줘" | search → 결과 반환 |
|
|
136
144
|
| "X에서 IT 개발자 좋아요하고 팔로우해줘" | search → like + follow (딜레이 자동 적용) |
|
|
137
145
|
| "내 X 타임라인 보여줘" | getFeed → 최신 트윗 표시 |
|
|
138
|
-
| "이 네이버 카페 가입해줘" | cafe-id → cafe-join (캡차
|
|
146
|
+
| "이 네이버 카페 가입해줘" | cafe-id → cafe-join (모바일 5회 캡차 면제) |
|
|
139
147
|
| "네이버 카페에 글 써줘" | cafe-list → cafe-write |
|
|
148
|
+
| "쓰레드에 글 올려줘" | login → publish (텍스트 또는 이미지) |
|
|
149
|
+
| "이 쓰레드에 답글 달아줘" | comment (rate limit 자동 적용) |
|
|
140
150
|
|
|
141
151
|
## 플랫폼별 가이드
|
|
142
152
|
|
|
143
153
|
- **[Tistory 가이드](docs/ko/guide-tistory.md)** — 블로그 발행, 이미지 업로드, 카테고리
|
|
144
154
|
- **[Naver Blog 가이드](docs/ko/guide-naver.md)** — SE Editor, 블로그 발행, 이미지 업로드
|
|
155
|
+
- **[Naver Cafe 가이드](docs/ko/guide-naver-cafe.md)** — 카페 가입 (모바일 5회 캡차 면제), 글쓰기, 슬라이드/콜라주
|
|
145
156
|
- **[Instagram 가이드](docs/ko/guide-instagram.md)** — 18개 API 메서드, rate limit 규칙, AI 댓글
|
|
146
157
|
- **[X (Twitter) 가이드](docs/ko/guide-x.md)** — GraphQL API, queryId 동적 동기화, rate limit 규칙
|
|
158
|
+
- **[Threads 가이드](docs/ko/guide-threads.md)** — Barcelona API, IGT:2 토큰 인증, rate limit 규칙
|
|
147
159
|
|
|
148
160
|
## 지원 환경
|
|
149
161
|
|
|
@@ -165,7 +177,8 @@ npx viruagent-cli login --provider x --auth-token <토큰> --ct0 <ct0>
|
|
|
165
177
|
| Rate Limiting | 유저별 영속 카운터 + 랜덤 딜레이 |
|
|
166
178
|
| 이미지 검색 | DuckDuckGo, Wikimedia Commons |
|
|
167
179
|
| 네이버 에디터 | SE Editor 컴포넌트 모델 + RabbitWrite API |
|
|
168
|
-
| 네이버 카페 API | 순수 HTTP (가입, 글쓰기, 게시판 조회,
|
|
180
|
+
| 네이버 카페 API | 순수 HTTP (가입, 글쓰기, 게시판 조회, 사용자 캡차 입력) |
|
|
181
|
+
| Threads API | Barcelona (Instagram Private API), IGT:2 토큰 인증 |
|
|
169
182
|
| 출력 형식 | JSON envelope (`{ ok, data }` / `{ ok, error, hint }`) |
|
|
170
183
|
|
|
171
184
|
## Contributing
|
package/README.md
CHANGED
|
@@ -24,14 +24,15 @@ Designed not for humans, but for **AI agents**.
|
|
|
24
24
|
|
|
25
25
|
## Supported Platforms
|
|
26
26
|
|
|
27
|
-
| Platform |
|
|
28
|
-
|
|
29
|
-
| **Tistory** |
|
|
30
|
-
| **Naver Blog** |
|
|
31
|
-
| **Naver Cafe** |
|
|
32
|
-
| **Instagram** |
|
|
33
|
-
| **X (Twitter)** |
|
|
34
|
-
| **Reddit** |
|
|
27
|
+
| Platform | Features | Guide |
|
|
28
|
+
|----------|----------|-------|
|
|
29
|
+
| **Tistory** | Publish, Draft, Categories, Image Upload | [Guide](docs/en/guide-tistory.md) |
|
|
30
|
+
| **Naver Blog** | Publish, Categories, SE Editor, Image Upload | [Guide](docs/en/guide-naver.md) |
|
|
31
|
+
| **Naver Cafe** | Cafe Join (captcha-free for 5 joins), Write Post, Board List, Image Upload (slide/collage) | [Guide](docs/en/guide-naver-cafe.md) |
|
|
32
|
+
| **Instagram** | Like, Comment, Follow, Post, Profile, Feed, Rate Limit | [Guide](docs/en/guide-instagram.md) |
|
|
33
|
+
| **X (Twitter)** | Tweet, Like, Retweet, Follow, Search, Timeline, Media Upload | [Guide](docs/en/guide-x.md) |
|
|
34
|
+
| **Reddit** | Post, Comment, Upvote, Search, Subscribe, Subreddit | [Guide](docs/en/guide-reddit.md) |
|
|
35
|
+
| **Threads** | Post, Reply, Like, Follow, Image Upload, Search, Feed | [Guide](docs/en/guide-threads.md) |
|
|
35
36
|
|
|
36
37
|
## How It Works
|
|
37
38
|
|
|
@@ -120,6 +121,13 @@ npx viruagent-cli login --provider x --auth-token <token> --ct0 <ct0>
|
|
|
120
121
|
>
|
|
121
122
|
> See the [X Guide](docs/en/guide-x.md) for full API reference, GraphQL sync, and rate limit rules.
|
|
122
123
|
|
|
124
|
+
### Threads
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx viruagent-cli login --provider threads --username <instagram_id> --password <password>
|
|
128
|
+
```
|
|
129
|
+
> Threads uses your Instagram account. No separate login needed.
|
|
130
|
+
|
|
123
131
|
## Usage
|
|
124
132
|
|
|
125
133
|
| Say this | Agent handles |
|
|
@@ -136,15 +144,19 @@ npx viruagent-cli login --provider x --auth-token <token> --ct0 <ct0>
|
|
|
136
144
|
| "Search X for AI tools" | search → return results |
|
|
137
145
|
| "Like and follow IT devs on X" | search → like + follow (with delays) |
|
|
138
146
|
| "Show my X timeline" | getFeed → show latest tweets |
|
|
139
|
-
| "Join this Naver cafe" | cafe-id → cafe-join (
|
|
147
|
+
| "Join this Naver cafe" | cafe-id → cafe-join (captcha-free for 5 joins) |
|
|
140
148
|
| "Write a post on Naver cafe" | cafe-list → cafe-write |
|
|
149
|
+
| "Post on Threads" | login → publish (text or image) |
|
|
150
|
+
| "Reply to a thread" | comment (with rate limit) |
|
|
141
151
|
|
|
142
152
|
## Platform Guides
|
|
143
153
|
|
|
144
154
|
- **[Tistory Guide](docs/en/guide-tistory.md)** — Blog publishing, image upload, categories
|
|
145
155
|
- **[Naver Blog Guide](docs/en/guide-naver.md)** — SE Editor, blog publishing, image upload
|
|
156
|
+
- **[Naver Cafe Guide](docs/en/guide-naver-cafe.md)** — Cafe join (captcha-free for 5 joins), write post, slide/collage images
|
|
146
157
|
- **[Instagram Guide](docs/en/guide-instagram.md)** — 18 API methods, rate limits, AI commenting
|
|
147
158
|
- **[X (Twitter) Guide](docs/en/guide-x.md)** — GraphQL API, dynamic queryId sync, rate limits
|
|
159
|
+
- **[Threads Guide](docs/en/guide-threads.md)** — Barcelona API, IGT:2 token auth, rate limits
|
|
148
160
|
|
|
149
161
|
## Supported Environments
|
|
150
162
|
|
|
@@ -166,7 +178,8 @@ npx viruagent-cli login --provider x --auth-token <token> --ct0 <ct0>
|
|
|
166
178
|
| Rate Limiting | Per-user persistent counters with random delays |
|
|
167
179
|
| Image Search | DuckDuckGo, Wikimedia Commons |
|
|
168
180
|
| Naver Editor | SE Editor component model + RabbitWrite API |
|
|
169
|
-
| Naver Cafe API | Pure HTTP (join, write, board list,
|
|
181
|
+
| Naver Cafe API | Pure HTTP (join, write, board list, manual captcha) |
|
|
182
|
+
| Threads API | Barcelona (Instagram Private API), IGT:2 token auth |
|
|
170
183
|
| Output Format | JSON envelope (`{ ok, data }` / `{ ok, error, hint }`) |
|
|
171
184
|
|
|
172
185
|
## Contributing
|
package/bin/index.js
CHANGED
|
@@ -18,7 +18,7 @@ program
|
|
|
18
18
|
|
|
19
19
|
// Global options
|
|
20
20
|
const addProviderOption = (cmd) =>
|
|
21
|
-
cmd.option('--provider <name>', 'Provider name (tistory, naver, insta, x, reddit)', 'tistory');
|
|
21
|
+
cmd.option('--provider <name>', 'Provider name (tistory, naver, insta, x, reddit, threads)', 'tistory');
|
|
22
22
|
|
|
23
23
|
const addDryRunOption = (cmd) =>
|
|
24
24
|
cmd.option('--dry-run', 'Validate params without executing', false);
|
|
@@ -290,7 +290,8 @@ addProviderOption(cafeJoinCmd);
|
|
|
290
290
|
cafeJoinCmd
|
|
291
291
|
.option('--cafe-url <url>', 'Cafe URL or slug')
|
|
292
292
|
.option('--nickname <nick>', 'Nickname to use (default: auto)')
|
|
293
|
-
.option('--captcha-
|
|
293
|
+
.option('--captcha-value <text>', 'Captcha answer text (from AI agent reading the image)')
|
|
294
|
+
.option('--captcha-key <key>', 'Captcha key (returned by previous captcha_required response)')
|
|
294
295
|
.option('--answers <answers>', 'Comma-separated answers for join questions')
|
|
295
296
|
.action((opts) => execute('cafe-join', opts));
|
|
296
297
|
|
package/package.json
CHANGED
package/skills/va-naver/SKILL.md
CHANGED
|
@@ -23,7 +23,7 @@ Naver 블로그 퍼블리싱을 위한 viruagent-cli 가이드. 항상 `--provid
|
|
|
23
23
|
| list-categories | va-naver-categories | 카테고리 조회 |
|
|
24
24
|
| list-posts, read-post | va-naver-posts | 글 목록/읽기 |
|
|
25
25
|
| cafe-id | va-naver-cafe-id | 카페 ID 추출 |
|
|
26
|
-
| cafe-join | va-naver-cafe-join | 카페 가입 (캡차
|
|
26
|
+
| cafe-join | va-naver-cafe-join | 카페 가입 (모바일 5회 캡차 면제) |
|
|
27
27
|
| cafe-list | va-naver-cafe-list | 카페 게시판 목록 |
|
|
28
28
|
| cafe-write | va-naver-cafe-write | 카페 글쓰기 (슬라이드/콜라주) |
|
|
29
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: va-naver-cafe-join
|
|
3
|
-
description: "Naver: 카페 가입 (캡차
|
|
3
|
+
description: "Naver: 카페 가입 (모바일 5회 캡차 면제, 이후 사용자 입력)"
|
|
4
4
|
metadata:
|
|
5
5
|
category: "command"
|
|
6
6
|
provider: "naver"
|
|
@@ -10,7 +10,7 @@ metadata:
|
|
|
10
10
|
|
|
11
11
|
# va-naver-cafe-join — 카페 가입
|
|
12
12
|
|
|
13
|
-
네이버 카페에 가입한다.
|
|
13
|
+
네이버 카페에 가입한다. 모바일 헤더 사용으로 처음 5회까지 캡차 없이 가입 가능. 캡차 발생 시 사용자에게 입력을 요청한다.
|
|
14
14
|
|
|
15
15
|
## 실행
|
|
16
16
|
|
|
@@ -18,7 +18,8 @@ metadata:
|
|
|
18
18
|
npx viruagent-cli cafe-join --provider naver \
|
|
19
19
|
--cafe-url <url_or_slug> \
|
|
20
20
|
[--nickname <닉네임>] \
|
|
21
|
-
[--captcha-
|
|
21
|
+
[--captcha-value <텍스트>] \
|
|
22
|
+
[--captcha-key <키>] \
|
|
22
23
|
[--answers "답1,답2"]
|
|
23
24
|
```
|
|
24
25
|
|
|
@@ -28,7 +29,8 @@ npx viruagent-cli cafe-join --provider naver \
|
|
|
28
29
|
|--------|------|------|--------|
|
|
29
30
|
| `--cafe-url` | O | 카페 URL 또는 슬러그 | - |
|
|
30
31
|
| `--nickname` | - | 사용할 닉네임 | 자동 생성 |
|
|
31
|
-
| `--captcha-
|
|
32
|
+
| `--captcha-value` | - | 캡차 이미지 텍스트 (사용자 입력) | - |
|
|
33
|
+
| `--captcha-key` | - | 캡차 세션 키 (captcha_required 응답에서 제공) | - |
|
|
32
34
|
| `--answers` | - | 가입 질문 답변 (쉼표 구분) | 모두 "네" |
|
|
33
35
|
|
|
34
36
|
### 가입 유형
|
|
@@ -40,15 +42,18 @@ npx viruagent-cli cafe-join --provider naver \
|
|
|
40
42
|
|
|
41
43
|
### 캡차 처리
|
|
42
44
|
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
+
- 모바일 버전(`x-cafe-product: mweb`) 가입은 **처음 5회까지 캡차가 발생하지 않음**
|
|
46
|
+
- 캡차 발생 시: `captcha_required` 상태와 `captchaImageUrl` 반환
|
|
47
|
+
- 사용자가 이미지 URL을 브라우저에서 열어 텍스트를 확인
|
|
48
|
+
- `--captcha-value <텍스트> --captcha-key <키>`와 함께 재실행
|
|
49
|
+
- 틀린 경우 `captcha_invalid`와 새 이미지 URL 제공 → 반복
|
|
45
50
|
|
|
46
51
|
## 에러 처리
|
|
47
52
|
|
|
48
53
|
| 에러 | 조치 |
|
|
49
54
|
|------|------|
|
|
50
55
|
| `ALREADY_JOINED` | 이미 가입된 카페 |
|
|
51
|
-
| `CAPTCHA_REQUIRED` | `--captcha-
|
|
56
|
+
| `CAPTCHA_REQUIRED` | `captchaImageUrl` 확인 후 `--captcha-value`/`--captcha-key`와 재실행 |
|
|
52
57
|
| `NOT_LOGGED_IN` | `login --provider naver` 먼저 실행 |
|
|
53
58
|
|
|
54
59
|
## See Also
|
|
@@ -13,6 +13,8 @@ triggers:
|
|
|
13
13
|
- 좋아요
|
|
14
14
|
- 댓글
|
|
15
15
|
- 팔로우
|
|
16
|
+
- 쓰레드
|
|
17
|
+
- threads
|
|
16
18
|
metadata:
|
|
17
19
|
category: "router"
|
|
18
20
|
requires:
|
|
@@ -32,6 +34,7 @@ viruagent-cli를 사용하는 블로그/SNS 자동화 에이전트입니다.
|
|
|
32
34
|
| 네이버, naver | `va-naver/SKILL.md` |
|
|
33
35
|
| 카페, cafe, 카페 가입, 카페 글쓰기 | `va-naver-cafe-join/SKILL.md` 또는 `va-naver-cafe-write/SKILL.md` |
|
|
34
36
|
| 인스타, instagram, 좋아요, 댓글, 팔로우 | `va-insta/SKILL.md` |
|
|
37
|
+
| 쓰레드, threads | `va-threads/SKILL.md` |
|
|
35
38
|
| 블로그 써줘 (플랫폼 미지정) | 사용자에게 플랫폼 질문 |
|
|
36
39
|
| 블로거 역할 | `persona-blogger/SKILL.md` |
|
|
37
40
|
| 인플루언서 관리 | `persona-influencer-manager/SKILL.md` |
|
|
@@ -64,7 +67,7 @@ SKILLS_DIR: <viruagent-cli 설치 경로>/skills/
|
|
|
64
67
|
| va-naver-categories | `va-naver-categories/SKILL.md` | 카테고리 조회 |
|
|
65
68
|
| va-naver-posts | `va-naver-posts/SKILL.md` | 글 목록/읽기 |
|
|
66
69
|
| va-naver-cafe-id | `va-naver-cafe-id/SKILL.md` | 카페 ID 추출 |
|
|
67
|
-
| va-naver-cafe-join | `va-naver-cafe-join/SKILL.md` | 카페 가입 (캡차
|
|
70
|
+
| va-naver-cafe-join | `va-naver-cafe-join/SKILL.md` | 카페 가입 (모바일 5회 캡차 면제) |
|
|
68
71
|
| va-naver-cafe-list | `va-naver-cafe-list/SKILL.md` | 카페 게시판 목록 |
|
|
69
72
|
| va-naver-cafe-write | `va-naver-cafe-write/SKILL.md` | 카페 글쓰기 (슬라이드/콜라주) |
|
|
70
73
|
| va-insta | `va-insta/SKILL.md` | Instagram 개요 + 레이트리밋 |
|
|
@@ -75,6 +78,8 @@ SKILLS_DIR: <viruagent-cli 설치 경로>/skills/
|
|
|
75
78
|
| va-insta-follow | `va-insta-follow/SKILL.md` | 팔로우/언팔 |
|
|
76
79
|
| va-insta-dm | `va-insta-dm/SKILL.md` | DM |
|
|
77
80
|
| va-insta-feed | `va-insta-feed/SKILL.md` | 피드/프로필/분석 |
|
|
81
|
+
| va-threads | `va-threads/SKILL.md` | Threads 개요 + 레이트리밋 |
|
|
82
|
+
| va-threads-publish | `va-threads-publish/SKILL.md` | Threads 글쓰기 |
|
|
78
83
|
|
|
79
84
|
### 페르소나 스킬
|
|
80
85
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: va-threads
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: "Threads 자동화: 명령 개요 및 레이트리밋 규칙"
|
|
5
|
+
metadata:
|
|
6
|
+
category: "overview"
|
|
7
|
+
provider: "threads"
|
|
8
|
+
requires:
|
|
9
|
+
bins: ["viruagent-cli"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# va-threads — Threads 자동화 개요
|
|
13
|
+
|
|
14
|
+
Threads 자동화를 위한 viruagent-cli 가이드. 항상 `--provider threads` 사용.
|
|
15
|
+
|
|
16
|
+
## 명령 목록
|
|
17
|
+
|
|
18
|
+
| 명령 | 스킬 | 설명 |
|
|
19
|
+
|------|------|------|
|
|
20
|
+
| login | va-threads | 로그인 (Instagram 계정) |
|
|
21
|
+
| publish | va-threads-publish | 글쓰기 (텍스트/이미지) |
|
|
22
|
+
| like | va-threads | 좋아요 |
|
|
23
|
+
| comment | va-threads | 답글 |
|
|
24
|
+
| follow | va-threads | 팔로우 |
|
|
25
|
+
| search | va-threads | 검색 |
|
|
26
|
+
| get-profile | va-threads | 프로필 조회 |
|
|
27
|
+
| get-feed | va-threads | 피드 조회 |
|
|
28
|
+
|
|
29
|
+
## Rate Limit Safety (신규 계정 기준)
|
|
30
|
+
|
|
31
|
+
| 액션 | 딜레이 | 시간당 | 일일 |
|
|
32
|
+
|------|--------|--------|------|
|
|
33
|
+
| Post | 2~5min | 5 | 25 |
|
|
34
|
+
| Like | 20~40s | 15 | 500 |
|
|
35
|
+
| Reply | 5~7min | 5 | 100 |
|
|
36
|
+
| Follow | 1~2min | 15 | 250 |
|
|
37
|
+
|
|
38
|
+
모든 딜레이는 랜덤화되어 자동 적용. 카운터는 세션별 userId로 영속화.
|
|
39
|
+
|
|
40
|
+
## 레이트리밋 확인
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx viruagent-cli rate-limit-status --provider threads
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
대량 작업 전 반드시 확인.
|
|
47
|
+
|
|
48
|
+
## 중요 사항
|
|
49
|
+
|
|
50
|
+
- Instagram 계정을 공유하므로 Instagram rate limit에 영향을 줄 수 있음
|
|
51
|
+
- Barcelona User-Agent + Bloks API 사용
|
|
52
|
+
- IGT:2 토큰이 주요 인증 수단
|
|
53
|
+
- 세션 + 카운터: `~/.viruagent-cli/sessions/threads-session.json`
|
|
54
|
+
|
|
55
|
+
## 환경변수
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
THREADS_USERNAME=
|
|
59
|
+
THREADS_PASSWORD=
|
|
60
|
+
# 또는
|
|
61
|
+
INSTA_USERNAME=
|
|
62
|
+
INSTA_PASSWORD=
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## See Also
|
|
66
|
+
|
|
67
|
+
va-shared, va-threads-publish
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: va-threads-publish
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: "Threads 글쓰기: 텍스트/이미지 포스팅"
|
|
5
|
+
metadata:
|
|
6
|
+
category: "publish"
|
|
7
|
+
provider: "threads"
|
|
8
|
+
requires:
|
|
9
|
+
bins: ["viruagent-cli"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# va-threads-publish — Threads 글쓰기
|
|
13
|
+
|
|
14
|
+
Threads에 텍스트/이미지를 발행하는 가이드.
|
|
15
|
+
|
|
16
|
+
## 명령어
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 텍스트만
|
|
20
|
+
npx viruagent-cli publish --provider threads \
|
|
21
|
+
--content "<텍스트>"
|
|
22
|
+
|
|
23
|
+
# 이미지 첨부
|
|
24
|
+
npx viruagent-cli publish --provider threads \
|
|
25
|
+
--content "<텍스트>" \
|
|
26
|
+
--image-urls "<이미지URL>"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 글쓰기 규칙 (Threads 최적화)
|
|
30
|
+
|
|
31
|
+
### 분량
|
|
32
|
+
- **최적 길이**: 100~300자 (짧고 임팩트 있게)
|
|
33
|
+
- Threads는 짧은 텍스트 중심 — 블로그처럼 길게 쓰지 않음
|
|
34
|
+
|
|
35
|
+
### 첫 줄이 전부다
|
|
36
|
+
- **숫자**: "3가지만 알면 됩니다"
|
|
37
|
+
- **반전**: "잘못 알고 있었습니다"
|
|
38
|
+
- **질문**: "이거 해보셨나요?"
|
|
39
|
+
- **공감**: "저만 이런 거 아니죠?"
|
|
40
|
+
|
|
41
|
+
### 해시태그
|
|
42
|
+
- Threads는 해시태그 효과가 Instagram보다 약함
|
|
43
|
+
- 최대 5개, 본문 하단에 배치
|
|
44
|
+
- 검색 기능이 제한적이므로 키워드를 본문에 자연스럽게 녹이기
|
|
45
|
+
|
|
46
|
+
### 이미지
|
|
47
|
+
- 이미지 1장 첨부 가능
|
|
48
|
+
- `--image-urls`에 직접 URL 지정
|
|
49
|
+
- 고해상도 (1080x1080 이상) 권장
|
|
50
|
+
|
|
51
|
+
## 발행 예시
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 짧은 의견
|
|
55
|
+
npx viruagent-cli publish --provider threads \
|
|
56
|
+
--content "AI가 코드 리뷰해주는 시대.
|
|
57
|
+
|
|
58
|
+
그런데 아직도 혼자 다 보고 있다면
|
|
59
|
+
도구를 바꿀 때입니다."
|
|
60
|
+
|
|
61
|
+
# 이미지 포함
|
|
62
|
+
npx viruagent-cli publish --provider threads \
|
|
63
|
+
--content "오늘의 작업 환경. 카페에서 코딩하는 게 제일 잘 됨." \
|
|
64
|
+
--image-urls "https://example.com/workspace.jpg"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 주의사항
|
|
68
|
+
|
|
69
|
+
- 글 최대 500자 (초과 시 잘림)
|
|
70
|
+
- 발행 간격: 최소 2~5분 (rate limit)
|
|
71
|
+
- 로그인 안 된 경우: `login --provider threads` 먼저 실행
|
|
72
|
+
- Instagram 계정 공유이므로 Instagram challenge 발생 가능
|
|
73
|
+
|
|
74
|
+
## See Also
|
|
75
|
+
|
|
76
|
+
va-threads, va-shared
|
|
@@ -198,39 +198,6 @@ const createCafeApiClient = ({ sessionPath }) => {
|
|
|
198
198
|
return buffer.toString('base64');
|
|
199
199
|
};
|
|
200
200
|
|
|
201
|
-
const solveCaptchaWith2Captcha = async (imageBase64, apiKey) => {
|
|
202
|
-
const submitRes = await fetch('https://2captcha.com/in.php', {
|
|
203
|
-
method: 'POST',
|
|
204
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
205
|
-
body: new URLSearchParams({
|
|
206
|
-
key: apiKey,
|
|
207
|
-
method: 'base64',
|
|
208
|
-
body: imageBase64,
|
|
209
|
-
json: '1',
|
|
210
|
-
}),
|
|
211
|
-
});
|
|
212
|
-
const submitData = await submitRes.json();
|
|
213
|
-
if (submitData.status !== 1) {
|
|
214
|
-
throw createError('CAPTCHA_SUBMIT_FAILED', `2Captcha submit failed: ${submitData.request}`);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const captchaId = submitData.request;
|
|
218
|
-
|
|
219
|
-
// Poll for result (max 120s)
|
|
220
|
-
for (let i = 0; i < 24; i++) {
|
|
221
|
-
await new Promise((r) => setTimeout(r, 5000));
|
|
222
|
-
const pollRes = await fetch(
|
|
223
|
-
`https://2captcha.com/res.php?key=${apiKey}&action=get&id=${captchaId}&json=1`,
|
|
224
|
-
);
|
|
225
|
-
const pollData = await pollRes.json();
|
|
226
|
-
if (pollData.status === 1) return pollData.request;
|
|
227
|
-
if (pollData.request !== 'CAPCHA_NOT_READY') {
|
|
228
|
-
throw createError('CAPTCHA_POLL_FAILED', `2Captcha poll failed: ${pollData.request}`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
throw createError('CAPTCHA_TIMEOUT', '2Captcha timeout (120s)');
|
|
232
|
-
};
|
|
233
|
-
|
|
234
201
|
const submitJoin = async (cafeId, { alimCode, clubTempId, applyPayload }) => {
|
|
235
202
|
const cookieStr = getCookieStr();
|
|
236
203
|
const queryParams = new URLSearchParams({
|
|
@@ -582,7 +549,6 @@ const createCafeApiClient = ({ sessionPath }) => {
|
|
|
582
549
|
checkNickname,
|
|
583
550
|
validateCaptcha,
|
|
584
551
|
downloadCaptchaImage,
|
|
585
|
-
solveCaptchaWith2Captcha,
|
|
586
552
|
submitJoin,
|
|
587
553
|
getBoardList,
|
|
588
554
|
getEditorInfo,
|
|
@@ -241,7 +241,7 @@ const createNaverProvider = ({ sessionPath, account }) => {
|
|
|
241
241
|
});
|
|
242
242
|
},
|
|
243
243
|
|
|
244
|
-
async cafeJoin({ cafeUrl, nickname,
|
|
244
|
+
async cafeJoin({ cafeUrl, nickname, captchaValue, captchaKey: inputCaptchaKey, answers } = {}) {
|
|
245
245
|
return withProviderSession(async () => {
|
|
246
246
|
if (!cafeUrl) {
|
|
247
247
|
const err = new Error('cafeUrl is required');
|
|
@@ -262,60 +262,41 @@ const createNaverProvider = ({ sessionPath, account }) => {
|
|
|
262
262
|
finalNickname = `user${Math.floor(Math.random() * 9000 + 1000)}`;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
// 4. Handle captcha
|
|
266
|
-
let captchaKey = form.captchaKey;
|
|
267
|
-
let
|
|
265
|
+
// 4. Handle captcha — prompt user for manual input
|
|
266
|
+
let captchaKey = inputCaptchaKey || form.captchaKey;
|
|
267
|
+
let resolvedCaptchaValue = captchaValue || '';
|
|
268
268
|
|
|
269
|
-
if (form.needCaptcha) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
// Auto-solve with 2Captcha (max 3 attempts)
|
|
285
|
-
let solved = false;
|
|
286
|
-
let captchaImageUrl = form.captchaImageUrl;
|
|
287
|
-
|
|
288
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
289
|
-
const imgBase64 = await cafeApi.downloadCaptchaImage(captchaImageUrl);
|
|
290
|
-
captchaValue = await cafeApi.solveCaptchaWith2Captcha(imgBase64, captchaApiKey);
|
|
291
|
-
const validateResult = await cafeApi.validateCaptcha(captchaKey, captchaValue);
|
|
292
|
-
|
|
293
|
-
if (validateResult.valid) {
|
|
294
|
-
solved = true;
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Update captcha for retry
|
|
299
|
-
if (validateResult.captchaKey) {
|
|
300
|
-
captchaKey = validateResult.captchaKey;
|
|
301
|
-
captchaImageUrl = validateResult.captchaImageUrl;
|
|
302
|
-
} else {
|
|
303
|
-
const newForm = await cafeApi.getJoinForm(id);
|
|
304
|
-
captchaKey = newForm.captchaKey;
|
|
305
|
-
captchaImageUrl = newForm.captchaImageUrl;
|
|
306
|
-
}
|
|
307
|
-
captchaValue = '';
|
|
308
|
-
}
|
|
269
|
+
if (form.needCaptcha && !resolvedCaptchaValue) {
|
|
270
|
+
return {
|
|
271
|
+
provider: 'naver',
|
|
272
|
+
mode: 'cafe-join',
|
|
273
|
+
status: 'captcha_required',
|
|
274
|
+
cafeId: id,
|
|
275
|
+
slug,
|
|
276
|
+
cafeName: form.cafeName,
|
|
277
|
+
captchaKey: form.captchaKey,
|
|
278
|
+
captchaImageUrl: form.captchaImageUrl,
|
|
279
|
+
nickname: finalNickname,
|
|
280
|
+
message: 'Captcha required. Open the captchaImageUrl in a browser, read the text, and re-run with --captcha-value <text> --captcha-key <key>',
|
|
281
|
+
};
|
|
282
|
+
}
|
|
309
283
|
|
|
310
|
-
|
|
284
|
+
// Validate captcha if provided
|
|
285
|
+
if (form.needCaptcha && resolvedCaptchaValue) {
|
|
286
|
+
const validateResult = await cafeApi.validateCaptcha(captchaKey, resolvedCaptchaValue);
|
|
287
|
+
if (!validateResult.valid) {
|
|
288
|
+
const newForm = await cafeApi.getJoinForm(id);
|
|
311
289
|
return {
|
|
312
290
|
provider: 'naver',
|
|
313
291
|
mode: 'cafe-join',
|
|
314
|
-
status: '
|
|
292
|
+
status: 'captcha_invalid',
|
|
315
293
|
cafeId: id,
|
|
316
294
|
slug,
|
|
317
295
|
cafeName: form.cafeName,
|
|
318
|
-
|
|
296
|
+
captchaKey: newForm.captchaKey,
|
|
297
|
+
captchaImageUrl: newForm.captchaImageUrl,
|
|
298
|
+
nickname: finalNickname,
|
|
299
|
+
message: 'Captcha answer was wrong. Open the new captchaImageUrl, read the text, and retry with --captcha-value <text> --captcha-key <key>',
|
|
319
300
|
};
|
|
320
301
|
}
|
|
321
302
|
}
|