diff --git a/README.md b/README.md index dda846a..54092e1 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,16 @@ When executed, the program reads the specified file and creates a multipart/form Upon a successful upload, the program outputs the URL to the uploaded file to the console and also **writes it to the clipboard**. +### Uploading multiple files + +When using `-dir` mode, the program will loop through all files in the directory provided and upload each one. Upon completing all uploads, the program will output a table with the files uploaded and the URL generated. This output will also be saved to a file in the source directory naned `upload.xxxxx.log`. + +The same functionality is available if more than one arg is provided (e.g. `npu file1.jpg file2.jpg`) but the log file will be stored in the executable's root directory. + ## To do -- [ ] Add support for multiple files -- [ ] Add support for directories +- [x] Add support for multiple files +- [x] Add support for directories - [ ] Add support for archiving files (with encryption) - [ ] Add configuration file diff --git a/src/npu/go.mod b/src/npu/go.mod index 60d0526..bcc9d41 100644 --- a/src/npu/go.mod +++ b/src/npu/go.mod @@ -5,6 +5,7 @@ go 1.20 require golang.design/x/clipboard v0.6.3 require ( + github.com/rodaine/table v1.1.0 // indirect golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect golang.org/x/mobile v0.0.0-20210716004757-34ab1303b554 // indirect diff --git a/src/npu/go.sum b/src/npu/go.sum index 4b3eb71..0c141db 100644 --- a/src/npu/go.sum +++ b/src/npu/go.sum @@ -1,4 +1,12 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rodaine/table v1.1.0 h1:/fUlCSdjamMY8VifdQRIu3VWZXYLY7QHFkVorS8NTr4= +github.com/rodaine/table v1.1.0/go.mod h1:Qu3q5wi1jTQD6B6HsP6szie/S4w1QUQ8pq22pz9iL8g= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.design/x/clipboard v0.6.3 h1:qIAjOL1yYLzfEclnPjaoUF4FTqmk4C57LB9MBz2QwCM= golang.design/x/clipboard v0.6.3/go.mod h1:kqBSweBP0/im4SZGGjLrppH0D400Hnfo5WbFKSNK8N4= @@ -39,3 +47,5 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/npu/npu.go b/src/npu/npu.go index 01ab4dd..0be1d57 100644 --- a/src/npu/npu.go +++ b/src/npu/npu.go @@ -2,25 +2,34 @@ package main import ( "bytes" + "errors" "flag" "fmt" "io" - "io/ioutil" "mime/multipart" "net/http" "os" "path/filepath" "strconv" + "strings" "time" + "github.com/rodaine/table" "golang.design/x/clipboard" ) +type uploadedFile struct { + fileName string + upUrl string +} + func main() { expires := flag.Int("expires", 0, "set expiration time for the uploaded file in hours") secret := flag.Bool("secret", false, "enable secret mode") + dirMode := flag.Bool("dir", false, "enable directory mode (non-recursive)") + flag.Usage = func() { - fmt.Fprintf(os.Stderr, "Usage: [-expires ] [-secret] \n") + fmt.Fprintf(os.Stderr, "Usage: [-expires ] [-secret] [-dir] \n") flag.PrintDefaults() fmt.Print("\n") os.Exit(1) @@ -31,12 +40,100 @@ func main() { if len(args) < 1 { flag.Usage() } - filePath := args[0] + // construct list of files to upload + filePaths := []string{} + dir := "./" + if *dirMode { + dir = strings.TrimSuffix(strings.TrimSuffix(args[0], "/"), "\\") // trim trailing slashes + + dirEntries, err := os.ReadDir(dir) + if err != nil { + fmt.Println("Failed to read files in directory:", err) + os.Exit(1) + } + + for _, entry := range dirEntries { + if !entry.IsDir() { + filePaths = append(filePaths, fmt.Sprintf("%s/%s", dir, entry.Name())) + } + } + } else { + for _, argPath := range args { + filePaths = append(filePaths, argPath) + } + } + + // upload files + uploadedFiles := []uploadedFile{} + for _, filePath := range filePaths { + responseBody, err := uploadFile(filePath, expires, secret) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if responseBody != nil { + upUrl := string(responseBody) + upUrl = strings.TrimSuffix(strings.TrimSuffix(upUrl, "\n"), "\r") + + uploadedFiles = append(uploadedFiles, uploadedFile{ + fileName: filepath.Base(filePath), + upUrl: upUrl, + }) + } + } + + if len(uploadedFiles) == 1 { + // original, single file upload + upUrl := uploadedFiles[0].upUrl + "\n" + fmt.Println(string(upUrl)) + clipboard.Write(clipboard.FmtText, []byte(upUrl)) + } else { + // for multiple file upload + printTblOutput(&uploadedFiles) + logFile, err := writeOutputToFile(dir, &uploadedFiles) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + fmt.Println("\nLog file saved:", logFile) + } +} + +func printTblOutput(uploadedFiles *[]uploadedFile) { + tbl := table.New("File", "| 0x0 Url") + for _, upFile := range *uploadedFiles { + tbl.AddRow(upFile.fileName, "| "+upFile.upUrl) + } + tbl.Print() +} + +func writeOutputToFile(dir string, uploadedFiles *[]uploadedFile) (logFile string, err error) { + logFile = fmt.Sprintf("%s/%s.%d.log", dir, "upload", time.Now().Unix()) + f, err := os.Create(logFile) + if err != nil { + return "", errors.New(fmt.Sprintf("Failed to save output log file: %v", err)) + } + defer f.Close() + + for _, upFile := range *uploadedFiles { + _, err = f.WriteString(fmt.Sprintf("%s: %s\r\n", upFile.fileName, upFile.upUrl)) + if err != nil { + return "", errors.New(fmt.Sprintf("Failed to write to log file: %v", err)) + } + } + + f.Sync() + + return logFile, nil +} + +func uploadFile(filePath string, expires *int, secret *bool) (responseBody []byte, err error) { file, err := os.Open(filePath) if err != nil { - fmt.Println("Failed to open file:", err) - os.Exit(1) + return nil, errors.New(fmt.Sprintf("Failed to open file: %v", err)) } defer file.Close() @@ -44,8 +141,7 @@ func main() { writer := multipart.NewWriter(body) part, err := writer.CreateFormFile("file", filepath.Base(filePath)) if err != nil { - fmt.Println("Failed to create form file:", err) - os.Exit(1) + return nil, errors.New(fmt.Sprintf("Failed to create form file: %v", err)) } io.Copy(part, file) @@ -62,33 +158,26 @@ func main() { err = writer.Close() if err != nil { - fmt.Println("Failed to close multipart writer:", err) - os.Exit(1) + return nil, errors.New(fmt.Sprintf("Failed to close multipart writer: %v", err)) } request, err := http.NewRequest("POST", "https://0x0.st", body) if err != nil { - fmt.Println("Failed to create HTTP request:", err) - os.Exit(1) + return nil, errors.New(fmt.Sprintf("Failed to create HTTP request: %v", err)) } request.Header.Set("Content-Type", writer.FormDataContentType()) client := &http.Client{} response, err := client.Do(request) if err != nil { - fmt.Println("Failed to send HTTP request:", err) - os.Exit(1) + return nil, errors.New(fmt.Sprintf("Failed to send HTTP request: %v", err)) } defer response.Body.Close() - responseBody, err := ioutil.ReadAll(response.Body) + responseBody, err = io.ReadAll(response.Body) if err != nil { - fmt.Println("Failed to read response body:", err) - os.Exit(1) + return nil, errors.New(fmt.Sprintf("Failed to read response body: %v", err)) } - if responseBody != nil { - fmt.Println(string(responseBody)) - clipboard.Write(clipboard.FmtText, []byte(string(responseBody))) - } + return responseBody, nil }