UPGIT切换其他图床

先说说心路历程,哈哈哈。

其实昨天踩点发文章,也是因为在倒腾这些。

我之前写过一篇文章,通过使用upgit来自动上传typora的图片。

当时,用的是github,从这个项目的名字也能看出,它应该最开始就是为github写的,所以支持的最好。

其实github本身没什么大问题,但因为众所周知的原因,网络是个大问题,即便我挂了梯子,也会有不好使的时候。

所以,我准备换。

切换Gitee

看了一下它的可扩展列表

gitee码云,应该是最合适的替代品,首先它是国内的产品,其次,它也是一个基于git的版本控制工具。我可以直接把github上的项目克隆到gitee上,把我所有图片的前缀url换一下就好了。

于是,我美滋滋的开始换了,但是,gitee给了我一个巨大的惊喜,她说因为检查到我有许多图片外链,怀疑我使用它做图床,不允许我把这个项目设置成公开权限,也就是说,图片我可以上传,但是我不能访问。那我要它还有什么用?

切换七牛云

既然gitee用不了,我总得换,再次浏览了一遍扩展列表,就七牛云比较熟悉,其他的也不知道会不会满足我的要求。既然有一个确定能满足我要求的,那就直接用它得了。

然后,就开始了我的踩坑之旅,哈哈哈。

第一件事,就是配置文件的修改。

这块我觉得它文档写得不太详细,我是看了日志,然后又去扫了一遍源码,才知道是个怎么回事的。

按照正常逻辑,我觉得我把这个默认上传器给改了,然后把它相应的配置加上就能用了。

可惜并不能,会报错:

可能是因为一叶障目,我开始并没有意识到这个错误是说D:\Program Files\upgit\extensions这个文件夹找不到。我光看到冒号后面的信息了,我因为它说找不到要上传的文件,就上面灰色的那些,我去文件夹里看了一眼,是有的。

后来左看右看,在日志里终于是发现了问题,于是赶紧把文件夹加上吧。

但还是报错了:

我一时间觉得很费解,难道是我的名字输错啦?那要是输错了,应该输入什么呢?我也没什么头绪。

所幸去源码里搜了一下,这个unknown uploader是从哪里报出来的。

于是,终于发现了问题,它需要一个相关的配置文件。

并且源码里有:

赶紧把我需要的copy下来。(我感觉吧,这些配置文件,理论上应该直接放在release包里的,还要自己去建文件夹,复制配置文件,感觉怪怪的,文档里提一句也行。)

这时候,故事才刚刚开始,之前提到的github、gitee都可以创建一个永久的token,这样把token写进配置文件中,就再也不用改了。

但是,七牛云可能是因为上传是免费的,下载才要钱的原因,所以对上传把控的比较严,所以,并没有永久的token,token有时效,过了时间需要重新请求token。

那么问题来了,upgit只提供了配置token的方式,并不支持刷新token。

这个时候,我陷入了天人交战。

放弃upgit,自己写一套基于七牛云的文件上传的具?感觉又犯不着。继续使用upgit,修改一下七牛云的逻辑?但是upgit是用go语言写的,我对go并不熟。

几番权衡之下,我感觉还是直接改源码比较快,虽然,我对go不熟,但是,我看了一眼七牛云的官方文档,有对go语言的支持,获取token还挺简单的:

签名那些东西,不用我自己写。所以,改动还是比较小的。

我只用把配置文件里的token改成accessKeysecretKeybucket,然后在代码里把原来获取token的地方,换成我新生成的token就行了。

虽然,我对go的语法不熟,也不太明白整个upgit的运行逻辑和框架。但是,我其实只用改获取配置文件那一块就好了。

想好了,就开干吧。

修改upgit源码

还记得我前面说找unknown uploader报错从哪报的吗?就在这个方法里,虽然看不懂语法细节,但能大体知道这是在干嘛,根据uploaderId来判断要用哪个load,前面几个if应该都是之前特别实现的。

剩下的那些没有特别if的,都归到了extensions里面:

然后,我就照葫芦画瓢,这里抄点代码,哪里抄点代码,进行了如下修改:

首先,导包:

然后,判断一下,如果是使用七牛云,就获取token

然后,把token放进config里

从配置文件中,能看到token是通过这种方式获取的:

那我其实替换一下token的值就行了

(我到时候,直接把代码放到文章最末尾,省得排版太长)

至此,改动结束。

其实,我不是太敢做一些结构上的修改。现在这么改一点都不优雅,但小步子总是没错的,先验证自己的想法没有问题,再优化也好,现在的目的仅仅是正确地运行。

代码改完了,下一步就是编译啦,要让它变成可执行文件。

我看到根目录上有一个Makefile文件,但是windows并没有GCC编译工具,于是,要先安装,我装的是MinGW,参考的这篇文章,我就不详细说了:

https://blog.csdn.net/LinusZhao1018/article/details/82152960?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param

装好之后,到项目更目录,执行make命令,但是,报错了:

查了一下,没有很快找到答案,我也不觉得真的是语法的问题,很可能是系统的问题,可能我是windows系统,作者是在linux上运行的也说不定。懒得去研究makefile的语法问题了。

看得出来真正有意义的是go build……这句话,那我自己按照我的系统拼一条命令出来不就行啦?

于是就有了:

go build -o ./dist/upgit_win_amd64.exe -ldflags="-s -w" .

运行!

不出所料,又报错啦

ext_cmd.go:11:2: github.com/alexflint/go-arg@v1.4.3: Get "https://proxy.golang.org/github.com/alexflint/go-arg/@v/v1.4.3.zip": dial tcp 142.251.43.17:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.

连接超时,把我的梯子打开,也没有用。google了一下,看起来是很多人都会遇到的问题,把访问地址改成国内的就行:

go env -w GOPROXY=https://goproxy.cn

再次运行,还是报错:

ain.go:32:2: no required module provides package github.com/qiniu/go-sdk/v7/auth/qbox;to add it:
go get github.com/qiniu/go-sdk/v7/auth/qbox
main.go:33:5: no required module provides package github.com/qiniu/go-sdk/v7/storage;to add it:
go get github.com/qiniu/go-sdk/v7/storage

接着google,因为我没有在go.mod文件添加相应的require版本:

require github.com/qiniu/go-sdk/v7 v7.13.0

再次运行,还是报错,哈哈哈:

main.go:32:2: missing go.sum entry for module providing package github.com/qiniu/go-sdk/v7/auth/qbox (imported by github.com/pluveto/upgit);to add:
go get github.com/pluveto/upgit
main.go:33:5: missing go.sum entry for module providing package github.com/qiniu/go-sdk/v7/storage (imported by github.com/pluveto/upgit);to add:
go get github.com/pluveto/upgit

再次google,好像是光修改go.mod文件不够,还需要运行下面的命令,来更新:

go mod tidy

然后再次运行,虽然还是报错了,但和环境无关了,终于和我的代码有关了,我的语法有些问题。

这么简单分享一点,今天学到的go语法相关知识。

1.:==

:=表示初始化,并赋值

=表示单纯的赋值

2.go的方法,参数在前,类型在后

3.go可以返回多个返回值

原本下面这句话,我抄过来的时候,把err给去掉了

但是报错了:

# github.com/pluveto/upgit
.\main.go:329:16: assignment mismatch: 1 variable but xapp.LoadUploaderConfig[map[string]interface{}] returns 2 values

4.map值的获取与设置(跟python一样)

获取值:qiniuConfig["bucket"]

设置值:extConfig["token"]=upToken

5.go的类型转换是.(type)放在后面的形式

原本我是这么写的:

bucket:=qiniuConfig["bucket"]
accessKey:=qiniuConfig["accessKey"]
secretKey:=qiniuConfig["secretKey"]

但是报错了:

.\main.go:334:12: cannot use bucket (variable of type interface{}) as type string in struct literal:
need type assertion
.\main.go:336:22: cannot use accessKey (variable of type interface{}) as type string in argument to qbox.NewMac:
need type assertion
.\main.go:336:33: cannot use secretKey (variable of type interface{}) as type string in argument to qbox.NewMac:
need type assertion

于是,转成string类型:

bucket:=qiniuConfig["bucket"].(string)
accessKey:=qiniuConfig["accessKey"].(string)
secretKey:=qiniuConfig["secretKey"].(string)

至此,我的程序可算是成功编译了。

还有另外一个令人高兴的事情是,我一次就改成功了。

我把配置文件改好之后,就能正常使用了,这篇文章里的图片,就是传到七牛云的。

配置文件:

config.toml

# 默认上传器
default_uploader="qiniu"

# 七牛云存储
[uploaders.qiniu]
bucket="moqian-public"
accessKey="xxxxxxxxxxxxxxxxxxx"
secretKey="xxxxxxxxxxxxxxxxx"
#你的域名前缀
prefix="http://file.moqian.cn/"

qiniu.jsonc

这个其实用源码里的就好了,只有一个地方可能要改,这个url,上传失败的话,返回的信息会告诉你,应该用哪个url的。

最后放上main.go的代码:

package main

import(
"errors"
"fmt"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"

"github.com/alexflint/go-arg"
"github.com/pelletier/go-toml/v2"
"github.com/pluveto/upgit/lib/model"
"github.com/pluveto/upgit/lib/qcloudcos"
"github.com/pluveto/upgit/lib/result"
"github.com/pluveto/upgit/lib/uploaders"
"github.com/pluveto/upgit/lib/upyun"
"github.com/pluveto/upgit/lib/xapp"
"github.com/pluveto/upgit/lib/xclipboard"
"github.com/pluveto/upgit/lib/xext"
"github.com/pluveto/upgit/lib/xio"
"github.com/pluveto/upgit/lib/xlog"
"github.com/pluveto/upgit/lib/xmap"
"github.com/pluveto/upgit/lib/xpath"
"github.com/pluveto/upgit/lib/xstrings"
"golang.design/x/clipboard"
"gopkg.in/validator.v2"
"github.com/qiniu/go-sdk/v7/auth/qbox"
"github.com/qiniu/go-sdk/v7/storage"
)

func main(){
result.AbortErr = xlog.AbortErr
iflen(os.Args)>=2&& os.Args[1]=="ext"{
extSubcommand()
return
}
mainCommand()
}

func mainCommand(){
// parse cli args
loadCliOpts()

// load config
loadEnvConfig(&xapp.AppCfg)
loadConfig(&xapp.AppCfg)

xlog.GVerbose.TraceStruct(xapp.AppCfg)

// handle clipboard if need
loadClipboard()

// validating args
validArgs()

// executing uploading
dispatchUploader()

if xapp.AppOpt.Wait {
fmt.Scanln()
}

return
}

// loadCliOpts load cli options into xapp.AppOpt
func loadCliOpts(){
arg.MustParse(&xapp.AppOpt)
xapp.AppOpt.TargetDir = strings.Trim(xapp.AppOpt.TargetDir,"/")
xapp.AppOpt.ApplicationPath = strings.Trim(xapp.AppOpt.ApplicationPath,"/")
iflen(xapp.AppOpt.ApplicationPath)>0{
xpath.ApplicationPath = xapp.AppOpt.ApplicationPath
}
if xapp.AppOpt.SizeLimit !=nil&&*xapp.AppOpt.SizeLimit >=0{
xapp.MaxUploadSize =*xapp.AppOpt.SizeLimit
}
iffalse== xapp.AppOpt.NoLog {
xlog.GVerbose.LogEnabled =true
xlog.GVerbose.LogFile = xpath.MustGetApplicationPath("upgit.log")
xlog.GVerbose.LogFileMaxSize =2*1024*1024// 2MiB
xlog.GVerbose.Info("Started")
xlog.GVerbose.TruncatLog()
}
xlog.GVerbose.VerboseEnabled = xapp.AppOpt.Verbose
xlog.GVerbose.TraceStruct(xapp.AppOpt)
}

func onUploaded(r result.Result[*model.Task]){
if!r.Ok()&& xapp.AppOpt.OutputType == xapp.O_Stdout {
fmt.Println("Failed: "+ r.Err.Error())
return
}
if xapp.AppOpt.Clean &&!r.Value.Ignored {
err := os.Remove(r.Value.LocalPath)
if err !=nil{
xlog.GVerbose.Info("Failed to remove %s: %s", r.Value.LocalPath, err.Error())
}else{
xlog.GVerbose.Info("Removed %s", r.Value.LocalPath)
}

}
outputLink(*r.Value)
recordHistory(*r.Value)
}

func mustMarshall(s interface{})string{
b, err := toml.Marshal(s)
if err !=nil{
return""
}
returnstring(b)
}

func recordHistory(r model.Task){
xio.AppendToFile(xpath.MustGetApplicationPath("history.log"),[]byte(
`{"time":"`+time.Now().Local().String()+`","rawUrl":"`+r.RawUrl+`","url":"`+r.Url+`"}`+"\n"),
)

xlog.GVerbose.Info(mustMarshall(r))
}

func outputLink(r model.Task){
outContent, err := outputFormat(r)
xlog.AbortErr(err)
switch xapp.AppOpt.OutputType {
case xapp.O_Stdout:
fmt.Println(outContent)
case xapp.O_Clipboard:
clipboard.Write(clipboard.FmtText,[]byte(outContent))
default:
xlog.AbortErr(errors.New("unknown output type: "+string(xapp.AppOpt.OutputType)))
}
}

func outputFormat(r model.Task)(content string, err error){
var outUrl string
if xapp.AppOpt.Raw || r.Url ==""{
outUrl = r.RawUrl
}else{
outUrl = r.Url
}
fmt := xapp.AppOpt.OutputFormat
if fmt ==""{
return outUrl,nil
}
val, ok := xapp.AppCfg.OutputFormats[fmt]
if!ok {
return"", errors.New("unknown output format: "+ fmt)
}
content = strings.NewReplacer(
"{url}", outUrl,
"{urlfname}", filepath.Base(outUrl),
"{fname}", filepath.Base(r.LocalPath),
).Replace(xstrings.RemoveFmtUnderscore(val))

return
}

func validArgs(){
if errs := validator.Validate(xapp.AppCfg); errs !=nil{
xlog.AbortErr(fmt.Errorf("incorrect config: "+ errs.Error()))
}

for _, path :=range xapp.AppOpt.LocalPaths {
if strings.HasPrefix(path,"http"){
continue
}
fs, err := os.Stat(path)
if errors.Is(err, os.ErrNotExist){
xlog.AbortErr(fmt.Errorf("invalid file to upload %s: no such file", path))
}
if err !=nil{
xlog.AbortErr(fmt.Errorf("invalid file to upload %s: %s", path, err.Error()))
}
if fs.Size()==0{
xlog.AbortErr(fmt.Errorf("invalid file to upload %s: file size is zero", path))
}
if xapp.MaxUploadSize !=0&& fs.Size()> xapp.MaxUploadSize {
xlog.AbortErr(fmt.Errorf("invalid file to upload %s: file size is larger than %d bytes", path, xapp.MaxUploadSize))
}
}
}

// loadConfig loads config from config file to xapp.AppCfg
func loadConfig(cfg *xapp.Config){

homeDir, err := os.UserHomeDir()
if err !=nil{
homeDir =""
}

appDir := xpath.MustGetApplicationPath("")

var configFiles =map[string]bool{
filepath.Join(homeDir,".upgit.config.toml"):false,
filepath.Join(homeDir, filepath.Join(".config","upgitrc")):false,
filepath.Join(appDir,"config.toml"):false,
filepath.Join(appDir,"upgit.toml"):false,
}

if xapp.AppOpt.ConfigFile !=""{
configFiles[xapp.AppOpt.ConfigFile]=true
}

for configFile, required :=range configFiles {
if _, err := os.Stat(configFile); err !=nil{
if required {
xlog.AbortErr(fmt.Errorf("config file %s not found", configFile))
}
continue
}
optRawBytes, err := ioutil.ReadFile(configFile)
if err ==nil{
err = toml.Unmarshal(optRawBytes,&cfg)
}
if err !=nil{
xlog.AbortErr(fmt.Errorf("invalid config: "+ err.Error()))
}
xapp.ConfigFilePath = configFile
break
}

if xapp.ConfigFilePath ==""{
xlog.AbortErr(fmt.Errorf("no config file found"))
}

// fill config
xapp.AppCfg.Rename = strings.Trim(xapp.AppCfg.Rename,"/")
xapp.AppCfg.Rename = xstrings.RemoveFmtUnderscore(xapp.AppCfg.Rename)

// -- integrated formats
ifnil== xapp.AppCfg.OutputFormats {
xapp.AppCfg.OutputFormats =make(map[string]string)
}
xapp.AppCfg.OutputFormats["markdown"]=`![{url_fname}]({url})`
xapp.AppCfg.OutputFormats["url"]=`{url}`

}

// UploadAll will upload all given file to targetDir.
// If targetDir is not set, it will upload using rename rules.
func UploadAll(uploader model.Uploader, localPaths []string, targetDir string){
for taskId, localPath :=range localPaths {

var ret result.Result[*model.Task]
task := model.Task{
Status: model.TASK_CREATED,
TaskId: taskId,
LocalPath: localPath,
TargetDir: targetDir,
RawUrl:"",
Url:"",
CreateTime: time.Now(),
}
var err error
// ignore non-local path
if strings.HasPrefix(localPath,"http"){
task.Ignored =true
task.Status = model.TASK_FINISHED
}else{
err = uploader.Upload(&task)
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容