前言

最近作者决定使用Halo CMS搭建自己的博客,但是Halo商城中的几个MarkDown编辑器就写作体验上相比Typora来说还是相差甚远。个人认为目前最佳的MarkDown写作方式还是 Typora + 图片上传器 + 图床,编写之后导入到Halo中,这种方式兼顾了写作体验和文章的可迁移性。在Halo CMS 中,用户上传的文件会保存在 /upload/目录下,因此我们无需额外搭建图床,只需实现一个图片上传器即可。

实现思路

根据Typora官方文档,如果我们设置了自定义的图像上传器,那么当我们在使用粘贴的方式插入图片时,Typora会自动给图像生成器传递图片路径,类似这样:

python uploader.py 图片路径1 图片路径2

图片生成器只需完成上传逻辑,并且在控制台输出这样的内容:

Upload Success:
http://remote-image-1.png
http://remote-image-2.png

之后 Typora 就会从输出中获取远程图片 url,并替换 Markdown 文档中使用的原始本地图片。

那么uploader.py的实现思路就很简单了:

  1. 抓包分析上传图片的请求和参数。
  2. 使用Python 的 requests 库来模拟这个文件上传操作。
  3. 输出图片的远程链接。

使用教程

安装Python环境

作者使用的版本是Python 3.13.3,已有Python环境可忽略。

复制代码并且保存

可以命名为uploader.py,保存的路径最好不要有中文。

import requests
from datetime import datetime
import os
import sys

# 全局变量
URL = '你的博客地址'
COOKIE = '你的Cookie'
GROUP_NAME = '分组名称'

def upload(file_path, custom_filename):
    """上传图片文件并返回上传成功后的URL"""
    upload_api = f'{URL}/apis/api.console.halo.run/v1alpha1/attachments/upload'  # 上传API的URL
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0',  # 用户代理
        'Accept': '*/*',  # 接受所有类型的响应
        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',  # 接受的语言
        'Accept-Encoding': 'gzip, deflate, br',  # 接受的编码
        'Referer': f'{URL}/console/attachments',  # 请求来源
        'Origin': URL,  # 请求的来源域名
        'Sec-Fetch-Dest': 'empty',  # 安全获取目标
        'Sec-Fetch-Mode': 'cors',  # 安全获取模式
        'Sec-Fetch-Site': 'same-origin',  # 安全获取站点
        'Priority': 'u=0',  # 优先级
        'Te': 'trailers',  # 尾部
        'Connection': 'keep-alive',  # 保持连接
        'Cookie': COOKIE  # Cookie用于身份验证
    }
    data = {
        'policyName': 'default-policy',  # 上传策略名称
        'groupName': GROUP_NAME  # 上传分组名称
    }
    files = {
        'file': (custom_filename, open(file_path, 'rb'), 'image/png')  # 上传文件,指定自定义文件名
    }
    response = requests.post(upload_api, headers=headers, data=data, files=files)  # 发送POST请求
    if response.status_code == 200:  # 如果响应状态码为200,表示上传成功
        return f'{URL}/upload/{custom_filename}'  # 返回上传后的URL
    else:
        print(f"上传失败: {response.text}")  # 打印错误信息
        return None  # 返回None表示上传失败

def generate_filename(extension, timestamp, count):
    """生成带有时间戳和后缀的唯一文件名(例如,2024-04-21-2147-A.jpg)"""
    suffix = chr(65 + count)  # 65是'A'的ASCII码,生成'A'、'B'等后缀
    return f"{timestamp}-{suffix}.{extension}"  # 返回生成的文件名

def get_timestamp():
    """获取当前时间戳,格式为YYYY-MM-DD-HHMM"""
    return datetime.now().strftime("%Y-%m-%d-%H%M")  # 返回格式化的时间戳

def main(file_paths):
    """上传提供的文件路径中的图片并返回成功上传的URL列表"""
    timestamp = get_timestamp()  # 获取当前时间戳
    count = 0  # 初始化计数器
    success_urls = []  # 初始化成功上传的URL列表

    for file_path in file_paths:  # 遍历文件路径列表
        extension = os.path.splitext(file_path)[1][1:]  # 提取文件扩展名(不带点)
        custom_filename = generate_filename(extension, timestamp, count)  # 生成自定义文件名
        uploaded_url = upload(file_path, custom_filename)  # 上传文件
        if uploaded_url:  # 如果上传成功
            success_urls.append(uploaded_url)  # 将URL添加到列表中
        count += 1  # 增加计数器

    if success_urls:  # 如果有成功上传的URL
        print("Upload Success:")  # 打印成功信息
        for url in success_urls:  # 遍历URL列表
            print(url)  # 打印每个URL
    else:
        print("No files were uploaded successfully.")  # 打印无文件上传成功的信息
    return success_urls  # 返回成功上传的URL列表


if __name__ == "__main__":
    file_paths = sys.argv[1:]
    main(file_paths)

浏览器抓包

  1. 打开浏览器,进入halo控制台,点击附件->上传->你要上传的分组->浏览,然后随意选择一个文件,注意先不要点上传上传文件

  2. 按下F12键打开浏览器控制台,选择网络

    打开浏览器控制台

  3. 点击上传按钮,这个时候会看到控制台出现新内容。点击打开。

    抓包

  4. 在其中找到这两个字段:

    复制Cookie

    复制groupName

  5. 用编辑器打开uploader.py,将这三行内容替换为你自己的

    URL = '你的博客地址'
    COOKIE = '你的Cookie'
    GROUP_NAME = '分组名称'
    

Typora配置

依次点击文件->偏好设置->图像,修改以下值:

Typora设置

如果配置成功,点击验证图片上传选项应该会看到这样的页面:
配置成功

之后每次你插入图片就会自动上传图片到你的Halo存储库,并且将图片链接替换为远程链接。