原副标题:UPGIT转换其它图床
先说说个人经历,呵呵。
只不过昨晚蹲守发该文,也是即使在玩儿那些。
我以后写过一则该文,透过采用upgit来自动上载typora的相片。
彼时,用的是github,从那个工程项目的英文名字也能窥见,它如果最已经开始是为github写的,因此全力支持的最合适。
只不过github这类无所谓大难题,但即使不可否认的其原因,互联网是个大难题,即使我挂了栅栏,也会有不太好使的这时候。
因此,我预备换。
转换Gitee
看了呵呵它的可扩充条目
gitee码云,如果是合适的代替品,具体来说它是亚洲地区的商品,其二,它也是两个如前所述git的管理辅助工具辅助工具。我能间接把github上的工程项目布季夫到gitee上,把我大部份相片的后缀url换呵呵就好了。
只好,我Shahdol的已经开始换了,但,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不熟,但,我看了一眼七牛云的官方文档
签名那些东西,不用我自己写。因此,改动还是比较小的。
我只用把配置文件里的token改成accessKey、secretKey、bucket,然后在代码里
想好了,就开干吧。
修改upgit源码
还记得我前面说找unknown uploader报错从哪报的吗?就在那个方法里,虽然看不懂语法细节,但能大体知道这是在干嘛,根据uploaderId来判断要用哪个load,前面几个if如果都是以后特别实现的。
剩下的那些没有特别if的,都归到了extensions里面:
然后,我就照葫芦画瓢,这里抄点代码,哪里抄点代码,进行了如下修改:
具体来说,导包:
en
然后,把token放进config里
那我只不过替换呵呵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/[email protected]: 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(importedby 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);toadd:
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
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
funcloadCliOpts(){
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))
}
funcoutputLink(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{
ifrequired{
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”]=``
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(),
}
varerrerror
// ignore non-local path
if strings.HasPrefix(localPath,“http”){
task.Ignored =true
task.Status =model.TASK_FINISHED
}else{
err = uploader.Upload(&task)
}
if err !=nil{
task.Status = model.TASK_FAILED
ret =result.Result[*model.Task]{
Err: err,
}
}else{
ret = result.Result[*model.Task]{
Value:&task,
}
}
if err ==nil{
xlog.GVerbose.TraceStruct(ret.Value)
}
callback := uploader.GetCallback()
ifnil!= callback {
callback(ret)
}
}
}
funcdispatchUploader(){
uploaderId := xstrings.ValueOrDefault(xapp.AppOpt.Uploader, xapp.AppCfg.DefaultUploader)
xlog.GVerbose.Info(“uploader: “+ uploaderId)
if uploaderId ==“github”{
gCfg, err := xapp.LoadUploaderConfig[uploaders.GithubUploaderConfig](uploaderId)
xlog.AbortErr(err)
err = validator.Validate(&gCfg)
xlog.AbortErr(err)
iflen(gCfg.Branch)==0{
gCfg.Branch = xapp.DefaultBranch
}
uploader := uploaders.GithubUploader{Config: gCfg, OnTaskStatusChanged: onUploaded}
UploadAll(uploader, xapp.AppOpt.LocalPaths, xapp.AppOpt.TargetDir)
return
}
if uploaderId ==“qcloudcos”{
qCfg, err := xapp.LoadUploaderConfig[qcloudcos.COSConfig](uploaderId)
xlog.AbortErr(err)
err = validator.Validate(&qCfg)
xlog.AbortErr(err)
xlog.GVerbose.Trace(“qcloudcos config: “)
xlog.GVerbose.TraceStruct(&qCfg)
uploader := qcloudcos.COSUploader{Config: qCfg,OnTaskStatusChanged: onUploaded}
UploadAll(uploader, xapp.AppOpt.LocalPaths, xapp.AppOpt.TargetDir)
return
}
if uploaderId ==“upyun”{
ucfg, err := xapp.LoadUploaderConfig[upyun.UpyunConfig](uploaderId)
xlog.AbortErr(err)
err =validator.Validate(&ucfg)
xlog.AbortErr(err)
xlog.GVerbose.Trace(“qcloudcos config: “)
xlog.GVerbose.TraceStruct(&ucfg)
uploader := upyun.UpyunUploader{Config: ucfg, OnTaskStatusChanged: onUploaded}
UploadAll(uploader, xapp.AppOpt.LocalPaths, xapp.AppOpt.TargetDir)
return
}
upToken :=“”
if uploaderId ==“qiniu”{
qiniuConfig,err:=xapp.LoadUploaderConfig[map[string]interface{}](uploaderId)
xlog.AbortErr(err)
bucket:=qiniuConfig[“bucket”].(string)
accessKey:=qiniuConfig[“accessKey”].(string)
secretKey:=qiniuConfig[“secretKey”].(string)
putPolicy := storage.PutPolicy{
Scope: bucket,
}
mac := qbox.NewMac(accessKey, secretKey)
upToken =putPolicy.UploadToken(mac)
}
// try http simple uploader
// list file in ./extensions
extDir := xpath.MustGetApplicationPath(“extensions”)
info, err := ioutil.ReadDir(extDir)
xlog.AbortErr(err)
var uploader *uploaders.SimpleHttpUploader
for _, f :=range info {
fname := f.Name()
xlog.GVerbose.Trace(“found file %s”, fname)
if!strings.HasSuffix(fname,“.json”)&&!strings.HasSuffix(fname,“.jsonc”){
xlog.GVerbose.Trace(“ignored file %s”, fname)
continue
}
// load file to json
uploaderDef, err := xext.GetExtDefinitionInterface(extDir, fname)
xlog.AbortErr(err)
if result.From[string](xmap.GetDeep[string](uploaderDef,`meta.id`)).ValueOrExit()!=uploaderId{
continue
}
if result.From[string](xmap.GetDeep[string](uploaderDef,“meta.type”)).ValueOrExit()!=“simple-http-uploader”{
continue
}
uploader =&uploaders.SimpleHttpUploader{OnTaskStatusChanged:onUploaded, Definition: uploaderDef}
extConfig, err := xapp.LoadUploaderConfig[map[string]interface{}](uploaderId)
if err ==nil{
if uploaderId ==“qiniu”{
extConfig[“token”]=upToken
}
uploader.Config = extConfig
xlog.GVerbose.Trace(“uploader config:”)
xlog.GVerbose.TraceStruct(uploader.Config)
}else{
xlog.GVerbose.Trace(“no uploader config found”)
}
break
}
ifnil== uploader {
xlog.AbortErr(errors.New(“unknown uploader: “+uploaderId))
}
UploadAll(uploader, xapp.AppOpt.LocalPaths, xapp.AppOpt.TargetDir)
return
}
func loadClipboard(){
iflen(xapp.AppOpt.LocalPaths)==1&& strings.ToLower(xapp.AppOpt.LocalPaths[0])== xapp.ClipboardPlaceholder {
err:= clipboard.Init()
if err !=nil{
xlog.AbortErr(fmt.Errorf(“failed to init clipboard: “+ err.Error()))
}
tmpFileName:= fmt.Sprint(os.TempDir(),“/upgit_tmp_”, time.Now().UnixMicro(),“.png”)
buf := clipboard.Read(clipboard.FmtImage)
ifnil== buf {
// try second chance for Windows user. To adapt bitmap format (compatible with Snipaste)
if runtime.GOOS ==“windows”{
buf, err = xclipboard.ReadClipboardImage()
}
if err !=nil{
xlog.GVerbose.Error(“failed to read clipboard image: “+ err.Error())
}
}
ifnil== buf {
xlog.AbortErr(fmt.Errorf(“failed: no image in clipboard or unsupported format”))
}
os.WriteFile(tmpFileName, buf, os.FileMode(fs.ModePerm))
xapp.AppOpt.LocalPaths[0]= tmpFileName
xapp.AppOpt.Clean =true
}
}
func loadEnvConfig(cfg *xapp.Config){
ifnil== cfg {
xlog.AbortErr(fmt.Errorf(“unable to load env config: nil config”))
}
if rename, found :=syscall.Getenv(“UPGIT_RENAME”); found {
cfg.Rename = rename
}
}
func loadGithubUploaderEnvConfig(gCfg *uploaders.GithubUploaderConfig){
// TODO: Auto generate env key name and adapt for all uploaders
if pat, found :=syscall.Getenv(“GITHUB_TOKEN”); found {
gCfg.PAT = pat
}
if pat, found := syscall.Getenv(“UPGIT_TOKEN”); found {
gCfg.PAT = pat
}
if username, found := syscall.Getenv(“UPGIT_USERNAME”); found {
gCfg.Username = username
}
if repo,found:= syscall.Getenv(“UPGIT_REPO”); found {
gCfg.Repo = repo
}
if branch, found := syscall.Getenv(“UPGIT_BRANCH”); found {
gCfg.Branch = branch
}
}