Typora的图床插件

前言

之前一直使用Typora + Picgo上传图片,但是在启动Picgo.exe是会卡死不动。然后最近想学习一下Go开发,那么就用Go开发一个上传图片到图床的插件。这里配合SM.MS图床

SM.MS的API

在SM.MS的官方文档里详细介绍了上传图片的API使用,详细可以参考https://doc.sm.ms/#api-Image-Upload。

API调用的地址为https://sm.ms/api/v2/

image-20240317183700083

上传功能

根据SM.MSAPI文档介绍,我们大致理一下思路,需要提供的条件如下

  • 地址:"https://sm.ms/api/v2/upload"
  • 方法:POST,请求
  • 头部
    • 头部类型默认为:multipart/form-data
    • 认证码为从SM.MS获取

image-20240317184207092

  • 参数一:需要上传的文件
  • 参数二:返回类型

因此首先需要利用Go语言发出POST请求,首选需要导入net/http库,从而可以使用http库。

  • 通过NewRequest函数生成一个请求
  • 创建Client结构体
  • 发送请求
  • 获取响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import(
"net/http"
"io"
)
...
//生成http请求
req, err := http.NewRequest("POST", url, body)
if err != nil {
fmt.Println("Error creating request:", err)
return ""
}
//创建Client结构体
client := &http.Client{}
//发送请求
resp, err := client.Do(req)

if err != nil {
fmt.Println("Error sending request:", err)
return ""
}
defer resp.Body.Close()
//获取响应,通过io库的ReadAll函数解析响应
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response:", err)
return ""
}
...

在发送请求前,我们需要构造符合条件的body以及header,首先从请求中取出Header在利用Set函数设置头部信息。

1
2
3
//设置Header
req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("Authorization", "xxxxxx")

body则需要将参数存储进去,首先存储图片文件,这里需要导入os库利用Open方法打开图片文件,defer关键字是当脱离当前函数域是会自动执行的操作。紧接着创建一段缓冲区,用于存放body。创建表单类型的写入对象,将文件名写入,紧接着将文件数据写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//打开图片路径获取文件
file, err := os.Open(imagePath)
if err != nil {
fmt.Println("Error opening file:", err)
}
defer file.Close()
//创建字节缓冲区,需要导入bytes库
body := &bytes.Buffer{}
//创建表单数据,需要导入mime/multipart库
writer := multipart.NewWriter(body)
//创建文件类型表单格式,将文件名写入
imagePart, err := writer.CreateFormFile("smfile", imagePath)
if (err != nil) {
fmt.Println("Error creating form file:", err)
return ""
}
//将文件数据拷贝到缓冲区中
_, err = io.Copy(imagePart, file)
if (err != nil) {
fmt.Println("Error writing file data to form field: ", err)
return ""
}

写入普通的参数则直接写入即可,利用WriteField函数。

1
2
3
4
5
6
7
8
9
10
11
//添加一个表单项
err = writer.WriteField("format", stringParam)
if err != nil {
fmt.Println("Error writing form field:", err)
return ""
}
err = writer.Close()
if err != nil {
fmt.Println("Error closing form writer:", err)
return ""
}

最后就是如何从响应中获取图片的路径,观察到在响应体中存在url的字段。

image-20240317190305639

首先我们在body设置了返回的类型为JSON,因此首要的是将响应解析为JSON格式。利用make创建映射,映射类型为string->任意类型,这里的interface{}是指空接口,类似与泛型,但是与泛型不同的是,它不会对类型进行校验。紧接着将响应包解析为JSON格式,这里的[]byte是定义字节的切片,类似于字节数组。

1
2
3
4
5
6
7
8
//创建一个键值对string->任意类型
responseJson := make(map[string]interface{})
//需要导入encoding/json库,将接收到的响应解析为json格式
err = json.Unmarshal([]byte(responseBody), &responseJson)
if err != nil{
fmt.Println("Error Json", err)
return ""
}

可以看到url是嵌入到message对象中的,因此是JSON格式中对象的对象。

1
2
3
4
5
6
7
8
9
10
11
12
//取出data对象,类型是映射对象
data, ok := responseJson["data"].(map[string]interface{})
if !ok {
fmt.Println("data does not exit")
return ""
}
//取出url
urlData, ok := data["url"].(string)
if !ok {
fmt.Println("url does not exit")
return ""
}

至此上传功能就完成了。

下载功能

这里下载的功能是指若Typora中的图床链接是远程的,想要将图片批量下载的本地所提供的功能。

首先是设置代理,因为像Github等图床链接不设置代理会因为网速的问题下载失败。

1
2
3
4
5
6
7
8
9
10
//需要导入net/url库
proxyURL, err := url.Parse("http://xxxx:xxx")
if err != nil{
fmt.Println("Error Set Proxy")
return ""
}
//用于设置全局的 HTTP 客户端传输配置,需要导入net/http库
http.DefaultTransport = &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}

设置完代理之后,就需要传入需要下载的图片路径,通过HttpGet请求获取,然后从响应中获取图片数据,这里为了方便,还指定了存放文件的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//发出Http请求
resp, err := http.Get(imageUrl)
if err != nil{
fmt.Println("Error Http", err)
return ""
}
defer resp.Body.Close()
//Base方法用户获取路径中的文件名,需要导入path/filepath库
fileName := filepath.Base(imageUrl)
//利用Create方法创建文件
file, err := os.Create(directoryPath + "\\" + fileName)
if err != nil{
fmt.Println("Error Create File", err)
return ""
}
defer file.Close()
//Copy用于拷贝数据
_ , err = io.Copy(file, resp.Body)
if err != nil{
fmt.Println("Error Copy Body ", err)
return ""
}

文件扫描功能

由于该工具是需要扫描文件,提取图片路径完成上传或者下载功能。

首先打开需要上传或者下载的文件,创建扫描器逐行提取内容,匹配图片的markdown格式,然后再次匹配获取括号内的内容,提取图片路径,将路径传入上传或下载功能,最后修改原本的图片路径为新路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
...
targetFile := os.Args[1]
//打开需要扫描的文件
file , err := os.Open(targetFile)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
...
//创建扫描器
scanner := bufio.NewScanner(file)
//创建正则表达式对象,获取![]()那一行,即图片格式
pattern := regexp.MustCompile(`!\[(.*?)\]\((.*?)\)`)

var newLines []string
//逐行扫描
for scanner.Scan() {
//获取每一行的内容
line := scanner.Text()
//使用正则表达式匹配适合的内容
if pattern.MatchString(line){
fmt.Println("Match found:", line)
//只提取()里的内容
re := regexp.MustCompile(`\(([^\(\)]*)\)`)
match := re.FindStringSubmatch(line)
//match[0]是原始内容
path := match[1]
...
//将原本图片的路径修改为上传或者下载后的路径
line = strings.Replace(line, match[1], url, -1)

Typora的图床插件
https://h0pe-ay.github.io/Typora-Image/
作者
hope
发布于
2024年3月18日
许可协议