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 +10 -9
- package/README.md +11 -10
- package/bin/index.js +2 -1
- 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 +1 -1
- package/src/providers/naver/cafeApiClient.js +0 -34
- package/src/providers/naver/index.js +28 -47
- package/src/runner.js +2 -1
package/README.ko.md
CHANGED
|
@@ -24,13 +24,13 @@
|
|
|
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
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 (가입, 글쓰기, 게시판 조회,
|
|
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 |
|
|
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
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 (
|
|
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,
|
|
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-
|
|
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
|
|
@@ -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,
|
|
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
|
}
|
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
|
-
|
|
349
|
+
captchaValue: opts.captchaValue || undefined,
|
|
350
|
+
captchaKey: opts.captchaKey || undefined,
|
|
350
351
|
answers: opts.answers ? parseList(opts.answers) : undefined,
|
|
351
352
|
}))();
|
|
352
353
|
|