viruagent-cli 0.8.0 → 0.8.1

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 CHANGED
@@ -24,13 +24,13 @@
24
24
 
25
25
  ## 지원 플랫폼
26
26
 
27
- | 플랫폼 | 로그인 | 주요 기능 | 가이드 |
28
- |--------|--------|----------|--------|
29
- | **Tistory** | Playwright (카카오) | 글 발행, 임시저장, 카테고리, 이미지 업로드 | [가이드](docs/ko/guide-tistory.md) |
30
- | **Naver Blog** | Playwright (네이버) | 글 발행, 카테고리, SE Editor, 이미지 업로드 | [가이드](docs/ko/guide-naver.md) |
31
- | **Naver Cafe** | HTTP (브라우저 불필요) | 카페 가입 (캡차 자동해결), 글쓰기, 게시판 조회 | [가이드](docs/ko/guide-naver.md) |
32
- | **Instagram** | HTTP (브라우저 불필요) | 좋아요, 댓글, 팔로우, 포스팅, 프로필, 피드 | [가이드](docs/ko/guide-instagram.md) |
33
- | **X (Twitter)** | HTTP (쿠키 인증) | 트윗, 좋아요, 리트윗, 팔로우, 검색, 타임라인, 미디어 업로드 | [가이드](docs/ko/guide-x.md) |
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
34
 
35
35
  ## 동작 방식
36
36
 
@@ -135,13 +135,14 @@ npx viruagent-cli login --provider x --auth-token <토큰> --ct0 <ct0>
135
135
  | "X에서 AI 도구 검색해줘" | search → 결과 반환 |
136
136
  | "X에서 IT 개발자 좋아요하고 팔로우해줘" | search → like + follow (딜레이 자동 적용) |
137
137
  | "내 X 타임라인 보여줘" | getFeed → 최신 트윗 표시 |
138
- | "이 네이버 카페 가입해줘" | cafe-id → cafe-join (캡차 자동해결) |
138
+ | "이 네이버 카페 가입해줘" | cafe-id → cafe-join (모바일 5회 캡차 면제) |
139
139
  | "네이버 카페에 글 써줘" | cafe-list → cafe-write |
140
140
 
141
141
  ## 플랫폼별 가이드
142
142
 
143
143
  - **[Tistory 가이드](docs/ko/guide-tistory.md)** — 블로그 발행, 이미지 업로드, 카테고리
144
144
  - **[Naver Blog 가이드](docs/ko/guide-naver.md)** — SE Editor, 블로그 발행, 이미지 업로드
145
+ - **[Naver Cafe 가이드](docs/ko/guide-naver-cafe.md)** — 카페 가입 (모바일 5회 캡차 면제), 글쓰기, 슬라이드/콜라주
145
146
  - **[Instagram 가이드](docs/ko/guide-instagram.md)** — 18개 API 메서드, rate limit 규칙, AI 댓글
146
147
  - **[X (Twitter) 가이드](docs/ko/guide-x.md)** — GraphQL API, queryId 동적 동기화, rate limit 규칙
147
148
 
@@ -165,7 +166,7 @@ npx viruagent-cli login --provider x --auth-token <토큰> --ct0 <ct0>
165
166
  | Rate Limiting | 유저별 영속 카운터 + 랜덤 딜레이 |
166
167
  | 이미지 검색 | DuckDuckGo, Wikimedia Commons |
167
168
  | 네이버 에디터 | SE Editor 컴포넌트 모델 + RabbitWrite API |
168
- | 네이버 카페 API | 순수 HTTP (가입, 글쓰기, 게시판 조회, 2Captcha 자동해결) |
169
+ | 네이버 카페 API | 순수 HTTP (가입, 글쓰기, 게시판 조회, 사용자 캡차 입력) |
169
170
  | 출력 형식 | JSON envelope (`{ ok, data }` / `{ ok, error, hint }`) |
170
171
 
171
172
  ## Contributing
package/README.md CHANGED
@@ -24,14 +24,14 @@ Designed not for humans, but for **AI agents**.
24
24
 
25
25
  ## Supported Platforms
26
26
 
27
- | Platform | Login | Features | Guide |
28
- |----------|-------|----------|-------|
29
- | **Tistory** | Playwright (Kakao) | Publish, Draft, Categories, Image Upload | [Guide](docs/en/guide-tistory.md) |
30
- | **Naver Blog** | Playwright (Naver) | Publish, Categories, SE Editor, Image Upload | [Guide](docs/en/guide-naver.md) |
31
- | **Naver Cafe** | HTTP (No Browser) | Cafe Join (auto-captcha), Write Post, Board List | [Guide](docs/en/guide-naver.md) |
32
- | **Instagram** | HTTP (No Browser) | Like, Comment, Follow, Post, Profile, Feed, Rate Limit | [Guide](docs/en/guide-instagram.md) |
33
- | **X (Twitter)** | HTTP (Cookie Auth) | Tweet, Like, Retweet, Follow, Search, Timeline, Media Upload | [Guide](docs/en/guide-x.md) |
34
- | **Reddit** | OAuth2 / Cookie | Post, Comment, Upvote, Search, Subscribe, Subreddit | [Guide](docs/en/guide-reddit.md) |
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
35
 
36
36
  ## How It Works
37
37
 
@@ -136,13 +136,14 @@ npx viruagent-cli login --provider x --auth-token <token> --ct0 <ct0>
136
136
  | "Search X for AI tools" | search → return results |
137
137
  | "Like and follow IT devs on X" | search → like + follow (with delays) |
138
138
  | "Show my X timeline" | getFeed → show latest tweets |
139
- | "Join this Naver cafe" | cafe-id → cafe-join (auto-captcha) |
139
+ | "Join this Naver cafe" | cafe-id → cafe-join (captcha-free for 5 joins) |
140
140
  | "Write a post on Naver cafe" | cafe-list → cafe-write |
141
141
 
142
142
  ## Platform Guides
143
143
 
144
144
  - **[Tistory Guide](docs/en/guide-tistory.md)** — Blog publishing, image upload, categories
145
145
  - **[Naver Blog Guide](docs/en/guide-naver.md)** — SE Editor, blog publishing, image upload
146
+ - **[Naver Cafe Guide](docs/en/guide-naver-cafe.md)** — Cafe join (captcha-free for 5 joins), write post, slide/collage images
146
147
  - **[Instagram Guide](docs/en/guide-instagram.md)** — 18 API methods, rate limits, AI commenting
147
148
  - **[X (Twitter) Guide](docs/en/guide-x.md)** — GraphQL API, dynamic queryId sync, rate limits
148
149
 
@@ -166,7 +167,7 @@ npx viruagent-cli login --provider x --auth-token <token> --ct0 <ct0>
166
167
  | Rate Limiting | Per-user persistent counters with random delays |
167
168
  | Image Search | DuckDuckGo, Wikimedia Commons |
168
169
  | Naver Editor | SE Editor component model + RabbitWrite API |
169
- | Naver Cafe API | Pure HTTP (join, write, board list, 2Captcha auto-solve) |
170
+ | Naver Cafe API | Pure HTTP (join, write, board list, manual captcha) |
170
171
  | Output Format | JSON envelope (`{ ok, data }` / `{ ok, error, hint }`) |
171
172
 
172
173
  ## Contributing
package/bin/index.js CHANGED
@@ -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-api-key <key>', '2Captcha API key for auto-solve')
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viruagent-cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "AI-agent-optimized CLI for blog/SNS publishing and engagement (Tistory, Naver, Instagram, X/Twitter, Reddit)",
5
5
  "private": false,
6
6
  "type": "commonjs",
@@ -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
- 네이버 카페에 가입한다. 캡차가 있으면 2Captcha API로 자동 해결한다.
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-api-key <2captcha_key>] \
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-api-key` | - | 2Captcha API (캡차 자동 해결) | - |
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
- - `--captcha-api-key` 미제공 시: 캡차 필요한 카페는 `captcha_required` 반환
44
- - `--captcha-api-key` 제공 시: 2Captcha로 자동 해결 (최대 3회 재시도)
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-api-key` 옵션 추가 |
56
+ | `CAPTCHA_REQUIRED` | `captchaImageUrl` 확인 후 `--captcha-value`/`--captcha-key`와 재실행 |
52
57
  | `NOT_LOGGED_IN` | `login --provider naver` 먼저 실행 |
53
58
 
54
59
  ## See Also
@@ -64,7 +64,7 @@ SKILLS_DIR: <viruagent-cli 설치 경로>/skills/
64
64
  | va-naver-categories | `va-naver-categories/SKILL.md` | 카테고리 조회 |
65
65
  | va-naver-posts | `va-naver-posts/SKILL.md` | 글 목록/읽기 |
66
66
  | va-naver-cafe-id | `va-naver-cafe-id/SKILL.md` | 카페 ID 추출 |
67
- | va-naver-cafe-join | `va-naver-cafe-join/SKILL.md` | 카페 가입 (캡차 자동해결) |
67
+ | va-naver-cafe-join | `va-naver-cafe-join/SKILL.md` | 카페 가입 (모바일 5회 캡차 면제) |
68
68
  | va-naver-cafe-list | `va-naver-cafe-list/SKILL.md` | 카페 게시판 목록 |
69
69
  | va-naver-cafe-write | `va-naver-cafe-write/SKILL.md` | 카페 글쓰기 (슬라이드/콜라주) |
70
70
  | va-insta | `va-insta/SKILL.md` | Instagram 개요 + 레이트리밋 |
@@ -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, captchaApiKey, answers } = {}) {
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 captchaValue = '';
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
- if (!captchaApiKey) {
271
- return {
272
- provider: 'naver',
273
- mode: 'cafe-join',
274
- status: 'captcha_required',
275
- cafeId: id,
276
- slug,
277
- cafeName: form.cafeName,
278
- captchaImageUrl: form.captchaImageUrl,
279
- captchaKey: form.captchaKey,
280
- message: 'Captcha is required. Provide --captcha-api-key for auto-solve or solve manually.',
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
- if (!solved) {
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: 'captcha_failed',
292
+ status: 'captcha_invalid',
315
293
  cafeId: id,
316
294
  slug,
317
295
  cafeName: form.cafeName,
318
- message: 'Captcha solve failed after 3 attempts.',
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
  }
package/src/runner.js CHANGED
@@ -346,7 +346,8 @@ const runCommand = async (command, opts = {}) => {
346
346
  return withProvider(() => provider.cafeJoin({
347
347
  cafeUrl: opts.cafeUrl,
348
348
  nickname: opts.nickname || undefined,
349
- captchaApiKey: opts.captchaApiKey || undefined,
349
+ captchaValue: opts.captchaValue || undefined,
350
+ captchaKey: opts.captchaKey || undefined,
350
351
  answers: opts.answers ? parseList(opts.answers) : undefined,
351
352
  }))();
352
353