CUBERWR/BLOG

使用go语言实现picgo部分功能

2023.07.24

起因

之前使用picgo+telegraph代理+Obsidian插件image Auto Upload Plugin实现图片自动上传到telegraph图床,效果完美,但是需要nodejs运行环境,还要装一堆依赖,虽然我主力机因为搭建了各种开发环境本来就有nodejs环境,但是我的另一台轻薄本我是不想装这么多乱七八糟的东西了,于是就想着找一个解决方案

过程记录

我的要求:

  • 配合image Auto Upload Plugin实现图片上传
  • 不依赖外部运行环境,至少windows和linux通用

go语言恰好符合平台通用性要求,这里使用go语言实现,这里先来看看image Auto Upload Plugin如何运作,以下是操作picgo-core上传的核心逻辑代码

export class PicGoCoreUploader {
  async uploadFiles(fileList: Array<String>): Promise<any> {
    let command = `${cli} upload ${fileList
      .map(item => `"${item}"`)
      .join(" ")}`;
    const res = await this.exec(command);
    const splitList = res.split("\n");
    const splitListLength = splitList.length;
    const data = splitList.splice(splitListLength - 1 - length, length);
    if (res.includes("PicGo ERROR")) {
    
	    } else {
	      return {
	        success: true,
	        result: data,
	      };
	    }
    }
  }

  // PicGo-Core 上传处理
  async uploadFileByClipboard() {
    const res = await this.uploadByClip();
    const splitList = res.split("\n");
    const lastImage = getLastImage(splitList);
    if (lastImage) {
      return {
        code: 0,
        msg: "success",
        data: lastImage,
      };
    } else {
		//错误处理
    }
  }

  // PicGo-Core的剪切上传反馈
  async uploadByClip() {
    let command;
    if (this.settings.picgoCorePath) {
      command = `${this.settings.picgoCorePath} upload`;
    } else {
      command = `picgo upload`;
    }
    const res = await this.exec(command);
    return res;
  }
}

export function getLastImage(list: string[]) {
  const reversedList = list.reverse();
  let lastImage;
  reversedList.forEach(item => {
    if (item && item.startsWith("http")) {
      lastImage = item;
      return item;
    }
  });
  return lastImage;
}

大概的逻辑就是:从剪切板粘贴就使用child_process执行picgo upload,其他的就执行picgo upload xxx1.jpg xxx2.jpg,然后从控制台输出读取上传之后图片的网址,每行一个,从最后一行读取跟参数数量一样的行数作为图片网址,如果是剪切板上传就读取最后一个http开头的字符串

理解逻辑之后就可以开始写了:

package main
import (
    "bytes"
    "fmt"
    "image/png"
    "io"
    "io/ioutil"
    "mime/multipart"
    "net/http"
    "os"
    "strings"

    "golang.design/x/clipboard"
)

func uploadImage(file bytes.Buffer) {
    buf := new(bytes.Buffer)
    wr := multipart.NewWriter(buf)
    
    fw, err := wr.CreateFormFile("uploadfile", "image.png")
    if err != nil {
        panic(err)
    }
    io.Copy(fw, &file)
    wr.Close()

    req, err := http.NewRequest("POST", "https://telegra.ph/upload", buf)
    req.Header.Set("Content-Type", wr.FormDataContentType())

    client := &http.Client{}
    resp, err := client.Do(req)

    body, _ := ioutil.ReadAll(resp.Body)
    str := string(body)
    str = strings.TrimPrefix(str, `[{"src":"\/file\/`)
    str = strings.TrimSuffix(str, `"}]`)
    fmt.Println("https://telegra.ph/file/" + str)
}

func main() {
    if len(os.Args) == 2 && os.Args[1] == "upload" {
        var file bytes.Buffer
        img := clipboard.Read(clipboard.FmtImage)
        if img != nil {
            img, err := png.Decode(bytes.NewReader(img))
            if err != nil {
                panic(err)
            }
            png.Encode(&file, img)
        }
        uploadImage(file)
    } else if len(os.Args) > 2 && os.Args[1] == "upload" {
        for _, filePath := range os.Args[2:] {
            var file bytes.Buffer
            f, err := os.Open(filePath)
            if err != nil {
                panic(err)
            }
            defer f.Close()
            io.Copy(&file, f)
  
            uploadImage(file)
        }
    } else {
        fmt.Println("Usage: go run main.go upload [image1] [image2] ...")
    }
}

写完了,把编译出来的程序改名成picgo.exe或者linux下改成picgo放在path里,或者在image Auto Upload Plugin里面设置路径,粘贴和拖入图片自动上传功能使用正常

有需要的可以自己拿去编译一下用,如果你的网络访问telegraph有障碍,可以参考picgo+自建telegraph代理图床实现无限图片存储搭建自己的代理,再把程序中的域名换成你自己的,之后可能更完善些支持其他图床,暂时就不扔github了,也许未来某天会继续完善,但是目前已经够我用了,如果有你有自己的需求可以在这上面改改拿着用