Skip to content

Repository files navigation

Zhihu On Emacs

在 Emacs 中使用 Markdown/Typst/Org 作为源文件来撰写、存档并发布知乎 回答、文章和想法,并可以指定文章专栏。

Warning

本项目使用知乎非公开的网页接口,知乎可能随时改变接口行为。同时此项目处于开发早期,由于作者仅使用 Firefox,在别的浏览器上的 cookie 读取未经过测试。

依赖

  • Emacs 29.1 或更高版本,并启用 SQLite、libxml 和 GnuTLS 支持
  • plz.el master(0.10-pre;)
  • yaml.el
  • pandoc 2.17 或更高版本
  • 可选:使用 Typst 源文件,或转换本地路径 / data: URL 中的 SVG 时, 需要 PATH 中的 typst;编辑 Typst 源文件可另装 typst-ts-modetypst-mode
  • 可选:如果 Markdown 或 Org 使用 Mermaid 图, @mermaid-js/mermaid-cli 提供的 mmdc

配置

请先在受支持的浏览器中登录知乎。本包支持 Firefox、Chromium、Google Chrome 和 Microsoft Edge;默认读取 Firefox。浏览器及其实际 profile 根目录都需要显式设置给zhihu-cookie-profile-directory。本包不会扫描或猜。

浏览器 zhihu-cookie-profile-directory 的语义 固定读取的相对路径
Firefox 例如 xxxxxxxx.default-release/ cookies.sqlite
Chromium / Chrome / Edge 例如 Default/Profile 1/ Network/Cookies

本包仅支持 GNU/Linux,浏览器版本只支持各项目的最新稳定版。原生 Windows 和 macOS 不受支持;Windows 用户请使用 WSL。

回答/文章没有单独指定转载权限时,使用 zhihu-publish-default-reprint-permission(默认 allowed);回答、文章或 想法没有单独指定评论权限时,使用 zhihu-publish-default-comment-permission(默认 all)。

Markdown 正文标题默认整体下移一级,为知乎页面标题保留 h1。如需保留Markdown 源稿的自然标题层级,可将zhihu-enable-markdown-heading-level-shift 设为 nil, Org 和 Typst 不读取该选项。

如需在文章末尾自动追加 Creative Commons 许可引用,可通过zhihu-article-cc-statement 自定义,这一选项将会适用于所有文章。

工作流

提供问题ID或者URL,写新回答:

M-x zhihu-new-answer

申请创建新的普通知乎专栏:

M-x zhihu-new-column

专栏名称必填且最多 20 个字符,简介可空且最多 1000 个字符。封面是可选的, 本命令不会提示或上传封面。

写新文章或想法无需专用的新建命令:创建 .typ.md.markdown.org 源稿即可。保留空 pin-id 时作为新想法,保留空 article-id 时作为 新文章;文章需要文档标题,想法标题可选。需要指定专栏、话题或其它发布设置时, 再添加相应 metadata。

更新已有知乎文章、回答或想法,或者把已有文件首次发布到知乎:

M-x zhihu-publish

发布过程中,本包会把服务端返回的知乎回答、文章或想法 ID 写回源文件; 其中空 article-idpin-id 表示首次发布,非空值表示更新已有内容。

zhihu-mode 是不占用键位的编辑辅助 minor mode。带有知乎 metadata 的 Markdown、Org 和 Typst 源稿会自动启用;也可以手动运行 M-x zhihu-mode。在 column-id 值槽运行 M-x completion-at-point,候选会显示当前账号可投稿的专栏名称和 ID, 选择后只把实际 ID 写入源稿。topics 的字符串元素也支持同样的 就地补全。

语法

在文章和回答中,数学公式、表格、加粗、斜体、标题层级、超链接、代码块、 task list(以字符形式降级)和脚注均受支持。采用各个源稿格式自带的语法。

文章内章节链接

文章可以用源格式原生的 fragment 链接跳到同一篇文章的标题。必须同时设置 该格式的目录请求,目标在最终 HTML 中必须是 h2h3

Markdown 用 heading attribute 声明稳定 ID:

[跳到结论](#conclusion)

## 结论 {#conclusion}

Org 用 CUSTOM_ID

[[#conclusion][跳到结论]]

** 结论
:PROPERTIES:
:CUSTOM_ID: conclusion
:END:

Typst 用 label:

#link(<conclusion>)[跳到结论]

== 结论 <conclusion>

若已经用 #set heading(numbering: "1.") 给标题编号,也可以写 参见 @conclusion。这是 Typst 的 ref 简写,会自动生成“Section 1.1” 一类引用文字和链接;未编号标题应继续使用 #link

链接卡片

卡片直接写在正文中,必须独占一个段落,显示标题不能为空。链接卡片目前只用于回答和文章;想法发布会明确拒绝它。

Markdown 使用链接 title card

[GitHub](https://github.com/ "card")

Org 使用后端属性修饰紧随其后的普通链接:

#+ATTR_ZHIHU: :type link-card
[[https://github.com/][GitHub]]

Typst 先在文档或模板中定义 card-link。知乎发布使用 HTML 标记,其它 Typst 输出目标则退化为普通链接:

#let card-link(url, body) = if sys.inputs.at(
  "target",
  default: "paged",
) == "html" {
  html.elem(
    "a",
    attrs: (
      href: url,
      "data-zhihu-card": "",
    ),
    body,
  )
} else {
  link(url, body)
}

#card-link("https://github.com/")[GitHub]

@ 知乎用户

在源稿中把光标移到插入位置,然后运行:

M-x zhihu-insert-user-mention

输入搜索词并选择候选后,命令会插入当前源格式的链接。启用 zhihu-mode 的 Markdown 源稿还可以直接在正文键入 @搜索词,再运行 M-x completion-at-point 选择用户;YAML front matter、代码、已有链接和 raw HTML 中不会触发该补全。

话题

直接在 topics 的某个字符串元素内输入关键词,然后运行:

M-x completion-at-point

补全会按当前元素的文字调用知乎话题搜索,保留远端相关性顺序,并在候选中 显示简介和话题 ID;提交后源稿只保留规范的话题名称。三种源格式的写法分别是:

topics:
  - "Emacs"
#+ZHIHU_TOPICS: ["Emacs", "org-mode"]
topics: (
  "Emacs",
  "Typst",
),

把光标放在引号内即可补全,空查询不会请求网络。这是按关键词的话题自动补全; 知乎网页编辑器根据已上传正文生成的“推荐话题”不是本地补全的一部分。

发布文章时,本地列表是完整事实来源:远端缺少的话题会绑定,多出的话题会 解绑;源稿没有 topics 时表示空集合,会清除远端全部话题。

想法也使用 topics,但目前直接编辑 metadata:最多十个,发布时随想法整体 提交。回答不支持该字段。源稿只保存话题名称;发布时再从知乎的名称完全匹配 候选取得对应 ID。

知乎想法

新想法保留一个空 pin-id;发布成功后会把服务端 ID 写入同一字段。已有非空 pin-id 的源稿会更新对应想法。

想法正文最多 2000 个字符,可选标题最多 50 个字符;正文和图片不能同时为空。 想法只承诺基本文字、段落、换行和图片,不具备回答或文章的富文本能力。 知乎不会把可选标题保存为独立字段,发布后会把它呈现为正文开头的 标题 | 前缀。

所有富文本效果都不受支持。

想法的图片和回答/文章不同:知乎把它们保存成正文之外的媒体列表。源稿中的 图片会按出现顺序从 HTML 正文抽出,使用 source=pin 上传,再写入 media.medias。目前最多 18 张,只接受本地路径或 data: URL;HTTP(S) 外链图片会在上传前报错。

脚注(知乎引用)

知乎引用只能保存一段纯文本和一个可选 URL。因此脚注目前必须是单段,最多 包含一个带 host 的 HTTP(S) 链接;强调、粗体、删除线和行内代码会转成纯文本。 多段、多链接、列表、代码块、图片、公式或 raw 内容会在发布前报错,避免静默 丢失信息;嵌套脚注不在支持范围内。纯文字脚注会生成空的引用 URL。Org Cite 不会自动冒充脚注。

分割线

Typst 的普通 line 在 HTML 导出时会被忽略,因此推荐采用类似方式:

#let thematic-break() = if sys.inputs.at(
  "target",
  default: "paged",
) == "html" {
  html.elem("hr")
} else {
  line(length: 100%)
}

#thematic-break()

paged 只是未指定 target 时代表 PDF 等分页输出的默认名称,不是分页符。 zhihu.el 发布时会传入 target=html;实际写正文只需调用 #thematic-break()

元数据示例

Typst:

#metadata((
  question-id: "123456",
)) <zhihu>

新文章:

#metadata((
  article-id: none,
)) <zhihu>

#set document(title: "示例标题")

#outline()

想法:

#metadata((
  pin-id: none,
  topics: ("Emacs",),
)) <zhihu>

banner 是独立的通用文档 metadata,不属于 <zhihu>

#metadata("./images/banner.jpg") <banner>

Markdown

---
title: 示例标题
zhihu:
  question-id: "123456"
---
---
title: 示例文章
banner: "./images/banner.jpg"
toc: true
zhihu:
  article-id:
  topics:
    - "Emacs"
    - "org-mode"
---

想法:

---
title: 可选的想法标题
zhihu:
  pin-id:
  topics:
    - "Emacs"
---

Typst 对应写作 topics: ("Emacs", "org-mode",);单元素 array 也保留尾逗号。

每篇源稿必须且只能包含 question-idarticle-idpin-id 之一,分别 表示回答、文章或想法。类型按字段是否存在判定,而不是按 ID 是否非空判定; 即使 ID 槽仍为空也算存在。

Markdown 的 zhihu: 必须单独占一行,字段写在后续缩进行;不能写成 zhihu: {...} 单行形式。顶层 zhihu 和其中每个已知字段都只能出现一次。

article-idpin-id 是仅有的空值例外:首次发布前保留空槽,发布成功后 自动在原位写入服务端 ID。Markdown 写作空值,Org 保留空关键字,Typst 写作 none。尚未取得的 answer-id 仍应整个省略。

需要加入专栏时,在含 article-id 的文章 metadata 下额外填写 column-idcolumn-id 不能单独标记文章。发布文章后会检查当前专栏,尚未收录时才发起 收录。Typst 也使用相同字段。启用 zhihu-mode 后,可在三种格式的该字段 值槽调用 completion-at-point,按专栏名称选择并写入 ID;候选列表在当前 buffer 中懒加载并缓存,revert 后重新读取。

bannertitle 一样属于通用文档 metadata:Markdown 使用 YAML 顶层 banner,Org 使用 #+BANNER,Typst 使用 #metadata("...") <banner>;它不写进 zhihu mapping 或任何 #+ZHIHU_* 关键字。值是非空的本地图片路径,相对路径以源稿所在目录为基。 知乎文章发布会把它用作题图,字段缺失会清除知乎上的现有封面;回答和想法发布 不会读取或使用它。

目录请求也属于通用文档结构,不属于知乎渠道字段。Markdown 在 YAML front matter 顶层写 toc: true;Typst 使用原生 #outline(),也可用 #outline(depth: 2) 限制深度;Org 使用原生目录指令:

#+TOC: headlines 2

Org 允许省略深度写成 #+TOC: headlines,或指定 1–3 的目录深度。只有文章 发布会读取目录请求,并把它映射为知乎原生文章目录;回答和想法会忽略它。 Typst 中,zhihu.el 只把以 heading(包括 heading.where(...))为 target 的 outline 当作这个开关,并在编译 HTML 前通过临时 Typst wrapper 的 show rule 隐藏该 heading outline,避免与知乎原生目录重复;原稿不会被修改。以 figure 等非 heading 元素为 target 的 outline 不会启用文章目录,也会保留 在发布正文中。没有目录请求,或 Markdown 将 toc 设为 false 时,文章目录 关闭。

以下知乎设置都属于单篇稿件。Typst 和 Markdown 使用表中的 metadata 字段; Org 使用对应的 #+ZHIHU_* 关键字。

设置 Typst / Markdown 字段 可用值 未填写时
创作声明 creation-statement spoilermedical_advicefictional_creationcontain_financeai_creation;想法不支持 无创作声明
内容来源 content-source officialWebsite(官方网站)、newsReport(新闻报道)、TVMedia(电视媒体)、printMedia(纸质媒体);想法不支持 不标注来源
话题 topics 文章至多三个,想法至多十个;回答不支持 文章会清空远端话题;想法不提交话题
转载权限 reprint-permission alloweddisallowedneed_payment;想法不支持 使用转载权限默认值
评论权限 comment-permission allcensorfolloweenobody;想法还支持 follower_n_days 使用评论权限默认值

content-source 对应“内容信息来源”渠道;知乎同一面板中的自行拍摄时间和地点 不是这个标量字段的一部分,目前不写入。

上表中的可选发布设置缺失时使用对应默认值;一旦出现,就必须是非空且类型、 取值有效的值。

Org:

  • #+ZHIHU_QUESTION_ID:问题 ID。
  • #+ZHIHU_ANSWER_ID:回答 ID;首次取得服务端 ID 后才会出现。
  • #+ZHIHU_ARTICLE_ID:文章 ID;新文章可保留空关键字,发布后自动写回。
  • #+ZHIHU_COLUMN_ID:文章要加入的专栏 ID;发布后会检查并收录。
  • #+ZHIHU_PIN_ID:想法 ID;新想法保留空关键字,发布后自动写回。
  • #+BANNER:通用文档题图;不是 #+ZHIHU_* 状态。
  • #+ZHIHU_TOPICS:文章或想法话题名称组成的单行 JSON array,例如 ["Emacs","org-mode"]
  • #+ZHIHU_CREATION_STATEMENT:本篇创作声明。
  • #+ZHIHU_CONTENT_SOURCE:本篇内容的信息来源渠道。
  • #+ZHIHU_REPRINT_PERMISSION:本篇转载权限;未写时使用默认值。
  • #+ZHIHU_COMMENT_PERMISSION:本篇评论权限;未写时使用默认值。

#+BANNER 和每个已知的 #+ZHIHU_* 关键字最多出现一次。只有 #+ZHIHU_ARTICLE_ID / #+ZHIHU_PIN_ID 允许为空;其它字段未设置时应删除 整行。#+ZHIHU_QUESTION_ID#+ZHIHU_ARTICLE_ID#+ZHIHU_PIN_ID 必须且只能出现一个。

致谢

  • zhihu.nvim:发布 payload、浏览器 Cookie 读取和图片上传协议的主要参考实现。
  • zhihu_obsidian:知乎用户 autocomplete、源稿标记和发布 HTML 的参考实现。
  • zhihu-sign-kt:ZSE v4 签名 算法的 MIT 许可实现。

About

Zhihu On Emacs:在 Emacs 中撰写、存档并发布知乎内容

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages