| name | tistory-publish |
| description | Automate Tistory blog publishing via OpenClaw Playwright CDP. Supports any post format โ handles TinyMCE editor manipulation, OG card insertion, banner upload, tag registration, category setting, and representative image selection. Includes template presets (mk-review, daum-trends, simple-post). Works around Tistory's isTrusted event filtering. |
| runtime | ["python3","playwright","node (optional, for banner generation)"] |
| credentials | [{"purpose":"Kakao login for Tistory session recovery (used only by scripts/login.sh --cred-file)","required":false,"format":"{\"email\": \"...\", \"password\": \"...\"} or email: ...\\npassword: ...","note":"Path is user-specified via --cred-file or TISTORY_CRED_FILE env var"}] |
Tistory Publish
ํฐ์คํ ๋ฆฌ ๋ธ๋ก๊ทธ ๋ฒ์ฉ ์๋ ๋ฐํ ์คํฌ. ์ด๋ค ํ์์ ๊ธ์ด๋ ์๋ ๋ฐํํ ์ ์์ต๋๋ค.
Tistory Open API ์ข
๋ฃ(2024.02) ์ดํ ์ ์ผํ ์๋ํ ๊ฒฝ๋ก์ธ ๋ธ๋ผ์ฐ์ ์๋ํ๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ ์ ์กฐ๊ฑด
- OpenClaw ๋ธ๋ผ์ฐ์ ์๋น์ค (Chrome CDP, ๊ธฐ๋ณธ port 18800)
- ํฐ์คํ ๋ฆฌ ์นด์นด์ค ๋ก๊ทธ์ธ ์๋ฃ (OpenClaw Chrome์์)
- Python 3 + Playwright (
pip install playwright)
- Node.js 18+ (๋ฐฐ๋ ์์ฑ ์, ์ ํ)
- (์ ํ) ์นด์นด์ค ์๊ฒฉ์ฆ๋ช
ํ์ผ โ ๋ก๊ทธ์ธ ์ธ์
๋ง๋ฃ ์ ๋ณต๊ตฌ์ฉ (
scripts/login.sh --cred-file <๊ฒฝ๋ก>)
- JSON ํ์:
{"email": "...", "password": "..."}
- ๋๋ key-value ํ์:
email: ...\npassword: ...
publish.sh๋ ์๊ฒฉ์ฆ๋ช
์ ์ฝ์ง ์์ (๋ก๊ทธ์ธ์ login.sh์์๋ง ์ฒ๋ฆฌ)
publish-post.sh ์๋ ๋ณต๊ตฌ๋ ํ์ฌ --blog์ --cdp-port๋ฅผ login.sh๋ก ์ ๋ฌํจ
๊ตฌ์กฐ
tistory-publish/
โโโ SKILL.md # ์ด ํ์ผ
โโโ scripts/
โ โโโ tistory-publish.js # ์ฝ์ด โ ์๋ํฐ ์กฐ์ ํจ์ ๋ชจ์
โ โโโ publish.sh # ๋ฒ์ฉ ๋ฐํ ์คํฌ๋ฆฝํธ
โ โโโ seo_check.py # ๋ฐํ ์ SEO ์ ์ ๊ฒ์ฌ
โ โโโ login.sh # ์นด์นด์ค ๋ก๊ทธ์ธ ์ธ์
๋ณต๊ตฌ
โโโ templates/
โโโ simple-post/ # ์์: ๋จ์ ๊ธ ๋ฐํ
โโโ RUNBOOK.md
๋น ๋ฅธ ์์
bash scripts/publish.sh \
--title "๊ธ ์ ๋ชฉ" \
--body-file body.html \
--category "์นดํ
๊ณ ๋ฆฌ๋ช
" \
--blog "your-blog.tistory.com"
bash scripts/publish.sh \
--template mk-review \
--article-title "๊ธฐ์ฌ ์ ๋ชฉ" \
--body-file body.html \
--banner /tmp/banner.jpg \
--tags "๋งค๊ฒฝ,๊ฒฝ์ ๋ด์ค" \
--blog "anthropic.tistory.com" \
--category "์ฌํ
ํฌ ์ด์ผ๊ธฐ/๊ฒฝ์ ์ ๋ฌธ ๋ฆฌ๋ทฐ" \
--cdp-port 18800
bash scripts/publish.sh \
--title "๊ธ ์ ๋ชฉ" \
--body-file body.html \
--category "์นดํ
๊ณ ๋ฆฌ๋ช
" \
--banner /tmp/banner.jpg \
--tags "ํ๊ทธ1,ํ๊ทธ2,ํ๊ทธ3" \
--private
๋ฐํ ์คํฌ๋ฆฝํธ ์ต์
(publish.sh)
| ์ต์
| ํ์ | ์ค๋ช
|
|---|
--title | โ
| ๊ธ ์ ๋ชฉ |
--body-file | โ
| ๋ณธ๋ฌธ HTML ํ์ผ ๊ฒฝ๋ก |
--category | โ
| ์นดํ
๊ณ ๋ฆฌ ์ด๋ฆ (์๋ํฐ์ ํ์๋๋ ์ด๋ฆ ๊ทธ๋๋ก) |
--template | | ํ
ํ๋ฆฟ preset (mk-review, daum-trends, simple-post) |
--article-title | | mk-review์ฉ ๊ธฐ์ฌ ์ ๋ชฉ (์๋ ๋ ์ง ์ ๋์ฌ) |
--tags | | ์ผํ ๊ตฌ๋ถ ํ๊ทธ ๋ชฉ๋ก |
--banner | | ๋ฐฐ๋ ์ด๋ฏธ์ง ํ์ผ ๊ฒฝ๋ก |
--banner-alt | | ๋ฐฐ๋ ์ด๋ฏธ์ง alt ํ
์คํธ (๊ธฐ๋ณธ: ์ ๋ชฉ โ ๋น alt๋ก ์
๋ก๋๋์ง ์๋๋ก) |
--blog | | ๋ธ๋ก๊ทธ ๋๋ฉ์ธ (๊ธฐ๋ณธ: tistory.com ์ฒซ ๋ฒ์งธ ๋ธ๋ก๊ทธ) |
--cdp-port | | OpenClaw Chrome CDP ํฌํธ (๊ธฐ๋ณธ: TISTORY_CDP_PORT ๋๋ ์คํฌ๋ฆฝํธ ๊ธฐ๋ณธ๊ฐ) |
--helper | | tistory-publish.js ๊ฒฝ๋ก (๊ธฐ๋ณธ: scripts/ ๋ด) |
--private | | ๋น๊ณต๊ฐ ๋ฐํ |
--seo-check | | ๋ฐํ ์ SEO ๊ฒ์ฌ ๋ชจ๋: off(๊ธฐ๋ณธ)/warn/strict. strict๋ error ๋ฐ๊ฒฌ ์ ๋ฐํ ์ค๋จ |
--seo-keyword | | SEO ํต์ฌ ํค์๋ (๊ธฐ๋ณธ: ์ ๋ชฉ ์ฒซ ๋จ์ด). ์ ๋ชฉ/๋์
๋ถ/์์ ๋ชฉ ๋ด ํค์๋ ๋ฐฐ์น ๊ฒ์ฌ์ ์ฌ์ฉ |
--seo-min-body-chars | 1000 | SEO ๋ณธ๋ฌธ ์ต์ ๋
ธ์ถ ๊ธ์์. strict์์๋ ๋ฏธ๋ฌ ์ ๋ฐํ ์ค๋จ |
ํ
ํ๋ฆฟ preset
| ์ด๋ฆ | ์นดํ
๊ณ ๋ฆฌ | ๋ธ๋ก๊ทธ | ์ ๋ชฉ ํ์ | ๋ฐฐ๋ |
|---|
mk-review | ์ฌํ
ํฌ ์ด์ผ๊ธฐ/๊ฒฝ์ ์ ๋ฌธ ๋ฆฌ๋ทฐ | anthropic.tistory.com | [๋งค๊ฒฝ] YYYY.MM.DD(์์ผ) - ๊ธฐ์ฌ ์ ๋ชฉ | ํ์ |
simple-post | (์ง์ ์ง์ ) | (์ง์ ์ง์ ) | (์ง์ ์ง์ ) | ์ ํ |
์์ ๋ง์ preset์ ์ถ๊ฐํ๋ ค๋ฉด templates/ ์๋์ ํด๋๋ฅผ ๋ง๋ค๊ณ publish.sh --template <์ด๋ฆ> ์ผ๋ก ์ฌ์ฉํ์ธ์.
Daum Trends preset
Use --template daum-trends for Daum ์ค์๊ฐ ํธ๋ ๋ posts. This preset supplies content defaults such as tags only. The caller must still pass --blog and --category explicitly.
ALLOW_DIRECT_TISTORY_PUBLISH=1 bash scripts/publish-post.sh \
--template daum-trends \
--title "Daum ์ค์๊ฐ ํธ๋ ๋ ..." \
--body-file body.html \
--blog "$DAUM_TRENDS_TISTORY_BLOG" \
--category "$DAUM_TRENDS_TISTORY_CATEGORY" \
--cdp-port "$TISTORY_CDP_PORT"
์ธ์
๋ณต๊ตฌ๋ฅผ ์ง์ ์คํํ ๋๋ ๊ฐ์ ํฌํธ๋ฅผ ๋ช
์ํ๋ค:
bash scripts/login.sh \
--cred-file "$TISTORY_LOGIN_CRED_FILE" \
--blog "$DAUM_TRENDS_TISTORY_BLOG" \
--cdp-port "$TISTORY_CDP_PORT"
์๋ ์ฒ๋ฆฌ ํญ๋ชฉ
์คํฌ๋ฆฝํธ๊ฐ ์์๋๋ก ์ฒ๋ฆฌ:
- SEO ๊ฒ์ฌ (
--seo-check warn|strict ์ง์ ์, ๋ฐํ ์ ์ ์ ๊ฒ์ฌ)
- ์ ๊ธ ํ์ด์ง ์ด๊ธฐ
- JS ํฌํผ ํจ์ ์ฃผ์
- ์นดํ
๊ณ ๋ฆฌ ์ ํ (ARIA combobox โ Playwright click)
- ์ ๋ชฉ ์
๋ ฅ (base64 ๋์ฝ๋ฉ์ผ๋ก ํ๊ธ ์ฒ๋ฆฌ)
- ๋ณธ๋ฌธ HTML ์ฝ์
- ๋ฐฐ๋ ์ด๋ฏธ์ง ์
๋ก๋ (์ฒจ๋ถโ์ฌ์ง ๋ฉ๋ด โ file input) + alt ํ
์คํธ ์ค์
- OG ์นด๋ ์์ฑ (placeholder URL โ Enter ํค โ ์นด๋ ๋ ๋๋ง)
- ๋ํ์ด๋ฏธ์ง ์ค์
- ํ๊ทธ ๋ฑ๋ก
- ๋ฐํ (๊ณต๊ฐ/๋น๊ณต๊ฐ)
๋ณธ๋ฌธ HTML ์์ฑ ๊ท์น
<p data-ke-size="size16"> ํ๊ทธ ์ฌ์ฉ
- ๋จ๋ฝ = ์ฌ๋ฌ ๋ฌธ์ฅ ๋ฌถ์ (
<p> ํ๋์ 2~4๋ฌธ์ฅ)
- OG ์นด๋ ์์น:
<p data-og-placeholder="URL">​</p>
- ๊ตฌ๋ถ์ :
<hr contenteditable="false" data-ke-type="horizontalRule" data-ke-style="style1">
SEO ๊ท์น (๊ฒ์ ๋
ธ์ถ์ฉ โ --seo-check๊ฐ ๊ฒ์ฌํ๋ ํญ๋ชฉ)
Tistory๋ ๋ณธ๋ฌธ ์์๋ถ๋ฅผ meta description / og:description์ผ๋ก ์ฌ์ฉํ๋ฏ๋ก ๋ณธ๋ฌธ ๊ตฌ์กฐ๊ฐ ๊ณง ๊ฒ์ ์ค๋ํซ์ด๋ค.
- ๋์
๋ถ ํ์: ์ฒซ
<h2> ์ด์ ์ 80~150์ ์์ฝ ๋ฌธ๋จ 1๊ฐ. ํต์ฌ ํค์๋๋ฅผ ์ฒซ 150์ ์์ ๋ฐฐ์น (๊ฒ์ ๊ฒฐ๊ณผ ์์ฝ๋ฌธ์ผ๋ก ๋
ธ์ถ๋จ)
- ์ ๋ชฉ: ํต์ฌ ํค์๋๋ฅผ ์์ชฝ(20์ ์ด๋ด)์ ๋ฐฐ์น, ์ ์ฒด 60์ ์ดํ (SERP์์ ํ๊ธ 30~35์๋ง ๋
ธ์ถ)
- ์์ ๋ชฉ: h2 2๊ฐ ์ด์, ์ต์ 1๊ฐ h2/h3์ ํต์ฌ ํค์๋ ํฌํจ
- ์ด๋ฏธ์ง alt: ๋ณธ๋ฌธ ๋ด ๋ชจ๋
<img>์ alt ํ์, ๋ฐฐ๋๋ --banner-alt๋ก ์ง์
- ๋ด๋ถ ๋งํฌ: ๊ฐ์ ๋ธ๋ก๊ทธ์ ๊ด๋ จ ๊ธ 2~3๊ฐ ๋งํฌ (ํฌ๋กค๋ง ๊ฒฝ๋ก + ์ฒด๋ฅ์๊ฐ)
- ์ธ๋ถ ์ถ์ฒ: ์๋ฌธ ๋งํฌ ๋๋ OG ์นด๋ 1๊ฐ ์ด์
- ๋ณธ๋ฌธ ๋ถ๋: ๋
ธ์ถ ํ
์คํธ 1,000์ ์ด์ (๋จ์ ๋ฐ์ท๋ ์ ํ์ง ์ฝํ
์ธ ๋ก ๋ถ๋ฅ๋ ์ ์์)
- ํ๊ทธ: 5~10๊ฐ, ์ค๋ณต ๊ธ์ง, ๋ฒ์ฉ ํค์๋ + ๋กฑํ
์ผ ํค์๋ ํผํฉ
๋จ๋
์คํ:
python3 scripts/seo_check.py \
--title "๊ธ ์ ๋ชฉ" --body-file body.html \
--tags "ํ๊ทธ1,ํ๊ทธ2" --keyword "ํต์ฌํค์๋" \
--blog "your-blog.tistory.com" --mode strict --min-body-chars 1000
ํ
ํ๋ฆฟ ์ถ๊ฐํ๊ธฐ
templates/ ๋๋ ํ ๋ฆฌ์ ์ ํด๋๋ฅผ ๋ง๋ค์ด ์์ ๋ง์ ์ํฌํ๋ก์ฐ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค:
templates/my-template/
โโโ RUNBOOK.md # ๋ฐํ ์์
โโโ TEMPLATE.md # ์๊ณ ์์ฑ ํ
ํ๋ฆฟ
โโโ banner.js # ๋ฐฐ๋ ์์ฑ ์คํฌ๋ฆฝํธ (์ ํ)
์ฃผ์ JS ํจ์ (tistory-publish.js)
์ฝํ
์ธ
insertContent(html) โ TinyMCE์ HTML ์ฝ์
buildBlogHTML({intro, articles}) โ ๊ตฌ์กฐํ๋ ๋ฐ์ดํฐ โ HTML ๋ณํ
OG ์นด๋
getOGPlaceholders() โ placeholder URL ๋ชฉ๋ก
prepareOGPlaceholder(url) โ placeholder โ URL ํ
์คํธ ๊ต์ฒด
verifyOGCard(url) โ ์นด๋ ๋ ๋๋ง ํ์ธ
๋ฉํ๋ฐ์ดํฐ
setTags(tags[]) โ ํ๊ทธ ๋ฑ๋ก
setRepresentImageFromEditor() โ ๋ํ์ด๋ฏธ์ง ์ค์
setImageAlt(alt, index) โ ์๋ํฐ ๋ด ์ด๋ฏธ์ง alt ํ
์คํธ ์ค์ (SEO)
setImageAltForUploadedImage(alt, filename, previousCount) โ ์
๋ก๋ ์งํ ์ ์ด๋ฏธ์ง alt ํ
์คํธ ์ค์ (๊ธฐ์กด ์ด๋ฏธ์ง ๋ฎ์ด์ฐ๊ธฐ ๋ฐฉ์ง)
๋ฐฐ๋
verifyBannerUpload() โ ์
๋ก๋ ํ์ธ
์๋ ค์ง ์ ์ฝ
isTrusted=false ์ด๋ฒคํธ ๋ฌด์ โ OG/ํ๊ทธ์ ์ฐํ ๋ก์ง ํ์
- ์นดํ
๊ณ ๋ฆฌ๊ฐ ARIA combobox โ Playwright click ํ์
- ๋ํ์ด๋ฏธ์ง ์
๋ ํฐ๊ฐ Tistory ์
๋ฐ์ดํธ๋ง๋ค ๋ณ๊ฒฝ ๊ฐ๋ฅ
๋ณ๊ฒฝ ์ด๋ ฅ
CHANGELOG.md ์ฐธ์กฐ