Wechat Converter

by davidlam-oss
5
4
3
2
1
Score: 36/100

Description

This plugin has not been manually reviewed by Obsidian staff. Convert Markdown into polished WeChat articles with live preview, copy, and draft sync.

Reviews

No reviews yet.

Stats

stars
4,170
downloads
0
forks
1
days
NaN
days
NaN
days
0
total PRs
0
open PRs
0
closed PRs
0
merged PRs
0
total issues
0
open issues
0
closed issues
0
commits

Latest Version

Invalid date

Changelog

README file from

Github

简体中文 | English

📝 微信公众号排版转换器 (WeChat Converter)

让技术写作回归优雅与纯粹。

一款专为 Obsidian 打造的微信公众号排版增强插件。它不仅仅是一个转换工具,更是您内容创作流中的"数字化妆师"。我们解决了 Obsidian 到微信公众号排版的"最后一公里"问题,让您专注于内容创作,无需为繁琐的格式调整而分心。

只需一键,即可将您的 Markdown 笔记转换为符合微信生态美学、阅读体验极佳的 HTML,无论是代码块、引用、列表还是本地图片,都能完美呈现。

Version Obsidian License

本项目基于开源项目 ai-writing-plugins 进行深度重构与迭代开发。我们致力于打造 Obsidian 生态中体验最好的公众号排版工具。

如果这个插件帮你节省了公众号排版、复制或同步草稿箱的时间,欢迎支持项目继续维护

💡 核心升级点 (Key Highlights)

相较于原版,我们重写了核心渲染逻辑并新增了大量实用功能,旨在实现真正的**"所见即所得"**:

  1. ➗ 完美支持数学公式 (Math Support) ⭐ v2.1 新增

    • LaTeX 全面支持:直接书写 $E=mc^2$$$...$$,所见即所得。
    • 纯矢量 SVG 渲染:采用独家无缓存技术,将公式转为独立的 SVG 矢量图,无论放大多少倍都清晰锐利。
    • 抗清洗:完美抵抗微信公众号的样式清洗,不再出现乱码或被吞掉的情况。
  2. 📊 Mermaid 图表支持

    • 沿用 Obsidian 原生渲染:在 Obsidian 里能正常显示的 Mermaid 图表,会在插件预览中继续显示。
    • 导出自动转 PNG:复制到公众号或同步到草稿箱时,会自动将 Mermaid 图表栅格化为 PNG,避免 SVG 过长导致微信拦截。
    • 保留图表原色:导出时不会套用数学公式的改色逻辑,尽量保持 Mermaid 主题与连线配色。
  3. 🚀 一键同步到微信草稿箱 (v2.2 增强)

    • 极速并发上传:图片上传速度提升 300%,支持多线程并发处理,大图文章秒传。
    • 智能重试机制:自动处理网络抖动与 Token 过期,全程零人工干预,稳如泰山。
    • 实时进度反馈:新增精确的上传进度条 (e.g., "3/12"),让等待不再焦虑。
    • 安全防重:严格的幂等性设计,杜绝因网络超时产生的重复草稿。
    • 告别复制粘贴:直接将文章同步到微信公众号后台草稿箱,图片自动上传。
    • 智能封面处理:如果你的文档头部信息(frontmatter)里写了 cover,会优先用它做封面;没写就自动用正文第一张图,也可以手动换图。
    • 智能摘要辅助:如果 frontmatter 里写了 excerpt,会优先用它;没写就自动截取前 45 个字,也可以手动改。
    • 同步后自动清理(可选):发送成功后,可以自动删除你在设置里指定的目录(默认关闭)。
    • 目录支持变量:清理目录支持 {{note}},会自动替换成当前文档名,例如 published/{{note}}_img
    • 可回收:支持优先移动到系统回收站,误删可恢复。
    • 多账号管理:支持最多 5 个公众号账号配置,快速切换同步。
    • 账号级发布默认值:可为每个公众号账号单独设置默认原文链接、留言开关和“仅粉丝可留言”。
    • Cloudflare 代理支持:解决 IP 白名单频繁变化问题,查看部署指南
  4. 🎛 全新可视化设置面板 (Settings Panel)

    • 告别繁琐的代码修改!我们内置了直观的设置面板,让您可以实时调整字体、字号、主题色等参数,一切尽在掌握。
  5. 🎨 三大专家级主题 (Premade Themes)

    • 内置 简约 (Simple)经典 (Classic)优雅 (Elegant) 三款精心设计的主题,覆盖从技术博客到人文随笔的各种场景。
    • 引用块支持更克制的中性灰样式,减少大面积主题色对正文阅读的干扰。
    • Callout 支持按语义类型高亮,如 notetipwarningdanger 等;未知类型会自动回退为信息类样式。
  6. 🖼️ 强大的本地图片支持 (Local Image Support)

    • 打破图床限制:完美支持 Obsidian 的本地图片引用(包括 ![[Wiki Link]]![]())。
    • 头像上传:支持直接上传本地图片作为作者头像,插件会自动转码为 Base64。
    • 强大的本地图片支持:无论是相对路径、绝对路径还是 WikiLink,都能自动识别并压缩。
    • GIF 动图支持:针对 GIF 格式特别优化,自动绕过压缩流程,完美保留完整动画帧。
    • 图片左右滑动查看:多张图片可以组织成横向滑动图片块,适合步骤截图、对比图、长图拆分和敏感图片提示。
    • 温馨提示:建议图片(尤其是 GIF)保持在 10MB 以内,以获得最佳的处理速度和公众号兼容性。超过 10MB 时插件会弹出提醒。
  7. ⚡️ 实时渲染预览 (Live Preview)

    • 右侧预览区实现了毫秒级响应的实时渲染。您在左侧 Markdown 编辑的每一个字符,都会即时反馈在右侧的公众号预览视图中。
    • 📱 双模预览 (Dual Mode Preview) ⭐ v2.2 新增
      • 手机仿真模式 (默认):提供逼真的 iPhone X 边框与刘海屏效果,支持深色模式适配,还原最真实的读者视角。
      • 经典全宽模式:在设置中一键切换回无边框全屏预览,利用屏幕每一寸空间。
    • ↕️ 双向同步滚动 (Bidirectional Sync):无论是在左侧编辑还是右侧浏览,另一侧都会如影随形,精准对齐。
  8. 💻 Mac 风格代码块与样式还原

    • 重新设计了代码块样式,支持 macOS 窗口风格及行号显示。
    • 1:1 完美还原:我们在 Obsidian 预览区看到的样式(包括间距、颜色、边框、阴影),复制到微信后台后将分毫不差
    • 宽表格横向滑动:列数较多或内容较长的表格会自动保持可左右滑动,避免在手机端被强行压缩或截断。

🆕 v2.7.0 新增了什么

  • AI 编排进入主工作流:现在可以直接在转换器顶部打开 AI 编排,配置 Provider、选择布局、切换颜色、查看 schema 校验结果,并导出调试快照。
  • AI 编排缓存更好用:按布局家族保留最新结果,重新打开面板后可以直接应用缓存;生成失败时也会尽量保留上一版成功结果。
  • 颜色选择更灵活:AI 编排可以自动推荐颜色,也可以在生成前手动选择颜色;生成后切换颜色会复用当前布局结构,不需要完整重新生成。
  • Mermaid 导出更稳:在 Obsidian 预览里继续保留 Mermaid 图表,复制到公众号或同步草稿时自动转成 PNG,减少微信吞图或拦截 SVG 的概率。
  • 多账号发布默认值:每个公众号账号都可以单独保存原文链接、留言开关和“仅粉丝可留言”,减少每次同步前的重复设置。
  • 引用 / Callout 风格更克制:新增中性灰引用模式,常见 Callout 会按语义类型自动着色,未知类型统一回退为信息类样式。

📖 使用方法

  1. 唤起插件

    • 点击 Obsidian 左侧边栏的 🪄 图标 (WeChat Converter)。
    • 或使用命令面板 (Cmd/Ctrl + P) 搜索并执行 "Open Wechat Converter"。
  2. 预览与调整

    • 插件会自动加载当前激活的笔记内容。
    • 在右侧面板中,您可以实时预览排版效果。
  3. 一键复制

    • 确认预览效果满意后,点击底部的 [📋 复制到公众号] 按钮。
    • 提示"已复制"后,直接在微信公众号后台编辑器中 Ctrl/Cmd + V 粘贴即可。
  4. 一键同步到微信草稿箱 ⭐ 新功能

    • 先在插件设置里填好公众号账号(AppID / AppSecret)。
    • 点击 [🚀 一键同步],选择账号后即可发送。
    • 每个公众号账号都可以单独配置发布默认值:
      • 默认原文链接
      • 默认开启留言
      • 默认仅粉丝可留言
    • 默认值读取规则:
      • 摘要优先读 excerpt
      • 封面优先读 cover
      • cover_dir 用于同步成功后的“自动清理目录”判断,不作为封面图来源
      • 如果没有,就自动回退到插件默认逻辑(摘要自动截取、封面取正文首图)
    • 你在弹窗里手动上传封面、手动改摘要,始终优先于自动值。
    • 发送成功后,文章会出现在公众号后台草稿箱。

横向滑动内容

宽表格左右滑动

当 Markdown 表格列数较多、单元格内容较长,或在手机预览中超过正文宽度时,插件会自动给表格加上横向滚动容器。你不需要额外写语法,正常写 Markdown 表格即可:

| 指标 | 第一季度 | 第二季度 | 第三季度 | 第四季度 | 备注 |
| --- | --- | --- | --- | --- | --- |
| 转化率 | 12.4% | 15.8% | 18.1% | 21.6% | 适合保留完整列宽 |

预览、复制到公众号、同步到草稿箱时,超宽表格都会尽量保持“左右滑动查看”,避免被压成很窄的列。

图片左右滑动

如果你想让多张图片横向排列,并在预览或微信公众号里左右滑动查看,可以使用 Obsidian Callout 写法:

> [!image-swipe] 左右滑动查看图片
> ![[步骤一.png]]
> ![[步骤二.png]]
> ![步骤三](https://raw.githubusercontent.com/davidlam-oss/obsidian-wechat-converter/HEAD/attachments/step-3.png)

如果第一屏需要先展示敏感图片提示,可以使用:

> [!image-sensitive] 此类图片可能引发不适,向左滑动查看
> ![[图片一.png]]
> ![[图片二.png]]

也可以先选中多行图片语法,再打开命令面板 (Cmd/Ctrl + P) 运行 插入图片块插入敏感图片块,插件会自动帮你包裹成对应的 Callout。

AI 编排(实验功能)

  • 入口有两处:
    • 插件设置里的 AI 编排:用于配置 Provider、默认布局、默认颜色和缓存策略
    • 转换器顶部工具栏里的 AI 编排:用于对当前文章生成和应用版式
  • 当前支持 3 类 Provider 接口:
    • OpenAI 兼容接口
    • Gemini 兼容格式
    • Anthropic 兼容格式
  • 内置 3 套布局家族:
    • 原文增强型:最接近普通预览,正文连续性更强
    • 教程卡片型:更适合步骤拆解、清单、案例说明
    • 轻杂志型:更强调留白、图文节奏和编辑感
  • 可以选择 自动配色,也可以在生成前手动指定颜色方案。
  • 生成后可以继续手动切换颜色方案,复用当前布局结构,不必每次都重新生成。
  • 每个布局家族会保留当前文章最新的一份缓存;重新打开侧边栏后,可以直接应用匹配当前文章的缓存结果。
  • 面板里可以直接看:
    • schema 校验提醒
    • 布局 JSON
    • 错误详情
    • 复制给 AI 的排查 Prompt
    • 当前 JSON 或错误详情
  • 如果重新生成失败,上一版成功结果仍然可以保留,方便你继续预览和比较。

同步后自动清理(可选)

  • 入口:插件设置 → 高级设置
  • 默认是关闭的。
  • 只有“发送到微信草稿箱成功”后,才会触发清理。
  • 清理的是你在设置里填的目录(删目录,不是删单个文件)。
  • 目录支持 {{note}},会自动替换成当前文档名。
  • 清理目录请填写 vault 内相对路径(不要填 /Users/...C:\\... 这类绝对路径)。
  • 清理成功后,如果文档里的 cover / cover_dir 指向这个已删除目录,插件会自动清空这些失效字段。
  • 建议开启“使用系统回收站”,这样删错也能找回。
  • 示例:published/{{note}}_img
    如果当前文档名是 post,实际删除目录是 published/post_img

Mermaid 图表导出说明

  • 在 Obsidian 预览里能正常显示的 Mermaid 图表,会尽量在插件预览里继续保持可见。
  • 当你执行“复制到公众号”或“一键同步到草稿箱”时,插件会自动把 Mermaid 图表栅格化为 PNG。
  • 这样做的目的不是改变样式,而是为了减少微信公众号对长 SVG、复杂 SVG 的清洗和拦截问题。
  • 导出链路会尽量保留 Mermaid 原本的颜色,不会套用数学公式那一套 SVG 清理逻辑。

✍️ 中文标点标准化规则

插件支持在右侧排版 setting panel 中开启或关闭“正文标点标准化”:

  • 入口:右侧排版 setting panel正文标点标准化
  • 作用范围:仅作用于预览 / 复制 / 同步结果
  • 不会修改原始 Markdown 文件

已支持的替换规则

中文语境下,插件会将常见英文半角标点替换为中文全角标点:

  • ,
  • .
  • :
  • ?
  • !
  • ;
  • "..."“...”
  • (...)(...)

已保护的场景

以下内容会尽量保持原样,避免误伤技术文档:

  • 行内代码与代码块,例如 `content````ts ... ```
  • URL 与邮箱,例如 https://example.com/a?b=1[email protected]
  • 常见技术性括号表达式、数学记号、函数/调用语法,例如 公式(x+y)矩阵(A,B)foo(bar, baz)f(x, y)
  • 英文原句与数字小数点,例如 Version 2.1 is stable.
  • 文件名、相对路径、Windows 路径,例如 README.mdtests/foo.test.jsC:\Users\name\demo.md
  • 命令行参数与脚本名,例如 --runtest:coverage
  • 环境变量赋值,例如 NODE_ENV=production
  • 日期时间,例如 2026-03-09 09:15:112026/02/13 23:00
  • Markdown 结构本身,例如表格分隔符、代码围栏、链接语法等

当前边界

  • 这是规则驱动的标准化能力,不依赖模型。
  • 它适合做“发布前质检”和“统一格式”,不适合做语义判断。
  • ... / ...... 这类省略号写法会保留原样,不会强制改写。
  • 像“并列词语之间把逗号改成顿号 ”这类规则目前没有启用,因为容易误改,后续如果要做,更适合放到 AI 精修能力里。

🎨 引用与 Callout 样式

  • 入口:右侧排版 setting panel → 引用 / Callout 样式
  • 现在支持两种整体模式:
    • 跟随主题
    • 中性灰(推荐)
  • 中性灰 模式更适合长文阅读,会明显减弱大面积主题色的存在感。
  • Callout 会按语义类型自动着色,例如 notetipwarningdanger
  • 对于插件还不认识的 Callout 类型,会统一回退为信息类样式,而不是随机继承当前主题色。

🔧 代理设置(解决 IP 白名单问题)

微信公众号 API 需要 IP 白名单验证。如果你使用 VPN 或动态 IP,可以通过 Cloudflare Worker 代理解决。

部署步骤

  1. 创建 Cloudflare Worker

    • 登录 Cloudflare Dashboard
    • 左侧菜单选择 Workers & PagesCreate ApplicationCreate Worker
    • 命名(如 wechat-proxy)并点击 Deploy
  2. 编辑 Worker 代码

    点击 Edit code,替换为以下代码:

    export default {
      async fetch(request, env) {
        const MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
        const ALLOWED_METHODS = new Set(['GET', 'POST', 'UPLOAD']);
        const corsHeaders = {
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Methods': 'POST, OPTIONS',
          'Access-Control-Allow-Headers': 'Content-Type',
        };
    
        const jsonResponse = (payload, status = 200) =>
          new Response(JSON.stringify(payload), {
            status,
            headers: {
              ...corsHeaders,
              'Content-Type': 'application/json; charset=utf-8',
            },
          });
    
        const isAllowedWechatUrl = (rawUrl) => {
          try {
            const parsed = new URL(rawUrl);
            return parsed.protocol === 'https:' && parsed.hostname === 'api.weixin.qq.com';
          } catch {
            return false;
          }
        };
    
        const toUint8Array = (base64) => {
          const binary = atob(base64);
          const bytes = new Uint8Array(binary.length);
          for (let i = 0; i < binary.length; i++) {
            bytes[i] = binary.charCodeAt(i);
          }
          return bytes;
        };
    
        // 处理 CORS 预检请求
        if (request.method === 'OPTIONS') {
          return new Response(null, { headers: corsHeaders });
        }
    
        if (request.method !== 'POST') {
          return jsonResponse({ error: 'Method Not Allowed' }, 405);
        }
    
        try {
          const body = await request.json();
          const { url, method = 'GET', data, fileData, fileName, mimeType } = body;
          const normalizedMethod = String(method || 'GET').toUpperCase();
          
          // 安全校验:只允许访问微信 API
          if (typeof url !== 'string' || !isAllowedWechatUrl(url)) {
            return jsonResponse({ error: 'Invalid URL. Only https://api.weixin.qq.com/ is allowed.' }, 400);
          }
    
          if (!ALLOWED_METHODS.has(normalizedMethod)) {
            return jsonResponse({ error: 'Invalid method. Only GET, POST, and UPLOAD are allowed.' }, 400);
          }
    
          let response;
          if (normalizedMethod === 'UPLOAD') {
            if (typeof fileData !== 'string' || fileData.length === 0) {
              return jsonResponse({ error: 'Missing fileData for upload.' }, 400);
            }
    
            const approxBytes = Math.floor(fileData.length * 3 / 4);
            if (approxBytes > MAX_UPLOAD_BYTES) {
              return jsonResponse({ error: 'Upload too large. Maximum size is 10 MB.' }, 413);
            }
    
            const safeMimeType =
              typeof mimeType === 'string' && mimeType.startsWith('image/')
                ? mimeType
                : 'application/octet-stream';
            const safeFileName =
              typeof fileName === 'string' && /^[\w.\-]+$/.test(fileName)
                ? fileName
                : 'image';
    
            // 文件上传处理:Base64 -> Binary -> FormData
            const bytes = toUint8Array(fileData);
            const formData = new FormData();
            formData.append('media', new Blob([bytes], { type: safeMimeType }), safeFileName);
            response = await fetch(url, { method: 'POST', body: formData });
          } else {
            // 普通 JSON 请求
            const opts = { method: normalizedMethod };
            if (normalizedMethod === 'POST') {
              opts.headers = { 'Content-Type': 'application/json' };
              if (data !== undefined) opts.body = JSON.stringify(data);
            }
            response = await fetch(url, opts);
          }
    
          const responseText = await response.text();
          let result;
          try {
            result = responseText ? JSON.parse(responseText) : {};
          } catch {
            return jsonResponse({ error: 'Upstream returned a non-JSON response.' }, 502);
          }
    
          return jsonResponse(result, response.status);
        } catch (error) {
          return jsonResponse({ error: 'Proxy request failed.' }, 500);
        }
      }
    };
    

    这个版本比旧示例更适合直接公开在文档里:

    • 不记录请求 URL,避免把 appidsecretaccess_token 写进日志。
    • 不回显原始请求体和异常堆栈,减少敏感信息暴露。
    • 只允许 GETPOSTUPLOAD,并且只允许访问 https://api.weixin.qq.com/
    • 上传大小限制为 10 MB,避免异常大文件拖垮 Worker。
    • 上传字段固定为 media,与插件当前行为保持一致。
  3. 配置微信 IP 白名单

    将 Cloudflare 出口 IP 添加到微信公众号白名单(官方 IP 列表):

    173.245.48.0/20, 103.21.244.0/22, 103.22.200.0/22, 103.31.4.0/22
    141.101.64.0/18, 108.162.192.0/18, 190.93.240.0/20, 188.114.96.0/20
    197.234.240.0/22, 198.41.128.0/17, 162.158.0.0/15, 104.16.0.0/13
    104.24.0.0/14, 172.64.0.0/13, 131.0.72.0/22
    
  4. 插件配置

    在插件设置 → 高级设置API 代理地址 中填入你的 Worker URL:

    https://wechat-proxy.your-account.workers.dev
    

    该代理仅建议自用,请不要公开分享 Worker 地址,也不要将其部署为公共服务。

🚀 安装

  1. GitHub Releases 下载最新的 obsidian-wechat-converter.zip 插件包。
  2. 解压并将其中的文件夹放入 Obsidian vault 的 .obsidian/plugins/ 目录中。

    最终路径应为:.../.obsidian/plugins/obsidian-wechat-converter/

  3. 确保文件夹内至少包含以下文件(三件套运行时):
    • main.js
    • manifest.json
    • styles.css
  4. 重启 Obsidian 或在设置中刷新插件列表,并启用插件。

BRAT 安装/更新

如果你使用 BRAT 管理插件更新:

  1. 安装并启用 BRAT 插件。
  2. 在 BRAT 中添加仓库:DavidLam-oss/obsidian-wechat-converter
  3. 安装后执行一次冒烟检查:
    • 打开转换面板
    • 预览渲染
    • 复制到公众号
    • (可选)一键同步到草稿箱

说明:当前版本已支持标准三件套运行时,BRAT 更新路径与 Obsidian 插件标准发布方式一致。

🤝 贡献 (Contributing)

欢迎提交 Issue 或 Pull Request!

  1. Fork 本仓库。
  2. 创建您的特性分支 (git checkout -b feature/AmazingFeature)。
  3. 提交您的更改 (git commit -m 'Add some AmazingFeature')。
  4. 推送到分支 (git push origin feature/AmazingFeature)。
  5. 开启一个 Pull Request。

📄 许可证

本项目采用 MIT License 开源。

👨‍💻 作者

林小卫很行 (DavidLam)

一名热衷于提升生产力的开发者与内容创作者。 如果您在使用过程中有任何问题、建议或发现了 Bug,欢迎随时在 GitHub Issue 区留言反馈。相信工具的力量,让创作更自由。