Skip to main content
Glama
drsound

markdown-to-whatsapp

by drsound

Markdown to WhatsApp Converter

License: MIT tests npm

将标准 Markdown 转换为 WhatsApp 的格式化语法——既可以作为网页使用,也可以作为 npm 库、命令行工具,或为智能体提供的 MCP 服务器。

➡️ 前往在线工具

npm i markdown-to-whatsapp · npx markdown-to-whatsapp mcp

应用截图


本工具的用途

WhatsApp 使用一种非标准的文本格式化语法(例如 *bold*_italic_~strikethrough~)。这与标准 Markdown 类似,但又不完全相同。

本工具提供了一种简单的方法,将来自 Markdown 来源(如文本编辑器、Google Docs 等)的文本转换为 WhatsApp 所期望的格式,无需手动修正。

整个转换过程完全在浏览器中通过 JavaScript 在本地运行,并解析器随页面一起加载。任何数据都不会发送到服务器 — 页面发出的唯一外部请求是获取字体。

支持的转换

该脚本使用 marked 库进行基于 AST 的正确解析,并处理:

文本样式

  • 加粗: **text***text*

  • 斜体: *text*_text__text_

  • 删除线: ~~text~~~text~

  • 行内代码: `code``code`

  • 加粗+斜体: ***text***_*text*_(保留两种样式)

标题

标题会转换为带有不同级别 emoji 前缀的加粗文本:

  • # H1*📌 H1*

  • ## H2*🟠 H2*

  • ### H3*🟡 H3*

  • 依此类推……

可以在界面中关闭 emoji 前缀(标题 · 表情符号),只保留纯文本 *Title*

列表

  • 无序列表: 使用 * 前缀,嵌套层级用 标记

    • 层级 1: * Item

    • 层级 2: * ◦ Item

    • 层级 3: * ◦ ◦ Item

  • 有序列表: 保留编号,使用 标记嵌套层级

    • 1. A / ◦ 1. A1 / ◦ ◦ 1. A1a / 2. B

  • 任务列表: - [x]- [ ],也可用于有序列表(1. ☑ done

  • 松散列表项: 同一个列表项中的多个段落会合并为同一行

  • 列表项中的块内容: 代码块、引用块和嵌套列表会各自一行输出在列表项下方

气泡宽度

WhatsApp 的气泡在一行内能容纳固定数量的等宽字符——在 360 px 的手机上约 26 个,这也是默认值。你可以给自己发送一个代码块,然后数它在在哪里换行来测量你的设备宽度,然后把 宽度 设置为该值(旁边的 ? 也会说明)。该字段接受 10 到 80:没有手机超出这个范围。

这个数字是手机本身的属性,而不是某个表格的属性,因此它控制所有等宽内容:表格会降级以保持在此宽度以下,预览中也会按该宽度绘制每个代码块,并在接收方 WhatsApp 必会换行的地方换行。

表格

表格的渲染有两种样式,可以在界面中针对整个文档或逐个表格进行选择:

  1. 自动(Auto)(默认):在等宽块内绘制一个表格,宽度按需自适应,但绝不会超过气泡宽度;当完全无法绘制盒框时,退化为项目符号列表。

    +--------+-------------+
    | Name   | Description |
    +========+=============+
    | Value  | Details     |
    +--------+-------------+
  2. 列表(List):总是使用项目符号列表。

列表的布局方式

列表可以按三种方式组合单元格。转换器会根据表头和加粗单元格自动判断,判断结果可按单个表格覆盖(布局:自动 · 按行 · 按列 · 按对):

  • 按对(Pairs) — 只有两列,无论表头是什么:每行是一条 key: value。在几乎每个表中,把表头逐行拼写出来都比 Italy: Rome 这种形式更难看。

    * *CPU:* Intel Xeon
    * *RAM:* 64 GB
    * *Storage:* 1 TB SSD
  • 按列(Columns) — 三列或更多,但第一列表头为空或表示一个属性(“Feature”、“Spec”、“Parameter” 等),或者第一列本身是加粗的:这种情况下是第二层比较矩阵,被比较的对象是列,因此每列成为一个组。

    * *Proxmox*
    * ◦ _Kernel:_ KVM
    * ◦ _License:_ AGPL v3
    * *ESXi*
    * ◦ _Kernel:_ VMkernel
    * ◦ _License:_ Proprietary
  • 按行(Rows) — 其他所有情况:每一行一组,由其第一个单元格用作标签。

    * *Product:* Laptop
    * ◦ _Price:_ $999
    * ◦ _Stock:_ 50
    * *Product:* Smartphone
    * ◦ _Price:_ $599
    * ◦ _Stock:_ 100

属性词是逐词匹配的(“Species” 不等于 “Spec”),支持 11 种语言: 英语、意大利语、西班牙语、法语、葡萄牙语、德语、俄语、阿拉伯语、印地语、孟加拉语和印度尼西亚语。Pairs 要求正好两列;如果用于更宽的表格,它会被当作 Rows 处理。

表格框的降级方式

在任何宽度下都不是直接绘制完整的表格框,而是逐步降级,直到其适合 monoWidth,当没有任何框能放得下时就变成列表。没有任何办法可以让表格比气泡更宽。

  1. 完整框,逐列去掉内边距(先右侧,再左侧)。

  2. 紧凑无边框样式,再次逐步去掉内边距:

     Head1|Head2       |Head-N
    ------+------------+------
     A    |BBBBBBBBBBBB|C
  3. 换行框,先是完整边框,再进行紧凑:每列至少获得其最长单词的宽度,剩余宽度按比例分配,单元格内容按单词换行。行的高度可以随意增长——没有限制——并且行之间始终绘制一条分隔线,否则两个已换行的行会挤在一起。单元格靠行顶部对齐。

    +---------+--------------+
    | Feature | Notes here   |
    +=========+==============+
    | Alpha   | short note   |
    +---------+--------------+
    | Beta    | a slightly   |
    |         | longer note  |
    +---------+--------------+
  4. 项目符号列表,当最长单词 / 内容连一个也放不下时(例如一个长 URL、五列且每列 26 字符……)。

判断一个较高的换行框是否优于列表,是预览区给你的手动判断空间:该表格自己的面板可以将其切换到“列表”。一个无法容纳任何表格框的表格会在其面板中明确说明,并直接提供列表布局,而不是提供一个无法产生任何效果的样式。

其他表格行为

  • 列宽按显示单元格衡量,因此 emoji 和 CJK 文本能保持对齐(日本語 各占两列)——这也受手机限制:这些字符来自备用字体,因此对齐是尽力而为,不像 ASCII 边框那样精确。

  • 列对齐标记(:---:---:---:)在表格框和紧凑样式下均会生效。

  • 仅包含表头的表格在渲染时不会出现空的表格体或双重边框。

  • 单元格内的 <br> 会变成一个空格,转义的 \| 会变成 ¦,这样它就无法伪造出额外的列。

  • 边框全部使用纯 ASCII+-|=),这是故意为之。WhatsApp 的等宽字体并没有制表;手机只能从一个备用字体中加载 ─ 和 ╭,并且使用那个字体自带的字符宽度,这样 26 个制表符会折成两行,而旁边的文本行不会。+-| 是等宽字体真正能保证宽度的字符——这也是为什么转义的竖线会变成 ¦¦ 是一个 与 à 同字体的 Latin-1 字符。

  • 行分隔线(默认关闭):在表格主体行之间绘制一条分隔线,在所有表格框和紧凑样式下都会显示;而换行表格无论如何都会绘制。

  • 样式行分隔线以及列表布局都可以按表格单独设置:像把鼠标悬停在某个表格上,就会出现它自己的控制项,这些控制项从文档默认值开始,只会覆盖该表格,而且只显示当前仍然适用的控制项(也就是哪些当前仍适用的显示出来)。表格宽度不在此列——只有一个气泡是所有表格共用的。一个带有自身设置的表格会有一个虚线标记,因为面板顶部的控制项故意不处理它,它的“重置”按钮会将其交回那些全局控制项。自定义设置通过表头文本与表格关联,因此在其上方增删任何其他表格不会改变这些设置。嵌套在列表项或引用块内的表格总是跟随文档默认设置。

代码块

围栏代码块和缩进代码块会原样进入 WhatsApp:转换器从不自动重新换行或重新缩进它们,因为代码内部的换行是内容,而不是排版布局。WhatsApp 会自行将长行在单词中间折行,而聊天气泡没有横向滚动——所以本地中会有的地方,预览会在 monoWidth 处模拟这种自动换行并显示精确位置,以便让你看出接收者会见到哪里断行。

其他元素

  • 链接: [text](url)text (url);自动链接、<https://x>[url](url)<me@x.com> 会渲染为完整的 URL 或地址(不重复,也不泄露 mailto:

  • HTML 实体: 支持十进制和十六进制引用以及常见的已命名引用——Latin-1 字母、标点和符号(caf&eacute;café&copy;©&#65;A)。Cold、不常见的引用(Greek、Math)保持原文。

  • 行内 HTML: <b>/<strong>*<i>/<em>_<s>/<del>~<code>`<br> → 换行;注释和其他标签会被去掉

  • HTML 块: 标签会被移除,块边界会变成换行,实体会被解码

  • 引用块: 保留 > 前缀,支持嵌套(> > nested)

  • 代码块: 保留三重反引号;代码块内容中的三反引号会替换成 ˋˋˋ,避免提前闭合

  • 水平分割线: ---───────────────

  • 转义字符: 使用 Unicode 形似字符(_),使 WhatsApp 不会把这些内容解释为格式

WhatsApp 相关处理

  • 忽略多词中间格式: super**bold**lysuperboldly(WhatsApp 不支持单词中间的格式)

  • 标点可以作为有效边界: **Name**: value*Name*: value,同样适用于 (**x**)`end.

如何使用

  1. 打开网页: https://drsound.github.io/markdown-to-whatsapp/

  2. **.md 文件粘贴、输入,

  3. 或拖放到左侧面板中。“尝试一个示例”会用示例消息填充面板。

  4. 在右侧面板中可以看到在 WhatsApp 气泡中显示的消息,与对方看到的完全一致;“查看原始语法”会显示将被复制的文本。两个面板保持同步滚动——鼠标悬停在哪一侧,就由哪边控制滚动——并且你输入时,预览会跟随光标。

  5. 点击 “复制到 WhatsApp” 可以直接复制,或点击 “在 WhatsApp 分享” 通过 wa.me 打开聊天并准备好消息。超长消息并不轻松放在链接中——因为浏览器会截断超过几千字符的 URL——所以此时分享按钮会自动禁用并提示改用复制。

界面会跟随操作系统浅色或深色主题;页头中的开关可手动覆盖这个选择,并且该选择会被记住。在选项栏(Options bar)中,每种内容类型拥有一个自己的区域,并且仅在话题中包含相应内容时显示:气泡(Bubble)(宽度,在存在表格或代码块时会出现)、表格(Tables)(样式和分隔符)以及 标题(Headings)(表情前缀)。如果一个选项因为其他选项已经设置而失去意义——例如列表样式中的分隔符、没有任任何等宽内容时的宽度——它会保持原位并置灰,而不是被去掉,这样选项栏会保持原来的形状。所有选项会连同主题一起保存在 localStorage 中。而独立表格的选项不会被保存:它们属于正在被转换的文本本身。

从代码、命令行或 agent 中使用它

同一个转换器也发布在 npm 上,包名为 markdown-to-whatsapp(Node 20 或更高版本)。可用选项与上方描述的相同,名称和默认值也完全一致,完整列表见本节末尾。

npm install markdown-to-whatsapp
import { convertTextToWhatsapp, convertToBlocks } from 'markdown-to-whatsapp';

convertTextToWhatsapp('# Hi **there**');
// → '*📌 Hi there*'

convertTextToWhatsapp(markdown, { monoWidth: 30, tableFormat: 'auto', headingEmojis: false });

// The same conversion with the blocks kept apart: each has the source `line` it starts on,
// and each table its `key`, `columns`, `fitsBox`, `asList` and `listLayout`
const { text, blocks } = convertToBlocks(markdown, { monoWidth: 30 });

命令行

npx markdown-to-whatsapp notes.md                  # a file…
cat notes.md | npx markdown-to-whatsapp            # …or stdin
npx markdown-to-whatsapp notes.md --width 32 --tables list --no-emoji
npx markdown-to-whatsapp notes.md --json           # the blocks, for scripting
npx markdown-to-whatsapp --help

--width (10–80),--tables auto|list--layout auto|rows|columns|pairs--separator--no-emoji--json,以及 -h, --help-v, --version。结果输出到标准输出;参数错误会说明原因到标准错误,并让进程退出并返回退出码 2。

MCP 服务器

该包可以作为一个 Model Context Protocol 服务器运行(通过 stdio 连接),从而让 Agent 可以自己完成转换——这在处理表格时尤其重要:因为在 26 字符串的宽度下计算需要多少列,恰恰是可通过模型(Model)容易算错而本工具能算对的内容。

claude mcp add markdown-to-whatsapp -- npx -y markdown-to-whatsapp mcp

或者,对于 Claude Desktop 以及需要 JSON 配置的其他客户端:

{
  "mcpServers": {
    "markdown-to-whatsapp": {
      "command": "npx",
      "args": ["-y", "markdown-to-whatsapp", "mcp"]
    }
  }
}

它对外暴露了一个工具,convert_markdown_to_whatsapp,接受 markdown 以及可选的 monoWidthtableFormatlistLayoutrowSeparatorheadingEmojis。文本会作为工具的内容返回,结构化的结果中它以 text 的形式出现,并附带 tables:每个表格对应一个条目,包含 keycolumnsfitsBoxasListlistLayout,这样智能体就能分辨哪些表格变成了方框、哪些变成了列表。该工具是只读且幂等的。

选项

选项包括 tableFormat (auto | list)、monoWidthrowSeparatorheadingEmojislistLayout (auto | rows | columns | pairs) 以及 tableOverrides——它可以是一个按表格在文档中的位置进行索引的数组,也可以是一个以表格的 key 为键的对象(key 是其标题文本用 | 连接而成,如果标题重复则在后面添加 #2#3…),每个条目只针对该表格单独覆盖其他选项。页面仅对表格单独暴露 listLayout

旧名称在输入时仍然被接受:tableThreshold 用于 monoWidthascii / always 用于 tableFormatascii 从未绘制过比聊天气泡更宽的方框,因此它对应到 auto)。如果同时给出了旧名称和新名称,新名称生效,旧名称会被丢弃。borderStyle 曾经用来选择 Unicode 方框绘制,现在仍然被接受但会被忽略。

开发

运行测试

Node 20 或更高版本,在仓库根目录下:

npm install
npm test

测试套件采用基于文件的测试:

  • tests/inputs/*.md —— Markdown 输入文件

  • tests/inputs/*.json —— 可选的每个测试夹具的转换选项(例如 { "monoWidth": 40 }

  • tests/expected/*.txt —— 预期的 WhatsApp 输出

外加几个不变量:仓库内自带的解析器与安装的解析器一致,convertToBlocks 报告正确的源行号,包入口就是页面脚本。

测试也会在每次推送和拉取请求时于 CI 中运行(.github/workflows/test.yml)。

项目结构

  • docs/converter.js——转换器本身:一个纯 ES 模块,不依赖 DOM,选项通过参数传入。它既是页面导入的脚本,也是 npm 包的入口(exports["."]),因此只有一份副本,没有构建步骤。它暴露了 convertTextToWhatsapp(markdown, options)convertToBlocks(markdown, options)——后一种是与之相同的转换,但保留了顶层块的区分,并标记每个块来自哪张表,这正是按表选项的基础——以及 UI 用来判断是否只显示适用选项的 mdContainsTable / mdContainsHeading / mdContainsCode 查询。convertToBlocks 中的每个块都会报告它在源文件中的起始 line,这是两个面板保持同步滚动的关键实现,每个表格块还会报告自己的 keycolumnsfitsBox(是否可能成为方框)、asList(实际写出的内容)和 listLayout,这样界面就能只提供剩余的选项。

  • docs/ui.js——页面逻辑:主题、按上下文出现的选项、WhatsApp 预览、两个面板之间的滚动同步、复制与分享

  • docs/index.htmldocs/style.css——标记语言和手写样式表(无 CSS 框架)

  • docs/vendor/——marked 的 ES 构建版本,由 npm run vendornode_modules 复制到该目录;页面的 import map 将 marked` 解析到这个本地副本

  • bin/markdown-to-whatsapp.js——命令行工具;bin/mcp.js——它通过 mcp 子命令启动的 MCP 服务器,且只有在执行该子命令时才加载,因此转换文件时绝不会加载协议 SDK

  • scripts/vendor.js——把 marked 的 ES 构建版本复制到 docs/vendor/npm run vendor

  • tests/——测试数据和运行器

marked 依赖

marked 的版本被固定在 package.json 中的 18.0.10,页面从 docs/vendor/ 加载的那份副本由测试套件与该版本进行比对,因此页面、包和测试始终以相同的方式解析 Markdown。如果要升级:修改固定版本,执行 npm installnpm run vendornpm test

发布

npm test
npm pack --dry-run   # docs/converter.js, bin/, README, LICENSE — nothing else
npm publish

本地开发

cd docs
python3 -m http.server 8080
# Open http://localhost:8080

许可证

MIT,参见 LICENSE 文件。

-
license - not tested
Not graded
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
Release cycle
1Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Use your own Word templates to convert Markdown → DOCX/PDF/HTML from any MCP-compatible AI.

  • Web scraping for AI agents. Converts URLs to clean, LLM-ready Markdown with anti-bot bypass.

  • Fonto (FontoXML) documentation for AI tools. Converts DITA XML to Markdown on demand.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/drsound/markdown-to-whatsapp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server