viruagent-cli 0.6.1 → 0.7.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 +16 -0
- package/README.md +16 -0
- package/package.json +1 -1
- package/skills/va-insta/SKILL.md +2 -1
- package/skills/va-insta-publish/SKILL.md +157 -0
- package/skills/va-shared/SKILL.md +1 -0
- package/skills/va-tistory-publish/SKILL.md +6 -36
- package/src/providers/tistory/index.js +10 -1
- package/src/providers/tistory/session.js +42 -0
- package/src/providers/x/apiClient.js +626 -0
- package/src/providers/x/auth.js +87 -0
- package/src/providers/x/graphqlSync.js +140 -0
- package/src/providers/x/index.js +251 -0
- package/src/providers/x/session.js +114 -0
- package/src/providers/x/utils.js +32 -0
- package/src/runner.js +33 -1
- package/src/services/providerManager.js +4 -2
package/README.ko.md
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
| **Tistory** | Playwright (카카오) | 글 발행, 임시저장, 카테고리, 이미지 업로드 | [가이드](docs/ko/guide-tistory.md) |
|
|
30
30
|
| **Naver Blog** | Playwright (네이버) | 글 발행, 카테고리, SE Editor, 이미지 업로드 | [가이드](docs/ko/guide-naver.md) |
|
|
31
31
|
| **Instagram** | HTTP (브라우저 불필요) | 좋아요, 댓글, 팔로우, 포스팅, 프로필, 피드 | [가이드](docs/ko/guide-instagram.md) |
|
|
32
|
+
| **X (Twitter)** | HTTP (쿠키 인증) | 트윗, 좋아요, 리트윗, 팔로우, 검색, 타임라인, 미디어 업로드 | [가이드](docs/ko/guide-x.md) |
|
|
32
33
|
|
|
33
34
|
## 동작 방식
|
|
34
35
|
|
|
@@ -95,6 +96,15 @@ npx viruagent-cli login --provider insta --username <인스타 ID> --password <
|
|
|
95
96
|
>
|
|
96
97
|
> 전체 API 레퍼런스와 rate limit 규칙은 [Instagram 가이드](docs/ko/guide-instagram.md)를 참고하세요.
|
|
97
98
|
|
|
99
|
+
### X (Twitter)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx viruagent-cli login --provider x --auth-token <토큰> --ct0 <ct0>
|
|
103
|
+
```
|
|
104
|
+
> 브라우저에서 `auth_token`과 `ct0` 쿠키를 추출하세요. 비밀번호 로그인 없음 — 쿠키 기반 인증만 지원.
|
|
105
|
+
>
|
|
106
|
+
> 전체 API 레퍼런스, GraphQL 동기화, rate limit 규칙은 [X 가이드](docs/ko/guide-x.md)를 참고하세요.
|
|
107
|
+
|
|
98
108
|
## 사용법
|
|
99
109
|
|
|
100
110
|
| 이렇게 말하면 | 에이전트가 알아서 |
|
|
@@ -107,12 +117,17 @@ npx viruagent-cli login --provider insta --username <인스타 ID> --password <
|
|
|
107
117
|
| "이 사람 피드 분석해서 댓글 달아줘" | analyzePost → AI 댓글 생성 → comment |
|
|
108
118
|
| "@user 팔로우해줘" | 로그인 → follow (딜레이 자동 적용) |
|
|
109
119
|
| "인스타 rate limit 확인해줘" | rate-limit-status → 카운터 표시 |
|
|
120
|
+
| "이 내용으로 트윗해줘" | X 로그인 → publish (rate limit 자동 적용) |
|
|
121
|
+
| "X에서 AI 도구 검색해줘" | search → 결과 반환 |
|
|
122
|
+
| "X에서 IT 개발자 좋아요하고 팔로우해줘" | search → like + follow (딜레이 자동 적용) |
|
|
123
|
+
| "내 X 타임라인 보여줘" | getFeed → 최신 트윗 표시 |
|
|
110
124
|
|
|
111
125
|
## 플랫폼별 가이드
|
|
112
126
|
|
|
113
127
|
- **[Tistory 가이드](docs/ko/guide-tistory.md)** — 블로그 발행, 이미지 업로드, 카테고리
|
|
114
128
|
- **[Naver Blog 가이드](docs/ko/guide-naver.md)** — SE Editor, 블로그 발행, 이미지 업로드
|
|
115
129
|
- **[Instagram 가이드](docs/ko/guide-instagram.md)** — 18개 API 메서드, rate limit 규칙, AI 댓글
|
|
130
|
+
- **[X (Twitter) 가이드](docs/ko/guide-x.md)** — GraphQL API, queryId 동적 동기화, rate limit 규칙
|
|
116
131
|
|
|
117
132
|
## 지원 환경
|
|
118
133
|
|
|
@@ -129,6 +144,7 @@ npx viruagent-cli login --provider insta --username <인스타 ID> --password <
|
|
|
129
144
|
| CLI 프레임워크 | Commander.js |
|
|
130
145
|
| 브라우저 자동화 | Playwright (Tistory, Naver만 사용) |
|
|
131
146
|
| Instagram API | 순수 HTTP fetch (브라우저 불필요) |
|
|
147
|
+
| X (Twitter) API | 내부 GraphQL API + queryId 동적 추출 |
|
|
132
148
|
| 세션 관리 | JSON 파일 (`~/.viruagent-cli/`) |
|
|
133
149
|
| Rate Limiting | 유저별 영속 카운터 + 랜덤 딜레이 |
|
|
134
150
|
| 이미지 검색 | DuckDuckGo, Wikimedia Commons |
|
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ Designed not for humans, but for **AI agents**.
|
|
|
29
29
|
| **Tistory** | Playwright (Kakao) | Publish, Draft, Categories, Image Upload | [Guide](docs/en/guide-tistory.md) |
|
|
30
30
|
| **Naver Blog** | Playwright (Naver) | Publish, Categories, SE Editor, Image Upload | [Guide](docs/en/guide-naver.md) |
|
|
31
31
|
| **Instagram** | HTTP (No Browser) | Like, Comment, Follow, Post, Profile, Feed, Rate Limit | [Guide](docs/en/guide-instagram.md) |
|
|
32
|
+
| **X (Twitter)** | HTTP (Cookie Auth) | Tweet, Like, Retweet, Follow, Search, Timeline, Media Upload | [Guide](docs/en/guide-x.md) |
|
|
32
33
|
|
|
33
34
|
## How It Works
|
|
34
35
|
|
|
@@ -95,6 +96,15 @@ npx viruagent-cli login --provider insta --username <id> --password <pw>
|
|
|
95
96
|
>
|
|
96
97
|
> See the [Instagram Guide](docs/en/guide-instagram.md) for full API reference and rate limit rules.
|
|
97
98
|
|
|
99
|
+
### X (Twitter)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx viruagent-cli login --provider x --auth-token <token> --ct0 <ct0>
|
|
103
|
+
```
|
|
104
|
+
> Extract `auth_token` and `ct0` cookies from your browser. No password login — cookie-based auth only.
|
|
105
|
+
>
|
|
106
|
+
> See the [X Guide](docs/en/guide-x.md) for full API reference, GraphQL sync, and rate limit rules.
|
|
107
|
+
|
|
98
108
|
## Usage
|
|
99
109
|
|
|
100
110
|
| Say this | Agent handles |
|
|
@@ -107,12 +117,17 @@ npx viruagent-cli login --provider insta --username <id> --password <pw>
|
|
|
107
117
|
| "Analyze and comment on @user's feed" | analyzePost → AI generates comment → comment |
|
|
108
118
|
| "Follow @user" | Login → follow (with delay) |
|
|
109
119
|
| "Check Instagram rate limit" | rate-limit-status → show counters |
|
|
120
|
+
| "Tweet this text" | X login → publish (with rate limit) |
|
|
121
|
+
| "Search X for AI tools" | search → return results |
|
|
122
|
+
| "Like and follow IT devs on X" | search → like + follow (with delays) |
|
|
123
|
+
| "Show my X timeline" | getFeed → show latest tweets |
|
|
110
124
|
|
|
111
125
|
## Platform Guides
|
|
112
126
|
|
|
113
127
|
- **[Tistory Guide](docs/en/guide-tistory.md)** — Blog publishing, image upload, categories
|
|
114
128
|
- **[Naver Blog Guide](docs/en/guide-naver.md)** — SE Editor, blog publishing, image upload
|
|
115
129
|
- **[Instagram Guide](docs/en/guide-instagram.md)** — 18 API methods, rate limits, AI commenting
|
|
130
|
+
- **[X (Twitter) Guide](docs/en/guide-x.md)** — GraphQL API, dynamic queryId sync, rate limits
|
|
116
131
|
|
|
117
132
|
## Supported Environments
|
|
118
133
|
|
|
@@ -129,6 +144,7 @@ npx viruagent-cli login --provider insta --username <id> --password <pw>
|
|
|
129
144
|
| CLI Framework | Commander.js |
|
|
130
145
|
| Browser Automation | Playwright (Tistory, Naver only) |
|
|
131
146
|
| Instagram API | Pure HTTP fetch (no browser) |
|
|
147
|
+
| X (Twitter) API | Internal GraphQL API with dynamic queryId extraction |
|
|
132
148
|
| Session Management | JSON file (`~/.viruagent-cli/`) |
|
|
133
149
|
| Rate Limiting | Per-user persistent counters with random delays |
|
|
134
150
|
| Image Search | DuckDuckGo, Wikimedia Commons |
|
package/package.json
CHANGED
package/skills/va-insta/SKILL.md
CHANGED
|
@@ -18,6 +18,7 @@ Instagram 자동화를 위한 viruagent-cli 가이드. 항상 `--provider insta`
|
|
|
18
18
|
| 명령 | 스킬 | 설명 |
|
|
19
19
|
|------|------|------|
|
|
20
20
|
| login | va-insta-login | 로그인 + 챌린지 해결 |
|
|
21
|
+
| publish | va-insta-publish | 게시물 발행 (어그로 전략 포함) |
|
|
21
22
|
| like, unlike | va-insta-like | 좋아요/좋아요 취소 |
|
|
22
23
|
| comment | va-insta-comment | 댓글 작성 |
|
|
23
24
|
| follow, unfollow | va-insta-follow | 팔로우/언팔로우 |
|
|
@@ -61,4 +62,4 @@ INSTA_PASSWORD=
|
|
|
61
62
|
|
|
62
63
|
## See Also
|
|
63
64
|
|
|
64
|
-
va-shared, va-insta-login, va-insta-like, va-insta-comment, va-insta-follow, va-insta-dm, va-insta-feed
|
|
65
|
+
va-shared, va-insta-login, va-insta-publish, va-insta-like, va-insta-comment, va-insta-follow, va-insta-dm, va-insta-feed
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: va-insta-publish
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: "Instagram 게시물 발행: 어그로 캡션 전략, 이미지 선택, 해시태그"
|
|
5
|
+
metadata:
|
|
6
|
+
category: "publish"
|
|
7
|
+
provider: "insta"
|
|
8
|
+
requires:
|
|
9
|
+
bins: ["viruagent-cli"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# va-insta-publish — Instagram 게시물 발행
|
|
13
|
+
|
|
14
|
+
Instagram에 이미지+캡션을 발행하는 가이드. 어그로(고engagement) 콘텐츠 전략 포함.
|
|
15
|
+
|
|
16
|
+
## 명령어
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx viruagent-cli publish --provider insta \
|
|
20
|
+
--image-urls "<이미지URL>" \
|
|
21
|
+
--content "<캡션텍스트>"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
또는 키워드로 이미지 자동 검색:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx viruagent-cli publish --provider insta \
|
|
28
|
+
--related-image-keywords "<키워드>" \
|
|
29
|
+
--content "<캡션텍스트>"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 어그로 주제 유형 (인게이지먼트 높은 순)
|
|
33
|
+
|
|
34
|
+
### 1. 논쟁형 (Controversial) — 저장+댓글 최고
|
|
35
|
+
공감대를 자극하는 주장. 한쪽 편을 들되 반론 여지를 남긴다.
|
|
36
|
+
|
|
37
|
+
| 예시 주제 | 핵심 앵글 |
|
|
38
|
+
|-----------|-----------|
|
|
39
|
+
| "개발자 월급 현실 폭로" | 기대 vs 실제 연봉 갭 |
|
|
40
|
+
| "운동 유튜버들이 절대 안 말하는 것" | 업계 비밀 폭로 프레임 |
|
|
41
|
+
| "20대에 집 사는 게 정말 답인가" | 세대 갈등 자극 |
|
|
42
|
+
| "직장인이 부업하면 안 되는 이유" | 역발상 |
|
|
43
|
+
| "한국 스타트업이 망하는 진짜 이유" | 내부자 폭로 |
|
|
44
|
+
|
|
45
|
+
### 2. 공감형 (Relatable) — 저장+공유 최고
|
|
46
|
+
"내 얘기다" 싶게 만드는 콘텐츠.
|
|
47
|
+
|
|
48
|
+
| 예시 주제 | 핵심 앵글 |
|
|
49
|
+
|-----------|-----------|
|
|
50
|
+
| "개발자라면 공감 100%" | 직군 공감 밈 |
|
|
51
|
+
| "월요일 아침 출근 전 뇌 상태" | 직장인 공감 |
|
|
52
|
+
| "카페에서 노트북 피는 사람 유형" | 유형 분류 |
|
|
53
|
+
| "운동 3개월 전후 마인드셋 변화" | 성장 공감 |
|
|
54
|
+
| "AI 쓰기 전 vs 후 업무량" | 도구 도입 전후 |
|
|
55
|
+
|
|
56
|
+
### 3. 정보형 (Educational) — 저장 최고, 팔로우 증가
|
|
57
|
+
"몰랐다, 유용하다" 반응 유도.
|
|
58
|
+
|
|
59
|
+
| 예시 주제 | 핵심 앵글 |
|
|
60
|
+
|-----------|-----------|
|
|
61
|
+
| "Claude AI 프롬프트 꿀팁 5가지" | 즉시 써먹는 팁 |
|
|
62
|
+
| "개발자 연봉 협상 스크립트" | 실전 대화 예시 |
|
|
63
|
+
| "인스타 알고리즘 2026 변화 요약" | 최신 정보 |
|
|
64
|
+
| "노션 템플릿 무료 공유" | 실용 리소스 |
|
|
65
|
+
| "사이드 프로젝트 6개월 수익 공개" | 숫자 공개 |
|
|
66
|
+
|
|
67
|
+
### 4. 스토리형 (Story) — 댓글+팔로우 최고
|
|
68
|
+
여정과 감정이 있는 서사.
|
|
69
|
+
|
|
70
|
+
| 예시 주제 | 핵심 앵글 |
|
|
71
|
+
|-----------|-----------|
|
|
72
|
+
| "비전공자가 개발자 되기까지 1년" | 성장 서사 |
|
|
73
|
+
| "월 300만원 부업 만든 방법" | 숫자+여정 |
|
|
74
|
+
| "회사 그만두고 6개월 후기" | 결단+결과 |
|
|
75
|
+
| "AI 에이전트로 블로그 자동화한 결과" | 실험 후기 |
|
|
76
|
+
| "팔로워 0에서 1만까지 걸린 시간" | 성장 수치 |
|
|
77
|
+
|
|
78
|
+
## 캡션 작성 규칙
|
|
79
|
+
|
|
80
|
+
### 첫 줄이 전부다 (2줄 이상 "더 보기" 전에 후킹)
|
|
81
|
+
- **숫자**: "3가지만 알면 됩니다"
|
|
82
|
+
- **반전**: "잘못 알고 있었습니다"
|
|
83
|
+
- **질문**: "이거 해보셨나요?"
|
|
84
|
+
- **금지**: "이것만은 하지 마세요"
|
|
85
|
+
- **공감**: "저만 이런 거 아니죠?"
|
|
86
|
+
|
|
87
|
+
### 구조 (300~500자 최적)
|
|
88
|
+
```
|
|
89
|
+
[첫 줄 후킹 — 1문장]
|
|
90
|
+
|
|
91
|
+
[본론 — 3~5줄. 줄바꿈 많이. 읽기 쉽게.]
|
|
92
|
+
|
|
93
|
+
[행동 유도 — 저장/댓글/팔로우 중 하나만]
|
|
94
|
+
|
|
95
|
+
[해시태그 — 줄바꿈 후 별도로]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 행동 유도 (CTA) 예시
|
|
99
|
+
- 저장: "나중에 써먹으려면 저장해 두세요 🔖"
|
|
100
|
+
- 댓글: "여러분은 어떻게 생각하세요? 댓글로 알려주세요 👇"
|
|
101
|
+
- 팔로우: "이런 내용 더 보려면 팔로우 ✅"
|
|
102
|
+
- 공유: "이거 꼭 필요한 친구한테 태그해 주세요 🏷️"
|
|
103
|
+
|
|
104
|
+
### 해시태그 전략 (20~25개)
|
|
105
|
+
- 대형 (100만+): 3~5개 (노출용)
|
|
106
|
+
- 중형 (10만~100만): 10~12개 (타겟용)
|
|
107
|
+
- 소형 (1만~10만): 5~7개 (경쟁 적음)
|
|
108
|
+
- 브랜드/니치: 2~3개 (커뮤니티용)
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
#개발자 #프로그래밍 #코딩 (대형)
|
|
112
|
+
#개발자일상 #개발자성장 #주니어개발자 (중형)
|
|
113
|
+
#사이드프로젝트 #AI자동화 #viruagent (소형/니치)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 이미지 선택 기준
|
|
117
|
+
|
|
118
|
+
| 유형 | 효과 | 권장 상황 |
|
|
119
|
+
|------|------|-----------|
|
|
120
|
+
| 텍스트 카드 | 저장률↑ | 정보형, 팁 |
|
|
121
|
+
| 비포/애프터 | 댓글↑ | 변화, 성장 |
|
|
122
|
+
| 밈/유머 | 공유↑ | 공감형 |
|
|
123
|
+
| 실제 화면캡처 | 신뢰↑ | 후기, 결과 공개 |
|
|
124
|
+
| 얼굴 포함 | 팔로우↑ | 스토리형 |
|
|
125
|
+
|
|
126
|
+
`--related-image-keywords`에 주제 관련 영어 키워드 사용 권장 (Unsplash 검색):
|
|
127
|
+
- 개발: `developer laptop coding`
|
|
128
|
+
- AI: `artificial intelligence technology`
|
|
129
|
+
- 성장: `growth success motivation`
|
|
130
|
+
- 일상: `coffee work lifestyle`
|
|
131
|
+
|
|
132
|
+
## 발행 예시
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# 정보형: Claude 팁 카드
|
|
136
|
+
npx viruagent-cli publish --provider insta \
|
|
137
|
+
--related-image-keywords "AI technology laptop" \
|
|
138
|
+
--content "Claude AI 쓰면서 바뀐 것들 🤖
|
|
139
|
+
|
|
140
|
+
프롬프트 하나로 블로그 글을 자동 발행합니다.
|
|
141
|
+
코드 리뷰도 10초면 끝납니다.
|
|
142
|
+
기획안도 5분 만에 초안이 나옵니다.
|
|
143
|
+
|
|
144
|
+
아직도 혼자 다 하고 있다면
|
|
145
|
+
도구를 바꿀 때입니다.
|
|
146
|
+
|
|
147
|
+
나중에 써먹으려면 저장해 두세요 🔖
|
|
148
|
+
|
|
149
|
+
#AI #Claude #개발자 #업무자동화 #생산성 #개발자일상 #프로그래밍 #코딩 #사이드프로젝트 #AI자동화 #viruagent #개발자성장 #주니어개발자 #테크 #스타트업"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 주의사항
|
|
153
|
+
|
|
154
|
+
- caption 최대 2,200자 (초과 시 잘림)
|
|
155
|
+
- 해시태그 최대 30개 (초과 시 인게이지먼트 감소)
|
|
156
|
+
- 발행 간격: 최소 1~2분 (rate limit)
|
|
157
|
+
- 로그인 안 된 경우: `va-insta-login` 먼저 실행
|
|
@@ -64,6 +64,7 @@ SKILLS_DIR: <viruagent-cli 설치 경로>/skills/
|
|
|
64
64
|
| va-naver-posts | `va-naver-posts/SKILL.md` | 글 목록/읽기 |
|
|
65
65
|
| va-insta | `va-insta/SKILL.md` | Instagram 개요 + 레이트리밋 |
|
|
66
66
|
| va-insta-login | `va-insta-login/SKILL.md` | 로그인 + 챌린지 |
|
|
67
|
+
| va-insta-publish | `va-insta-publish/SKILL.md` | 게시물 발행 + 어그로 전략 |
|
|
67
68
|
| va-insta-like | `va-insta-like/SKILL.md` | 좋아요 |
|
|
68
69
|
| va-insta-comment | `va-insta-comment/SKILL.md` | 댓글 |
|
|
69
70
|
| va-insta-follow | `va-insta-follow/SKILL.md` | 팔로우/언팔 |
|
|
@@ -52,7 +52,7 @@ npx viruagent-cli publish \
|
|
|
52
52
|
- **소제목 간격**: 600~1,000자(약 300~500단어)마다 h3 1개
|
|
53
53
|
- **목차**: 3개 이상 섹션이면 앵커 링크 목차 삽입 (Featured Snippet 진입 확률 상승)
|
|
54
54
|
- **이미지 alt 태그**: 반드시 `핵심키워드 + 설명` 형식으로 작성
|
|
55
|
-
- **내부 링크**:
|
|
55
|
+
- **내부 링크**: 실제 발행된 URL이 제공된 경우에만 삽입. URL 없으면 섹션 전체 생략 (가짜 링크 절대 금지)
|
|
56
56
|
- **E-E-A-T**: 직접 경험 기반 문장, 데이터/출처 인용, 작성자 관점 명시
|
|
57
57
|
|
|
58
58
|
### Tistory HTML 규칙
|
|
@@ -163,13 +163,7 @@ npx viruagent-cli publish \
|
|
|
163
163
|
<p>[예상 못한 결과나 부가적 인사이트 — 이 섹션이 인간미를 만듦]</p>
|
|
164
164
|
<p> </p>
|
|
165
165
|
|
|
166
|
-
<!-- 내부
|
|
167
|
-
<h2>관련 글</h2>
|
|
168
|
-
<ul>
|
|
169
|
-
<li><a href="[URL1]">[관련 글 1 제목]</a></li>
|
|
170
|
-
<li><a href="[URL2]">[관련 글 2 제목]</a></li>
|
|
171
|
-
</ul>
|
|
172
|
-
<p> </p>
|
|
166
|
+
<!-- 내부 링크: 실제 발행된 URL만 사용. 모르면 섹션 전체 생략 -->
|
|
173
167
|
|
|
174
168
|
<p>[핵심 요약 1~2문장] [독자에게 구체적 행동 제안] [공유/댓글 유도]</p>
|
|
175
169
|
```
|
|
@@ -233,13 +227,7 @@ npx viruagent-cli publish \
|
|
|
233
227
|
<p>[구체적 답변]</p>
|
|
234
228
|
<p> </p>
|
|
235
229
|
|
|
236
|
-
<!-- 내부
|
|
237
|
-
<h2>관련 글</h2>
|
|
238
|
-
<ul>
|
|
239
|
-
<li><a href="[URL1]">[관련 글 1]</a></li>
|
|
240
|
-
<li><a href="[URL2]">[관련 글 2]</a></li>
|
|
241
|
-
</ul>
|
|
242
|
-
<p> </p>
|
|
230
|
+
<!-- 내부 링크: 실제 발행된 URL만 사용. 모르면 섹션 전체 생략 -->
|
|
243
231
|
|
|
244
232
|
<p>[완료 축하 + 다음 단계 제안]</p>
|
|
245
233
|
```
|
|
@@ -294,13 +282,7 @@ npx viruagent-cli publish \
|
|
|
294
282
|
<p>[이유]</p>
|
|
295
283
|
<p> </p>
|
|
296
284
|
|
|
297
|
-
<!-- 내부
|
|
298
|
-
<h2>관련 글</h2>
|
|
299
|
-
<ul>
|
|
300
|
-
<li><a href="[URL1]">[관련 글 1]</a></li>
|
|
301
|
-
<li><a href="[URL2]">[관련 글 2]</a></li>
|
|
302
|
-
</ul>
|
|
303
|
-
<p> </p>
|
|
285
|
+
<!-- 내부 링크: 실제 발행된 URL만 사용. 모르면 섹션 전체 생략 -->
|
|
304
286
|
|
|
305
287
|
<p>[내 개인 픽 + 이유] [댓글 유도 — "여러분이 써본 것 중 좋았던 건?"]</p>
|
|
306
288
|
```
|
|
@@ -360,13 +342,7 @@ npx viruagent-cli publish \
|
|
|
360
342
|
<p>[맞지 않는 상황도 언급 — 신뢰도 상승]</p>
|
|
361
343
|
<p> </p>
|
|
362
344
|
|
|
363
|
-
<!-- 내부
|
|
364
|
-
<h2>관련 글</h2>
|
|
365
|
-
<ul>
|
|
366
|
-
<li><a href="[URL1]">[관련 글 1]</a></li>
|
|
367
|
-
<li><a href="[URL2]">[관련 글 2]</a></li>
|
|
368
|
-
</ul>
|
|
369
|
-
<p> </p>
|
|
345
|
+
<!-- 내부 링크: 실제 발행된 URL만 사용. 모르면 섹션 전체 생략 -->
|
|
370
346
|
|
|
371
347
|
<p>[최종 점수나 별점 + 총평 1~2문장]</p>
|
|
372
348
|
```
|
|
@@ -423,13 +399,7 @@ npx viruagent-cli publish \
|
|
|
423
399
|
</ul>
|
|
424
400
|
<p> </p>
|
|
425
401
|
|
|
426
|
-
<!-- 내부
|
|
427
|
-
<h2>관련 글</h2>
|
|
428
|
-
<ul>
|
|
429
|
-
<li><a href="[URL1]">[관련 글 1]</a></li>
|
|
430
|
-
<li><a href="[URL2]">[관련 글 2]</a></li>
|
|
431
|
-
</ul>
|
|
432
|
-
<p> </p>
|
|
402
|
+
<!-- 내부 링크: 실제 발행된 URL만 사용. 모르면 섹션 전체 생략 -->
|
|
433
403
|
|
|
434
404
|
<p>[추가 궁금한 점은 댓글로 — 독자 참여 유도]</p>
|
|
435
405
|
```
|
|
@@ -12,7 +12,7 @@ const {
|
|
|
12
12
|
} = require('./utils');
|
|
13
13
|
const { normalizeThumbnailForPublish } = require('./imageNormalization');
|
|
14
14
|
const { enrichContentWithUploadedImages, resolveMandatoryThumbnail } = require('./imageEnrichment');
|
|
15
|
-
const { createWithProviderSession } = require('./session');
|
|
15
|
+
const { createWithProviderSession, checkAndIncrementRateLimit, getRateLimitStatus } = require('./session');
|
|
16
16
|
const { createAskForAuthentication } = require('./auth');
|
|
17
17
|
|
|
18
18
|
const createTistoryProvider = ({ sessionPath, account }) => {
|
|
@@ -95,6 +95,7 @@ const createTistoryProvider = ({ sessionPath, account }) => {
|
|
|
95
95
|
|
|
96
96
|
async publish(payload) {
|
|
97
97
|
return withProviderSession(async () => {
|
|
98
|
+
checkAndIncrementRateLimit(sessionPath, 'publish');
|
|
98
99
|
const title = payload.title || 'Untitled';
|
|
99
100
|
const rawContent = payload.content || '';
|
|
100
101
|
const visibility = mapVisibility(payload.visibility);
|
|
@@ -528,6 +529,14 @@ const createTistoryProvider = ({ sessionPath, account }) => {
|
|
|
528
529
|
});
|
|
529
530
|
},
|
|
530
531
|
|
|
532
|
+
rateLimitStatus() {
|
|
533
|
+
return {
|
|
534
|
+
provider: 'tistory',
|
|
535
|
+
mode: 'rateLimitStatus',
|
|
536
|
+
...getRateLimitStatus(sessionPath),
|
|
537
|
+
};
|
|
538
|
+
},
|
|
539
|
+
|
|
531
540
|
async logout() {
|
|
532
541
|
clearProviderMeta('tistory', account);
|
|
533
542
|
return {
|
|
@@ -5,6 +5,46 @@ const { sleep, readCredentialsFromEnv, parseSessionError, buildLoginErrorMessage
|
|
|
5
5
|
const { clickKakaoAccountContinue } = require('./browserHelpers');
|
|
6
6
|
const { extractAllCookies, filterCookies, cookiesToSessionFormat } = require('../chromeManager');
|
|
7
7
|
|
|
8
|
+
// ── Rate Limit (일일 발행 제한) ──
|
|
9
|
+
const DAILY_LIMIT = { publish: 15 };
|
|
10
|
+
|
|
11
|
+
const readSessionFile = (sessionPath) => {
|
|
12
|
+
if (!fs.existsSync(sessionPath)) return null;
|
|
13
|
+
try { return JSON.parse(fs.readFileSync(sessionPath, 'utf-8')); } catch { return null; }
|
|
14
|
+
};
|
|
15
|
+
const writeSessionFile = (sessionPath, data) => {
|
|
16
|
+
fs.mkdirSync(path.dirname(sessionPath), { recursive: true });
|
|
17
|
+
fs.writeFileSync(sessionPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const checkAndIncrementRateLimit = (sessionPath, type) => {
|
|
21
|
+
const raw = readSessionFile(sessionPath) || {};
|
|
22
|
+
if (!raw.rateLimits) raw.rateLimits = {};
|
|
23
|
+
const c = raw.rateLimits[type] || { daily: 0, dayStart: Date.now() };
|
|
24
|
+
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
if (now - c.dayStart > 86400000) { c.daily = 0; c.dayStart = now; }
|
|
27
|
+
|
|
28
|
+
const dailyMax = DAILY_LIMIT[type];
|
|
29
|
+
if (dailyMax && c.daily >= dailyMax) {
|
|
30
|
+
throw new Error(`daily_limit: ${type} exceeded daily limit of ${dailyMax}. Try again tomorrow.`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
c.daily++;
|
|
34
|
+
raw.rateLimits[type] = { ...c, savedAt: new Date().toISOString() };
|
|
35
|
+
writeSessionFile(sessionPath, raw);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const getRateLimitStatus = (sessionPath) => {
|
|
39
|
+
const raw = readSessionFile(sessionPath) || {};
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const [type, max] of Object.entries(DAILY_LIMIT)) {
|
|
42
|
+
const c = raw.rateLimits?.[type] || { daily: 0 };
|
|
43
|
+
result[type] = { daily: `${c.daily}/${max}` };
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
|
|
8
48
|
const isLoggedInByCookies = async (context, page) => {
|
|
9
49
|
try {
|
|
10
50
|
// Use CDP to get all cookies including httpOnly (TSSESSION)
|
|
@@ -114,4 +154,6 @@ module.exports = {
|
|
|
114
154
|
waitForLoginFinish,
|
|
115
155
|
persistTistorySession,
|
|
116
156
|
createWithProviderSession,
|
|
157
|
+
checkAndIncrementRateLimit,
|
|
158
|
+
getRateLimitStatus,
|
|
117
159
|
};
|