likes
comments
collection
share

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

作者站长头像
站长
· 阅读数 3

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

为什么要开发这款工具?

Typora 是一款 IT 界常用的 Markdown 笔记软件,它有强大的 Markdown 语法功能。同时它也可以粘贴图片、音频、视频等媒体文件。

通常,从剪切板粘贴的图片都会默认保存到本地的 assets 同级目录中,文件名称为截图工具生成的名称。保存在本地虽然说可以长时间保存,但是如果你要迁移你的文档到其他平台的时候就很麻烦,比如说语雀、石墨等平台。

如果你使用 Typora 提供的上传服务那得要架梯子,文件存储到国外服务器,不会科学上网的小伙伴就麻烦了。或者你公司可能要求所有文档相关图片要上传到公司的服务器,你也得自己搞一个上传工具。

选择作为图床的平台

适合作为图床的平台通常可以是云服务厂商提供的 OSS 存储服务平台、代码托管平台、公司自己搭建的 OSS 服务平台。

下面以 Gitee 为例,调用 Gitee 的开发者 API 上传图片。

Gitee

Gitee 开发者文档

首先我们需要打开 Gitee 的开发者文档官网:Gitee API 文档。然后找到上传文件的 API:

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

而且之前你还需要获得 Gitee API 的授权,就在页面的右上角点击获取授权,笔者这里已经获取授权了。

在图中,可以看到几个参数:

  • access_token:用户授权码,相当于令牌。
  • owner:是你的注册账号的用户名不是昵称。
  • repo:仓库名。
  • path:文件在仓库里面存储的路径。
  • content:文件的 base64 编码。
  • message:提交的消息。
  • branch:分支,默认是你的主分支。

接下来,先去获取用户授权码。点击设置,找到用户账号设置,找到私人令牌:

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

创建令牌完成了之后,把令牌内容保存下来,因为只显示一次。

Typora 图片上传命令行文档

上述工作完成之后,找到 Typora 图片上传命令行文档:

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

简单来讲,Typora 在你粘贴图片的时候会执行像这样的命令:

[some path]/upload-image.sh "image-path-1" "image-path-2"

这个命令后面跟着多个参数表示多个图片文件。

最后你需要在控制台打印,如下内容:

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

编写代码

在了解如上条件之后,我们接下来就开始编写代码。笔者使用 Go 开发这款工具,因为 Go 代码可以直接编译成二进制文件,正好符合 Typora 的要求。

定义响应体结构体类型:

package main  
  
type UploadFileResponse struct {  
    Commit Commit `json:"commit"`  
    Content Content `json:"content"`  
}  
  
type Commit struct {  
    Sha string `json:"sha,omitempty"`  
    Author string `json:"author,omitempty"`  
    Committer string `json:"committer,omitempty"`  
    Message string `json:"message,omitempty"`  
    Tree string `json:"tree,omitempty"`  
    Parents string `json:"parents,omitempty"`  
}  
  
type Content struct {  
    Name string `json:"name,omitempty"`  
    Path string `json:"path,omitempty"`  
    Size string `json:"size,omitempty"`  
    Sha string `json:"sha,omitempty"`  
    Type string `json:"type,omitempty"`  
    Url string `json:"url,omitempty"`  
    HtmlUrl string `json:"html_url,omitempty"`  
    DownloadUrl string `json:"download_url,omitempty"`  
    Links string `json:"_links,omitempty"`  
}

编写主函数:

package main  
  
import (  
    "bytes"  
    "encoding/base64"  
    "encoding/json"  
    "errors"  
    "fmt"  
    "io"  
    "log"  
    "net/http"  
    "net/url"  
    "os"  
    "text/template"  
    "time"  
)

const (  
    giteeUploadApiTemplate = "https://gitee.com/api/v5/repos/{{.owner}}/{{.repo}}/contents/{{.path}}"  
    owner = "Lob" // 用户名  
    repo = "image-bed" // 图床仓库名称  
    accessToken = "1ba4ac9113e5153bd128dc6" // accessToken改成你自己的  
    message = "图床上传图片"  
    branch = "master"  
)  
  
func uploadPic(path, content string) (string, error) {  
    tmplData := map[string]any{  
        "owner": owner,  
        "repo": repo,  
        "path": path,  
    }  

    formData := url.Values{  
        "access_token": {accessToken},  
        "content": {content},  
        "message": {message},  
        "branch": {branch},  
    }  
  
    tmpl, err := template.New("tmpl").Parse(giteeUploadApiTemplate)  
    if err != nil {  
        log.Println("Error parsing template:", err)  
        return "", errors.New("error parsing template")  
    }  
  
    var buf bytes.Buffer  
    err = tmpl.Execute(&buf, tmplData)  
    if err != nil {  
        log.Println("Error parsing template:", err)  
        return "", errors.New("error parsing template")  
    }  
    api := buf.String()  
  
    req, err := http.NewRequest(http.MethodPost, api, bytes.NewBufferString(formData.Encode()))  
    if err != nil {  
        log.Println("Error creating request:", err)  
        return "", errors.New("error creating request")  
    }  
  
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")  

    client := &http.Client{}  
    resp, err := client.Do(req)  
    defer resp.Body.Close()  
    if err != nil {  
        log.Println("Error sending request:", err)  
        return "", errors.New("error creating request")  
    }  
  
    byteArr, _ := io.ReadAll(resp.Body)  
    u := &UploadFileResponse{}  
    json.Unmarshal(byteArr, u)  
    return u.Content.DownloadUrl, nil  
}  
  
func main() {  
    parameters := os.Args[1:]  
    var picUrls []string  
    for _, parameter := range parameters {
        path := fmt.Sprintf("%s.png", time.Now().UnixMicro())  

        data, err := os.ReadFile(parameter)  
        if err != nil {  
            log.Printf("read file failed: %s", parameter)  
        }  
        content := base64.StdEncoding.EncodeToString(data)  

        picUrl, err := uploadPic(path, content)  
        if err != nil {  
            return  
        }  
        picUrls = append(picUrls, picUrl)  
    }  
    fmt.Println("Upload Success:")  
    for _, picUrl := range picUrls {  
        fmt.Println(picUrl)  
    }  
}

编译测试

go build -o /Users/element/go/bin/gitee-uploader

编译并保存到 GOPATH 下面,然后设置 Typora 的图片上传服务:

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

最后测试一下是否成功:

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

可以看见成功上传了。图片都在仓库里面:

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

阿里云 OSS

下面来介绍如何使用阿里云 OSS 来存储文件。阿里云 OSS 相较于 Gitee 平台传输时延更低、响应更快。

进入阿里云控制台

首先需要进入阿里云控制台,找到 OSS 服务。点击右下角的 Access Key 按钮,去创建 Access Key。

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

然后点击 API 文档,找到开发参考的 Go 的代码:

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

安装依赖

输入命令,安装阿里云 OSS 的 SDK 依赖:

go get github.com/aliyun/aliyun-oss-go-sdk

编写代码

package main  
  
import (  
    "context"  
    "fmt"  
    "os"  
    "time"  

    "github.com/aliyun/aliyun-oss-go-sdk/oss"  
)  
  
const (  
    endPoint = "https://oss-cn-shanghai.aliyuncs.com" // 接入点  
    accessKeyId = "LTAI4G3DN5kdk7jbe9hGfEuc" // 密钥ID  
    accessKeySecret = "7RzUrCyhsH1aAWFLRaAMtsONb3V0RG" // 密钥  
    bucketName = "codeart-oss" // 桶名  
    accessAddress = "https://codeart-oss.oss-cn-shanghai.aliyuncs.com" // 接入的地址  
)  
  
func main() {  
    parameters := os.Args[1:]  
    client, err := oss.New(endPoint, accessKeyId, accessKeySecret)  
    if err != nil {  
        fmt.Println("Error:", err)  
        os.Exit(-1)  
    }  

    bucket, err := client.Bucket(bucketName)  
    if err != nil {  
        fmt.Println("Error:", err)  
        os.Exit(-1)  
    }  

    ctx := context.Background()  
    // 指定请求上下文过期时间。  
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)  
    defer cancel()  

    // 将本地文件上传至OSS。  
    var picUrls []string  
    for _, parameter := range parameters {  
        objectKey := fmt.Sprintf("%d.png", time.Now().UnixMicro())  
        err = bucket.PutObjectFromFile(objectKey, parameter, oss.WithContext(ctx))  
        if err != nil {  
            select {  
            case <-ctx.Done():  
                fmt.Println("Request cancelled or timed out")  
            default:  
                fmt.Println("Upload fail, Error:", err)  
            }  
            continue  
        }  
        picUrl := fmt.Sprintf("%s/%s", accessAddress, objectKey)  
        picUrls = append(picUrls, picUrl)  
    }  
    fmt.Println("Upload Success:")  
    for _, picUrl := range picUrls {  
        fmt.Println(picUrl)  
    }  
}

编译测试

输入命令编译:

go build -o /Users/element/go/bin/aliyun-uploader

同样来测试一下是否成功:

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

图片可以在阿里云控制台看到:

开发一款Typora自定义图片上传命令行工具,上传到你自己的图床!

参考源码

源码地址:typora-pic-uploader: 使用 Go 编写的 Tyora 自定义图片上传命令行工具 (gitee.com)