hashnode

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Call the Hashnode Public GraphQL API with
curl + jq
. The user's Personal Access Token is in
$HASHNODE_TOKEN
. Single endpoint:
https://gql.hashnode.com
(always
POST
a JSON body
{query, variables}
).
Every request needs these headers:
Authorization: $HASHNODE_TOKEN      # raw token — NO "Bearer " prefix
Content-Type: application/json
GraphQL always returns HTTP
200
; real failures live in the JSON
errors
array — always inspect it and show it verbatim. An
errors
entry mentioning
Unauthorized
/
not authenticated
means the token is invalid → the user must re-connect the Hashnode connector.
Helper — send a query/mutation (
$1
= query string,
$2
= variables JSON):
bash
gql() {
  jq -n --arg q "$1" --argjson v "${2:-null}" '{query:$q, variables:$v}' \
  | curl -sS -X POST https://gql.hashnode.com \
      -H "Authorization: $HASHNODE_TOKEN" \
      -H "Content-Type: application/json" \
      -d @-
}
使用
curl + jq
调用Hashnode Public GraphQL API。用户的个人访问令牌存储在
$HASHNODE_TOKEN
中。唯一的端点是:
https://gql.hashnode.com
(始终以
POST
方式发送JSON请求体
{query, variables}
)。
每个请求都需要以下请求头:
Authorization: $HASHNODE_TOKEN      # raw token — NO "Bearer " prefix
Content-Type: application/json
GraphQL始终返回HTTP
200
状态码;真正的错误信息存在于JSON的
errors
数组中——务必检查该数组并原样展示。如果
errors
条目提到
Unauthorized
/
not authenticated
,说明令牌无效→用户必须重新连接Hashnode连接器。
辅助函数——发送查询/变更请求(
$1
= 查询字符串,
$2
= 变量JSON):
bash
gql() {
  jq -n --arg q "$1" --argjson v "${2:-null}" '{query:$q, variables:$v}' \
  | curl -sS -X POST https://gql.hashnode.com \
      -H "Authorization: $HASHNODE_TOKEN" \
      -H "Content-Type: application/json" \
      -d @-
}

Always start by confirming the token and finding the publication id

始终先确认令牌并获取出版物ID

publishPost
/
createDraft
need a
publicationId
. Fetch the account and its publications first (a user can have several blogs):
bash
gql 'query { me { id username publications(first: 10) { edges { node { id title url } } } } }' \
  | jq '{me: .data.me.username, publications: [.data.me.publications.edges[].node | {id, title, url}], errors: .errors}'
Pick the target blog's
id
(that is the
publicationId
). If the user has exactly one publication, use it; otherwise ask which blog to post to.
publishPost
/
createDraft
需要
publicationId
。先获取账户及其出版物信息(一个用户可以拥有多个博客):
bash
gql 'query { me { id username publications(first: 10) { edges { node { id title url } } } } }' \
  | jq '{me: .data.me.username, publications: [.data.me.publications.edges[].node | {id, title, url}], errors: .errors}'
选择目标博客的
id
(即
publicationId
)。如果用户只有一个出版物,则直接使用;否则询问用户要发布到哪个博客。

Publish a post

发布文章

Confirm with the user before publishing publicly. If they are not sure, save a draft first (see below).
tags
is required — supply 1–5 tags, each as
{name, slug}
(slug lowercase, no spaces).
bash
PUB_ID="PUBLICATION_ID"          # from the me query above
TITLE="My title"
BODY_MD="$(cat article.md)"      # full Markdown body

VARS=$(jq -n --arg p "$PUB_ID" --arg t "$TITLE" --arg b "$BODY_MD" '{
  input: {
    publicationId: $p,
    title: $t,
    contentMarkdown: $b,
    tags: [{name:"AI", slug:"ai"}, {name:"Web Development", slug:"web-development"}]
  }
}')

gql 'mutation PublishPost($input: PublishPostInput!) {
  publishPost(input: $input) { post { id slug url title } }
}' "$VARS" | jq '{post: .data.publishPost.post, errors: .errors}'
Useful optional
input
fields:
  • subtitle
    — post subtitle.
  • slug
    — custom URL slug (otherwise derived from the title).
  • canonicalUrl
    /
    originalArticleURL
    — when cross-posting, point SEO back to the original source. Set this whenever the same article also lives elsewhere.
  • coverImageOptions: { coverImageURL: "https://..." }
    — cover image.
  • publishedAt
    — ISO-8601 timestamp to backdate;
    disableComments
    etc. live under
    settings
    .
**发布公开文章前请与用户确认。**如果用户不确定,先保存为草稿(见下文)。
tags
是必填项——提供1–5个标签,每个标签格式为
{name, slug}
(slug为小写,无空格)。
bash
PUB_ID="PUBLICATION_ID"          # 来自上述me查询
TITLE="My title"
BODY_MD="$(cat article.md)"      # 完整Markdown内容

VARS=$(jq -n --arg p "$PUB_ID" --arg t "$TITLE" --arg b "$BODY_MD" '{
  input: {
    publicationId: $p,
    title: $t,
    contentMarkdown: $b,
    tags: [{name:"AI", slug:"ai"}, {name:"Web Development", slug:"web-development"}]
  }
}')

gql 'mutation PublishPost($input: PublishPostInput!) {
  publishPost(input: $input) { post { id slug url title } }
}' "$VARS" | jq '{post: .data.publishPost.post, errors: .errors}'
实用的可选
input
字段:
  • subtitle
    — 文章副标题。
  • slug
    — 自定义URL路径(否则从标题自动生成)。
  • canonicalUrl
    /
    originalArticleURL
    — 交叉发布时,将SEO权重指向原始来源。只要同一文章还发布在其他地方,就设置该字段。
  • coverImageOptions: { coverImageURL: "https://..." }
    — 封面图片。
  • publishedAt
    — ISO-8601时间戳用于回溯发布时间;
    disableComments
    等选项位于
    settings
    字段下。

Save a draft (safe, non-public)

保存草稿(安全,非公开)

Use this when the user has not explicitly approved public publishing:
bash
VARS=$(jq -n --arg p "$PUB_ID" --arg t "$TITLE" --arg b "$BODY_MD" '{
  input: { publicationId: $p, title: $t, contentMarkdown: $b }
}')

gql 'mutation CreateDraft($input: CreateDraftInput!) {
  createDraft(input: $input) { draft { id slug title } }
}' "$VARS" | jq '{draft: .data.createDraft.draft, errors: .errors}'
当用户未明确批准公开发布时使用此方法:
bash
VARS=$(jq -n --arg p "$PUB_ID" --arg t "$TITLE" --arg b "$BODY_MD" '{
  input: { publicationId: $p, title: $t, contentMarkdown: $b }
}')

gql 'mutation CreateDraft($input: CreateDraftInput!) {
  createDraft(input: $input) { draft { id slug title } }
}' "$VARS" | jq '{draft: .data.createDraft.draft, errors: .errors}'

Update a published post

更新已发布的文章

updatePost
needs the post
id
(from
publishPost
, or the list query below):
bash
VARS=$(jq -n --arg id "POST_ID" --arg b "$(cat article.md)" '{
  input: { id: $id, contentMarkdown: $b }
}')

gql 'mutation UpdatePost($input: UpdatePostInput!) {
  updatePost(input: $input) { post { id url title } }
}' "$VARS" | jq '{post: .data.updatePost.post, errors: .errors}'
updatePost
需要文章
id
(来自
publishPost
或下文的列表查询):
bash
VARS=$(jq -n --arg id "POST_ID" --arg b "$(cat article.md)" '{
  input: { id: $id, contentMarkdown: $b }
}')

gql 'mutation UpdatePost($input: UpdatePostInput!) {
  updatePost(input: $input) { post { id url title } }
}' "$VARS" | jq '{post: .data.updatePost.post, errors: .errors}'

List / inspect my posts

列出/查看我的文章

bash
gql 'query { me { posts(pageSize: 20, page: 1) { nodes { id title slug url views reactionCount responseCount publishedAt } } } }' \
  | jq '[.data.me.posts.nodes[] | {id, title, url, views, reactions: .reactionCount, responses: .responseCount}]'
Single post with full content + stats:
bash
gql 'query GetPost($id: ObjectId!) { post(id: $id) { title url views reactionCount responseCount content { markdown } } }' \
  "$(jq -n --arg id "POST_ID" '{id:$id}')" | jq '.data.post | {title, url, views, reactions: .reactionCount}'
bash
gql 'query { me { posts(pageSize: 20, page: 1) { nodes { id title slug url views reactionCount responseCount publishedAt } } } }' \
  | jq '[.data.me.posts.nodes[] | {id, title, url, views, reactions: .reactionCount, responses: .responseCount}]'
查看单篇文章的完整内容+统计数据:
bash
gql 'query GetPost($id: ObjectId!) { post(id: $id) { title url views reactionCount responseCount content { markdown } } }' \
  "$(jq -n --arg id "POST_ID" '{id:$id}')" | jq '.data.post | {title, url, views, reactions: .reactionCount}'

Gotchas

注意事项

  • No
    Bearer
    prefix
    — the
    Authorization
    header carries the raw token.
  • publicationId
    is mandatory
    for publishing/drafting — never guess it, read it from the
    me
    query.
  • tags
    is required on
    publishPost
    — supply at least one
    {name, slug}
    ; slug must be lowercase with hyphens (e.g.
    machine-learning
    ).
  • errors
    on HTTP 200
    — GraphQL reports failures in the
    errors
    array, not via status codes; always surface them.
  • Idempotency — re-running
    publishPost
    creates a new post each time; to change an existing one use
    updatePost
    with its
    id
    .
  • 不要加
    Bearer
    前缀
    Authorization
    请求头携带原始令牌即可。
  • publicationId
    是发布/保存草稿的必填项
    — 切勿猜测,务必从
    me
    查询中获取。
  • publishPost
    必须包含
    tags
    — 至少提供一个
    {name, slug}
    ;slug必须是小写并使用连字符(例如
    machine-learning
    )。
  • HTTP 200状态码下仍可能存在
    errors
    — GraphQL通过
    errors
    数组报告错误,而非状态码;务必展示这些错误信息。
  • 幂等性 — 重复运行
    publishPost
    会每次创建一篇文章;要修改现有文章,请使用
    updatePost
    并传入文章
    id

Record the output

记录输出结果

After you successfully publish and obtain the live result URL, call the built-in
publish_artifact
tool ONCE so the user can track this deliverable in My Outputs:
publish_artifact(kind="article", channel="hashnode", title="<title>", url="<the REAL returned URL>", status="delivered")
Use the real returned URL — never fabricate one. Call it once per published item, only after delivery is confirmed; skip it (or use
status="failed"
) if publishing failed. See
_shared/artifacts.md
.
成功发布并获取文章的实际URL后,调用内置的
publish_artifact
工具一次,以便用户在我的输出中跟踪此交付成果:
publish_artifact(kind="article", channel="hashnode", title="<title>", url="<the REAL returned URL>", status="delivered")
使用实际返回的URL——切勿伪造。每发布一个项目调用一次,仅在确认交付成功后调用;如果发布失败,则跳过(或使用
status="failed"
)。详情请见
_shared/artifacts.md