├── README.md ├── go.mod ├── go.sum ├── hosts ├── anonfiles │ ├── anonfiles.go │ └── structs.go ├── catbox │ └── catbox.go ├── fileio │ ├── fileio.go │ └── structs.go ├── filemail │ ├── filemail.go │ └── structs.go ├── ftp │ ├── ftp.go │ └── structs.go ├── gofile │ ├── gofile.go │ └── structs.go ├── krakenfiles │ ├── krakenfiles.go │ └── structs.go ├── letsupload │ ├── letsupload.go │ └── structs.go ├── megaup │ ├── megaup.go │ └── structs.go ├── mixdrop │ ├── mixdrop.go │ └── structs.go ├── onefichier │ ├── 1fichier.go │ └── structs.go ├── pixeldrain │ ├── pixeldrain.go │ └── structs.go ├── racaty │ ├── racaty.go │ └── structs.go ├── transfersh │ └── transfersh.go ├── uguu │ ├── structs.go │ └── uguu.go ├── wetransfer │ ├── structs.go │ └── wetransfer.go ├── workupload │ ├── structs.go │ └── workupload.go └── zippyshare │ └── zippyshare.go ├── main.go └── utils ├── maps.go ├── structs.go └── utils.go /README.md: -------------------------------------------------------------------------------- 1 | # go-upload 2 | File uploader with support for multiple hosts and progress reporting written in Go. Large file-friendly. 3 | ![](https://i.imgur.com/Mtfn3pu.png) 4 | [Windows, Linux, macOS and Android binaries](https://github.com/Sorrow446/go-upload/releases) 5 | 6 | ## Usage 7 | Upload single file to anonfiles: 8 | `go-ul_x64.exe anonfiles -f G:\file.bin` 9 | 10 | Upload two files to anonfiles and catbox and write output template: 11 | `go-ul_x64.exe anonfiles catbox -f G:\file.bin G:\file2.bin -o urls.txt` 12 | 13 | Upload all files in `G:\stuff` to zippyshare recursively with a 500 kB/s limit and write output template: 14 | `go-ul_x64.exe zippyshare -d G:\stuff -r -o urls.txt -l 0.5` 15 | 16 | Upload a single file to FTP server to /x/y/ and overwrite it if it already exists. 17 | `go-ul_x64.exe ftp -f G:\file.bin -U ftp://myusername:mypassword@ftp.server.com:21/x/y/ -O` 18 | 19 | ``` 20 | Usage: go-ul_x64.exe [--outpath OUTPATH] [--wipe] [--files FILES] [--private] [--template TEMPLATE] [--overwrite] [--user USER] [--directories DIRECTORIES] [--recursive] [--speedlimit SPEEDLIMIT] HOSTS [HOSTS ...] 21 | 22 | Positional arguments: 23 | HOSTS Which hosts to upload to. 24 | 25 | Options: 26 | --outpath OUTPATH, -o OUTPATH 27 | Path of text file to write template to. It will be created if it doesn't already exist. 28 | --wipe, -w Wipe output text file on startup. 29 | --files FILES, -f FILES 30 | --private, -P *Set upload as private. 31 | --template TEMPLATE, -t TEMPLATE 32 | Output text file template. Vars: filename, filePath, fileUrl [default: # {{.filename}}\n{{.fileUrl}}\n] 33 | --overwrite, -O *Overwrite file on host if it already exists. 34 | --user USER, -u USER *User form for FTP. Folders will be created recursively if they don't already exist. 35 | --directories DIRECTORIES, -d DIRECTORIES 36 | --recursive, -r Include subdirectories. 37 | --speedlimit SPEEDLIMIT, -l SPEEDLIMIT 38 | *Upload speed limit in megabytes. Example: 0.5 = 500 kB/s, 1 = 1 MB/s, 1.5 = 1.5 MB/s. [default: -1] 39 | --joboutpath JOBOUTPATH, -j JOBOUTPATH 40 | Path of JSON to write jobs to. 41 | --help, -h display this help and exit 42 | ``` 43 | \* = Not supported for all hosts. 44 | 45 | ### Template 46 | 47 | Default: `# {{.filename}}\n{{.fileUrl}}\n` 48 | Output with the default template: 49 | ``` 50 | # 2.jpg 51 | https://anonfiles.com/Hde2H4F5ue/2_jpg 52 | ``` 53 | Vars: filename, filePath, fileUrl 54 | 55 | ## Supported hosts 56 | |Host|Argument|Size Limit| 57 | | --- | --- | --- | 58 | |[anonfiles](https://anonfiles.com/)|anonfiles|20 GB 59 | |[Catbox](https://catbox.moe/)|catbox|200 MB 60 | |[file.io](https://www.file.io/)|fileio|2 GB 61 | |[Filemail](https://www.filemail.com/)|filemail|5 GB 62 | |FTP|ftp|- 63 | |[Gofile](https://gofile.io/)|gofile|unlim 64 | |[KrakenFiles](https://krakenfiles.com/)|krakenfiles|1 GB 65 | |[LetsUpload](https://letsupload.io/)|letsupload|10 GB 66 | |[MegaUp](https://megaup.net/)|megaup|5 GB 67 | |[MixDrop](https://mixdrop.co/)|mixdrop|unlim 68 | |[pixeldrain](https://pixeldrain.com/)|pixeldrain|10 GB 69 | |[Racaty](https://racaty.net/)|racaty|10 GB 70 | |[transfer.sh](https://transfer.sh/)|transfersh|unlim 71 | |[Uguu](https://uguu.se/)|uguu|128 MB 72 | |[WeTransfer](https://wetransfer.com/)|wetransfer|2 GB 73 | |[workupload](https://workupload.com/)|workupload|2 GB 74 | |[zippyshare](https://www.zippyshare.com/)|zippyshare|500 MB 75 | 76 | Host arguments are case insensitive. 77 | 78 | ## For developers 79 | If you would like to use go-upload with your software, you can use the `-j` arg to have it write a jobs JSON to a specified path. 80 | 81 | It will only panic and return an exit code 1 if: 82 | 1. Setup fails (arg parsing, output text or job file setup). 83 | 2. A job fails to write. 84 | 85 | Example output: 86 | ```json 87 | { 88 | "jobs": [ 89 | { 90 | "url": "https://anonfiles.com/La53h1l3ye/1_gif", 91 | "host": "anonfiles", 92 | "filename": "1.gif", 93 | "file_path": "G:\\go\\ul_5\\1.gif", 94 | "ok": true, 95 | "error_text": "" 96 | }, 97 | { 98 | "url": "https://we.tl/t-tNBYrFyQhH", 99 | "host": "wetransfer", 100 | "filename": "1.gif", 101 | "file_path": "G:\\go\\ul_5\\1.gif", 102 | "ok": true, 103 | "error_text": "" 104 | } 105 | ] 106 | } 107 | ``` 108 | If a job file with the same path already exists, it will be wiped. 109 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/alexflint/go-arg v1.4.3 7 | github.com/dustin/go-humanize v1.0.0 8 | github.com/jlaffaye/ftp v0.1.0 9 | ) 10 | 11 | require ( 12 | github.com/alexflint/go-scalar v1.1.0 // indirect 13 | github.com/hashicorp/errwrap v1.0.0 // indirect 14 | github.com/hashicorp/go-multierror v1.1.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo= 2 | github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA= 3 | github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM= 4 | github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 9 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 10 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 11 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 12 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 13 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 14 | github.com/jlaffaye/ftp v0.1.0 h1:DLGExl5nBoSFoNshAUHwXAezXwXBvFdx7/qwhucWNSE= 15 | github.com/jlaffaye/ftp v0.1.0/go.mod h1:hhq4G4crv+nW2qXtNYcuzLeOudG92Ps37HEKeg2e3lE= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 20 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 21 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 22 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 23 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 24 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 25 | -------------------------------------------------------------------------------- /hosts/anonfiles/anonfiles.go: -------------------------------------------------------------------------------- 1 | package anonfiles 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "main/utils" 7 | ) 8 | 9 | const ( 10 | uploadUrl = "https://api.anonfiles.com/upload" 11 | referer = "https://anonfiles.com/" 12 | ) 13 | 14 | func upload(path string, size, byteLimit int64, headers map[string]string) (string, error) { 15 | respBody, err := utils.MultipartUpload(uploadUrl, path, "file", size, byteLimit, nil, nil, headers) 16 | if err != nil { 17 | return "", err 18 | } 19 | defer respBody.Close() 20 | var obj Upload 21 | err = json.NewDecoder(respBody).Decode(&obj) 22 | if err != nil { 23 | return "", err 24 | } 25 | if !obj.Status { 26 | return "", errors.New("Bad response. " + obj.Error.Type) 27 | } else if obj.Data.File.Metadata.Size.Bytes != size { 28 | return "", errors.New("Byte count mismatch.") 29 | } 30 | return obj.Data.File.URL.Full, nil 31 | } 32 | 33 | func Run(args *utils.Args, path string) (string, error) { 34 | size, err := utils.CheckSize(path, "20GB") 35 | if err != nil { 36 | return "", err 37 | } 38 | headers := map[string]string{ 39 | "Referer": referer, 40 | } 41 | fileUrl, err := upload(path, size, args.ByteLimit, headers) 42 | return fileUrl, err 43 | } 44 | -------------------------------------------------------------------------------- /hosts/anonfiles/structs.go: -------------------------------------------------------------------------------- 1 | package anonfiles 2 | 3 | type Upload struct { 4 | Status bool `json:"status"` 5 | Data struct { 6 | File struct { 7 | URL struct { 8 | Full string `json:"full"` 9 | Short string `json:"short"` 10 | } `json:"url"` 11 | Metadata struct { 12 | ID string `json:"id"` 13 | Name string `json:"name"` 14 | Size struct { 15 | Bytes int64 `json:"bytes"` 16 | Readable string `json:"readable"` 17 | } `json:"size"` 18 | } `json:"metadata"` 19 | } `json:"file"` 20 | } `json:"data"` 21 | Error struct { 22 | Message string `json:"message"` 23 | Type string `json:"type"` 24 | Code int `json:"code"` 25 | } `json:"error"` 26 | } 27 | -------------------------------------------------------------------------------- /hosts/catbox/catbox.go: -------------------------------------------------------------------------------- 1 | package catbox 2 | 3 | import ( 4 | "io" 5 | "main/utils" 6 | ) 7 | 8 | const ( 9 | uploadUrl = "https://catbox.moe/user/api.php" 10 | referer = "https://catbox.moe/" 11 | ) 12 | 13 | func upload(path string, size, byteLimit int64, formMap, headers map[string]string) (string, error) { 14 | respBody, err := utils.MultipartUpload(uploadUrl, path, "fileToUpload", size, byteLimit, formMap, nil, headers) 15 | if err != nil { 16 | return "", err 17 | } 18 | defer respBody.Close() 19 | bodyBytes, err := io.ReadAll(respBody) 20 | return string(bodyBytes), err 21 | } 22 | 23 | func Run(args *utils.Args, path string) (string, error) { 24 | size, err := utils.CheckSize(path, "200MB") 25 | if err != nil { 26 | return "", err 27 | } 28 | formMap := map[string]string{ 29 | "userhash": "", 30 | "reqtype": "fileupload", 31 | } 32 | headers := map[string]string{ 33 | "Referer": referer, 34 | } 35 | fileUrl, err := upload(path, size, args.ByteLimit, formMap, headers) 36 | return fileUrl, err 37 | } 38 | -------------------------------------------------------------------------------- /hosts/fileio/fileio.go: -------------------------------------------------------------------------------- 1 | package fileio 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "main/utils" 7 | ) 8 | 9 | const ( 10 | uploadUrl = "https://file.io/" 11 | referer = "https://www.file.io/" 12 | ) 13 | 14 | func upload(path string, size, byteLimit int64, headers map[string]string) (string, error) { 15 | respBody, err := utils.MultipartUpload(uploadUrl, path, "file", size, byteLimit, nil, nil, headers) 16 | if err != nil { 17 | return "", err 18 | } 19 | defer respBody.Close() 20 | var obj Upload 21 | err = json.NewDecoder(respBody).Decode(&obj) 22 | if err != nil { 23 | return "", err 24 | } 25 | if !obj.Success { 26 | return "", errors.New("Bad response.") 27 | } else if obj.Size != size { 28 | return "", errors.New("Byte count mismatch.") 29 | } 30 | return obj.Link, nil 31 | } 32 | 33 | func Run(args *utils.Args, path string) (string, error) { 34 | size, err := utils.CheckSize(path, "2GB") 35 | if err != nil { 36 | return "", err 37 | } 38 | headers := map[string]string{ 39 | "Referer": referer, 40 | } 41 | fileUrl, err := upload(path, size, args.ByteLimit, headers) 42 | return fileUrl, err 43 | } 44 | -------------------------------------------------------------------------------- /hosts/fileio/structs.go: -------------------------------------------------------------------------------- 1 | package fileio 2 | 3 | import "time" 4 | 5 | type Upload struct { 6 | Success bool `json:"success"` 7 | Status int `json:"status"` 8 | ID string `json:"id"` 9 | Key string `json:"key"` 10 | Name string `json:"name"` 11 | Link string `json:"link"` 12 | Private bool `json:"private"` 13 | Expires time.Time `json:"expires"` 14 | Downloads int `json:"downloads"` 15 | MaxDownloads int `json:"maxDownloads"` 16 | AutoDelete bool `json:"autoDelete"` 17 | Size int64 `json:"size"` 18 | MimeType string `json:"mimeType"` 19 | Created time.Time `json:"created"` 20 | Modified time.Time `json:"modified"` 21 | } 22 | -------------------------------------------------------------------------------- /hosts/filemail/filemail.go: -------------------------------------------------------------------------------- 1 | package filemail 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "main/utils" 7 | "strconv" 8 | ) 9 | 10 | const ( 11 | apiBase = "https://www.filemail.com/api/transfer/" 12 | referer = "https://www.filemail.com/" 13 | ) 14 | 15 | func initUpload(size int64, headers map[string]string) (*Initialize, error) { 16 | initUrl := apiBase + "initialize" 17 | params := map[string]string{ 18 | "sourcedetails": "fupload4.0 @ https://www.filemail.com/", 19 | "days": "7", 20 | "confirmation": "true", 21 | "transfersize": strconv.FormatInt(size, 10), 22 | } 23 | respBody, err := utils.DoPost(initUrl, params, headers, nil) 24 | if err != nil { 25 | return nil, err 26 | } 27 | defer respBody.Close() 28 | var obj Initialize 29 | err = json.NewDecoder(respBody).Decode(&obj) 30 | if err != nil { 31 | return nil, err 32 | } 33 | if obj.Responsestatus != "OK" { 34 | return nil, errors.New("Bad response.") 35 | } 36 | return &obj, nil 37 | } 38 | 39 | func upload(uploadUrl, path string, size, byteLimit int64, params, headers map[string]string) error { 40 | respBody, err := utils.MultipartUpload(uploadUrl, path, "file", size, byteLimit, nil, params, headers) 41 | if err != nil { 42 | return err 43 | } 44 | respBody.Close() 45 | return nil 46 | } 47 | 48 | func finalizeUpload(params, headers map[string]string) (string, error) { 49 | finalizeUrl := apiBase + "complete" 50 | params["failed"] = "false" 51 | respBody, err := utils.DoPost(finalizeUrl, params, headers, nil) 52 | if err != nil { 53 | return "", err 54 | } 55 | defer respBody.Close() 56 | var obj Finalize 57 | err = json.NewDecoder(respBody).Decode(&obj) 58 | if err != nil { 59 | return "", err 60 | } 61 | if obj.Responsestatus != "OK" { 62 | return "", errors.New("Bad response.") 63 | } 64 | return obj.Downloadurl, nil 65 | } 66 | 67 | func Run(args *utils.Args, path string) (string, error) { 68 | size, err := utils.CheckSize(path, "5GB") 69 | if err != nil { 70 | return "", err 71 | } 72 | headers := map[string]string{ 73 | "Referer": referer, 74 | "Source": "Web", 75 | } 76 | initMeta, err := initUpload(size, headers) 77 | if err != nil { 78 | return "", err 79 | } 80 | params := map[string]string{ 81 | "transferid": initMeta.Transferid, 82 | "transferkey": initMeta.Transferkey, 83 | } 84 | err = upload(initMeta.Transferurl, path, size, args.ByteLimit, params, headers) 85 | if err != nil { 86 | return "", err 87 | } 88 | fileUrl, err := finalizeUpload(params, headers) 89 | return fileUrl, err 90 | } 91 | -------------------------------------------------------------------------------- /hosts/filemail/structs.go: -------------------------------------------------------------------------------- 1 | package filemail 2 | 3 | type Initialize struct { 4 | Transferid string `json:"transferid"` 5 | Transferkey string `json:"transferkey"` 6 | Transferurl string `json:"transferurl"` 7 | Transferip string `json:"transferip"` 8 | Udpport int `json:"udpport"` 9 | Udpthreshold int `json:"udpthreshold"` 10 | Responsestatus string `json:"responsestatus"` 11 | } 12 | 13 | type Finalize struct { 14 | Downloadurl string `json:"downloadurl"` 15 | Responsestatus string `json:"responsestatus"` 16 | } 17 | -------------------------------------------------------------------------------- /hosts/ftp/ftp.go: -------------------------------------------------------------------------------- 1 | package ftp 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "main/utils" 8 | "net/url" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | "time" 13 | 14 | "github.com/dustin/go-humanize" 15 | "github.com/jlaffaye/ftp" 16 | _ftp "github.com/jlaffaye/ftp" 17 | ) 18 | 19 | func auth(host, username, password string) (*_ftp.ServerConn, error) { 20 | c, err := _ftp.Dial(host, _ftp.DialWithTimeout(time.Second*30)) 21 | if err != nil { 22 | return nil, err 23 | } 24 | err = c.Login(username, password) 25 | if err != nil { 26 | c.Quit() 27 | return nil, err 28 | } 29 | return c, nil 30 | } 31 | 32 | func fileExists(c *_ftp.ServerConn, path, filename string) (bool, error) { 33 | entries, err := c.List(path) 34 | if err != nil { 35 | return false, err 36 | } 37 | for _, entry := range entries { 38 | if entry.Type == _ftp.EntryTypeFile && entry.Name == filename { 39 | return true, nil 40 | } 41 | } 42 | return false, nil 43 | } 44 | 45 | func dirExists(c *ftp.ServerConn, directory string) (bool, error) { 46 | entries, err := c.List("") 47 | if err != nil { 48 | return false, err 49 | } 50 | for _, entry := range entries { 51 | if entry.Type == ftp.EntryTypeFolder && entry.Name == directory { 52 | return true, nil 53 | } 54 | } 55 | return false, nil 56 | } 57 | 58 | func upload(c *_ftp.ServerConn, path, filename string) error { 59 | defer fmt.Println("") 60 | f, err := os.Open(path) 61 | if err != nil { 62 | return err 63 | } 64 | defer f.Close() 65 | stat, err := f.Stat() 66 | if err != nil { 67 | return err 68 | } 69 | size := stat.Size() 70 | counter := &utils.WriteCounter{ 71 | Total: size, 72 | TotalStr: humanize.Bytes(uint64(size)), 73 | StartTime: time.Now().UnixMilli(), 74 | } 75 | err = c.Stor(filename, io.TeeReader(f, counter)) 76 | return err 77 | } 78 | 79 | func parseUrl(userString string) (*User, error) { 80 | if !strings.HasPrefix(userString, "ftp://") { 81 | userString = "ftp://" + userString 82 | } 83 | u, err := url.Parse(userString) 84 | if err != nil { 85 | return nil, err 86 | } 87 | host := u.Host 88 | path := u.Path 89 | userInfo := u.User 90 | username := userInfo.Username() 91 | password, _ := userInfo.Password() 92 | if host == "" { 93 | return nil, errors.New("Host required.") 94 | } else if username == "" { 95 | return nil, errors.New("Username required.") 96 | } else if password == "" { 97 | return nil, errors.New("Password required.") 98 | } 99 | if path == "/" { 100 | path = "" 101 | } 102 | user := &User{ 103 | Host: host, 104 | Username: username, 105 | Password: password, 106 | Path: path, 107 | } 108 | return user, nil 109 | } 110 | 111 | func makeDirRecur(c *ftp.ServerConn, path string) error { 112 | path = strings.Trim(path, "/") 113 | splitPath := strings.Split(path, "/") 114 | for _, dir := range splitPath { 115 | exists, err := dirExists(c, dir) 116 | if err != nil { 117 | return err 118 | } 119 | if !exists { 120 | err := c.MakeDir(dir) 121 | if err != nil { 122 | return err 123 | } 124 | } 125 | err = c.ChangeDir(dir) 126 | if err != nil { 127 | return err 128 | } 129 | } 130 | return nil 131 | } 132 | 133 | func Run(args *utils.Args, path string) (string, error) { 134 | filename := filepath.Base(path) 135 | if args.User == "" { 136 | return "", errors.New("User required (host, port, username and password).") 137 | } 138 | u, err := parseUrl(args.User) 139 | if err != nil { 140 | return "", err 141 | } 142 | c, err := auth(u.Host, u.Username, u.Password) 143 | if err != nil { 144 | return "", err 145 | } 146 | defer c.Quit() 147 | if u.Path != "" { 148 | err = makeDirRecur(c, u.Path) 149 | if err != nil { 150 | return "", err 151 | } 152 | } 153 | if !args.Overwrite { 154 | exists, err := fileExists(c, u.Path, filename) 155 | if err != nil { 156 | return "", err 157 | } 158 | if exists { 159 | return "", errors.New("File already exists on FTP. Use the -O flag to overwrite.") 160 | } 161 | } 162 | err = upload(c, path, filename) 163 | if err != nil { 164 | return "", err 165 | } 166 | outPath := u.Path + "/" + filename 167 | return outPath, nil 168 | } 169 | -------------------------------------------------------------------------------- /hosts/ftp/structs.go: -------------------------------------------------------------------------------- 1 | package ftp 2 | 3 | type User struct { 4 | Host string 5 | Username string 6 | Password string 7 | Path string 8 | } 9 | -------------------------------------------------------------------------------- /hosts/gofile/gofile.go: -------------------------------------------------------------------------------- 1 | package gofile 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "main/utils" 7 | ) 8 | 9 | const ( 10 | referer = "https://gofile.io/" 11 | serverUrl = "https://api.gofile.io/getServer" 12 | ) 13 | 14 | func getServer() (string, error) { 15 | respBody, err := utils.DoGet(serverUrl, nil, nil) 16 | if err != nil { 17 | return "", err 18 | } 19 | defer respBody.Close() 20 | var obj GetServer 21 | err = json.NewDecoder(respBody).Decode(&obj) 22 | if err != nil { 23 | return "", err 24 | } 25 | if obj.Status != "ok" { 26 | return "", errors.New("Bad response.") 27 | } 28 | return obj.Data.Server, nil 29 | } 30 | 31 | func upload(uploadUrl, path string, size, byteLimit int64, headers map[string]string) (string, error) { 32 | respBody, err := utils.MultipartUpload(uploadUrl, path, "file", size, byteLimit, nil, nil, headers) 33 | if err != nil { 34 | return "", err 35 | } 36 | defer respBody.Close() 37 | var obj Upload 38 | err = json.NewDecoder(respBody).Decode(&obj) 39 | if err != nil { 40 | return "", err 41 | } 42 | if obj.Status != "ok" { 43 | return "", errors.New("Bad response.") 44 | } 45 | return obj.Data.DownloadPage, nil 46 | } 47 | 48 | func Run(args *utils.Args, path string) (string, error) { 49 | size, err := utils.CheckSize(path, "unlim") 50 | if err != nil { 51 | return "", err 52 | } 53 | server, err := getServer() 54 | if err != nil { 55 | return "", err 56 | } 57 | uploadUrl := "https://" + server + ".gofile.io/uploadFile" 58 | headers := map[string]string{ 59 | "Referer": referer, 60 | } 61 | url, err := upload(uploadUrl, path, size, args.ByteLimit, headers) 62 | return url, err 63 | } 64 | -------------------------------------------------------------------------------- /hosts/gofile/structs.go: -------------------------------------------------------------------------------- 1 | package gofile 2 | 3 | type GetServer struct { 4 | Status string `json:"status"` 5 | Data struct { 6 | Server string `json:"server"` 7 | } `json:"data"` 8 | } 9 | 10 | type Upload struct { 11 | Status string `json:"status"` 12 | Data struct { 13 | DownloadPage string `json:"downloadPage"` 14 | Code string `json:"code"` 15 | ParentFolder string `json:"parentFolder"` 16 | FileID string `json:"fileId"` 17 | FileName string `json:"fileName"` 18 | Md5 string `json:"md5"` 19 | DirectLink string `json:"directLink"` 20 | Info string `json:"info"` 21 | } `json:"data"` 22 | } 23 | -------------------------------------------------------------------------------- /hosts/krakenfiles/krakenfiles.go: -------------------------------------------------------------------------------- 1 | package krakenfiles 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "main/utils" 8 | ) 9 | 10 | const ( 11 | referer = "https://krakenfiles.com/" 12 | uloadUrlRegexStr = `url: "([^"]+)"` 13 | ) 14 | 15 | func getUploadUrl() (string, error) { 16 | match, err := utils.FindHtmlSubmatch(referer, uloadUrlRegexStr) 17 | if err != nil { 18 | return "", err 19 | } 20 | if match == nil { 21 | return "", errors.New("No regex match.") 22 | } 23 | return match[1], nil 24 | } 25 | func upload(uploadUrl, path string, size, ByteLimit int64, headers map[string]string) (string, error) { 26 | respBody, err := utils.MultipartUpload(uploadUrl, path, "files[]", size, ByteLimit, nil, nil, headers) 27 | if err != nil { 28 | return "", err 29 | } 30 | defer respBody.Close() 31 | var obj Upload 32 | err = json.NewDecoder(respBody).Decode(&obj) 33 | if err != nil { 34 | return "", err 35 | } 36 | file := obj.Files[0] 37 | if file.Error != "" { 38 | return "", errors.New("Bad response: " + file.Error) 39 | } 40 | return referer + file.URL[1:], nil 41 | } 42 | 43 | func Run(args *utils.Args, path string) (string, error) { 44 | size, err := utils.CheckSize(path, "1GB") 45 | if err != nil { 46 | return "", err 47 | } 48 | uploadUrl, err := getUploadUrl() 49 | if err != nil { 50 | fmt.Println("Failed to get upload URL.") 51 | return "", err 52 | } 53 | headers := map[string]string{ 54 | "Referer": referer, 55 | } 56 | fileUrl, err := upload(uploadUrl, path, size, args.ByteLimit, headers) 57 | return fileUrl, err 58 | } 59 | -------------------------------------------------------------------------------- /hosts/krakenfiles/structs.go: -------------------------------------------------------------------------------- 1 | package krakenfiles 2 | 3 | type Upload struct { 4 | Files []struct { 5 | Name string `json:"name"` 6 | Size string `json:"size"` 7 | Error string `json:"error"` 8 | URL string `json:"url"` 9 | Hash string `json:"hash"` 10 | } `json:"files"` 11 | } 12 | -------------------------------------------------------------------------------- /hosts/letsupload/letsupload.go: -------------------------------------------------------------------------------- 1 | package letsupload 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "main/utils" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | const ( 13 | referer = "https://letsupload.io/" 14 | uploaderUrl = referer + "assets/js/uploader.js" 15 | uploadUrlRegexStr = `url: '([^']+)'` 16 | formDataRegexStr = `data.formData = {_sessionid: '([^']+)', cTracker: '([^']+)'` 17 | ) 18 | 19 | func getEpochStr() string { 20 | epoch := strconv.FormatInt(time.Now().Unix(), 10) 21 | return epoch 22 | } 23 | 24 | func extractMeta() (*Meta, error) { 25 | epoch := getEpochStr() 26 | html, err := utils.GetHtml(uploaderUrl + "?r=" + epoch) 27 | if err != nil { 28 | return nil, err 29 | } 30 | uploadUrlMatch := utils.FindStringSubmatch(html, uploadUrlRegexStr) 31 | if uploadUrlMatch == nil { 32 | return nil, errors.New("No regex match for upload URL.") 33 | } 34 | formDataMatch := utils.FindStringSubmatch(html, formDataRegexStr) 35 | if formDataMatch == nil { 36 | return nil, errors.New("No regex match for form data.") 37 | } 38 | meta := &Meta{ 39 | UploadURL: uploadUrlMatch[1], 40 | SessionID: formDataMatch[1], 41 | Tracker: formDataMatch[2], 42 | } 43 | return meta, nil 44 | } 45 | 46 | func upload(uploadUrl, path string, size, byteLimit int64, headers, formMap map[string]string) (string, error) { 47 | respBody, err := utils.MultipartUpload(uploadUrl, path, "files[]", size, byteLimit, formMap, nil, headers) 48 | if err != nil { 49 | return "", err 50 | } 51 | defer respBody.Close() 52 | var obj UploadResp 53 | err = json.NewDecoder(respBody).Decode(&obj) 54 | if err != nil { 55 | return "", err 56 | } 57 | file := obj[0] 58 | fileErr := file.Error 59 | if fileErr != nil { 60 | return "", errors.New("Bad response: " + fileErr.(string)) 61 | } 62 | if int64(file.Size) != size { 63 | return "", errors.New("Byte count mismatch.") 64 | } 65 | return file.URL, nil 66 | } 67 | 68 | func Run(args *utils.Args, path string) (string, error) { 69 | size, err := utils.CheckSize(path, "10GB") 70 | if err != nil { 71 | return "", err 72 | } 73 | meta, err := extractMeta() 74 | if err != nil { 75 | fmt.Println("Failed to extract meta.") 76 | return "", err 77 | } 78 | headers := map[string]string{ 79 | "Referer": referer, 80 | } 81 | formMap := map[string]string{ 82 | "_sessionid": meta.SessionID, 83 | "cTracker": meta.Tracker, 84 | "maxChunkSize": "100000000", 85 | "folderId": "-1", 86 | "uploadSource": "file_manager", 87 | } 88 | fileUrl, err := upload(meta.UploadURL, path, size, args.ByteLimit, headers, formMap) 89 | return fileUrl, err 90 | } 91 | -------------------------------------------------------------------------------- /hosts/letsupload/structs.go: -------------------------------------------------------------------------------- 1 | package letsupload 2 | 3 | type Meta struct { 4 | UploadURL string 5 | SessionID string 6 | Tracker string 7 | } 8 | 9 | type UploadResp []struct { 10 | Name string `json:"name"` 11 | Size int `json:"size"` 12 | Type string `json:"type"` 13 | Error interface{} `json:"error"` 14 | URL string `json:"url"` 15 | DeleteURL string `json:"delete_url"` 16 | InfoURL string `json:"info_url"` 17 | DeleteType string `json:"delete_type"` 18 | DeleteHash string `json:"delete_hash"` 19 | Hash string `json:"hash"` 20 | StatsURL string `json:"stats_url"` 21 | ShortURL string `json:"short_url"` 22 | FileID string `json:"file_id"` 23 | UniqueHash string `json:"unique_hash"` 24 | URLHTML string `json:"url_html"` 25 | URLBbcode string `json:"url_bbcode"` 26 | SuccessResultHTML string `json:"success_result_html"` 27 | } 28 | -------------------------------------------------------------------------------- /hosts/megaup/megaup.go: -------------------------------------------------------------------------------- 1 | package megaup 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "main/utils" 7 | "strconv" 8 | ) 9 | 10 | const ( 11 | referer = "https://megaup.net/" 12 | urlRegexString = `https://f\d{1,3}.megaup.net/core/page/ajax/file_upload_handler.ajax.php\?` + 13 | `r=megaup.net&p=https&csaKey1=[a-z\d]{64}&csaKey2=[a-z\d]{64}` 14 | sessTrackRegexString = `_sessionid: '([a-z\d]{26})'. cTracker: '([a-z\d]{32})'` 15 | ) 16 | 17 | func getUploadUrl() (string, string, string, error) { 18 | 19 | html, err := utils.GetHtml(referer) 20 | if err != nil { 21 | return "", "", "", err 22 | } 23 | match := utils.FindStringSubmatch(html, urlRegexString) 24 | if err != nil { 25 | return "", "", "", err 26 | } 27 | if match == nil { 28 | return "", "", "", errors.New("No regex match.") 29 | } 30 | 31 | match = utils.FindStringSubmatch(html, sessTrackRegexString) 32 | if err != nil { 33 | return "", "", "", err 34 | } 35 | if match == nil { 36 | return "", "", "", errors.New("No regex match.") 37 | } 38 | return match[0], match[1], match[2], nil 39 | } 40 | 41 | func upload(uploadUrl, path string, size, byteLimit int64, formMap, headers map[string]string) (string, error) { 42 | respBody, err := utils.MultipartUpload(uploadUrl, path, "files[]", size, byteLimit, formMap, nil, headers) 43 | if err != nil { 44 | return "", err 45 | } 46 | defer respBody.Close() 47 | var obj Upload 48 | err = json.NewDecoder(respBody).Decode(&obj) 49 | if err != nil { 50 | return "", err 51 | } 52 | returnedSize, err := strconv.ParseInt(obj[0].Size, 10, 64) 53 | if err != nil { 54 | return "", err 55 | } 56 | if obj[0].Error != nil { 57 | return "", errors.New("Bad response.") 58 | } else if returnedSize != size { 59 | return "", errors.New("Byte count mismatch.") 60 | } 61 | return obj[0].URL, nil 62 | } 63 | 64 | func Run(args *utils.Args, path string) (string, error) { 65 | uploadUrl, sessionId, tracker, err := getUploadUrl() 66 | if err != nil { 67 | return "", err 68 | } 69 | size, err := utils.CheckSize(path, "5GB") 70 | if err != nil { 71 | return "", err 72 | } 73 | formMap := map[string]string{ 74 | "_sessionid": sessionId, 75 | "cTracker": tracker, 76 | "folderId": "", 77 | "maxChunkSize": "100000000", 78 | } 79 | headers := map[string]string{ 80 | "Referer": referer, 81 | } 82 | fileUrl, err := upload(uploadUrl, path, size, args.ByteLimit, formMap, headers) 83 | return fileUrl, err 84 | } 85 | -------------------------------------------------------------------------------- /hosts/megaup/structs.go: -------------------------------------------------------------------------------- 1 | package megaup 2 | 3 | type Upload []struct { 4 | Name string `json:"name"` 5 | Size string `json:"size"` 6 | Type string `json:"type"` 7 | Error interface{} `json:"error"` 8 | URL string `json:"url"` 9 | DeleteURL string `json:"delete_url"` 10 | InfoURL string `json:"info_url"` 11 | DeleteType string `json:"delete_type"` 12 | DeleteHash string `json:"delete_hash"` 13 | Hash string `json:"hash"` 14 | StatsURL string `json:"stats_url"` 15 | ShortURL string `json:"short_url"` 16 | FileID string `json:"file_id"` 17 | UniqueHash string `json:"unique_hash"` 18 | URLHTML string `json:"url_html"` 19 | URLBbcode string `json:"url_bbcode"` 20 | SuccessResultHTML string `json:"success_result_html"` 21 | } 22 | -------------------------------------------------------------------------------- /hosts/mixdrop/mixdrop.go: -------------------------------------------------------------------------------- 1 | package mixdrop 2 | 3 | import ( 4 | "encoding/json" 5 | "main/utils" 6 | ) 7 | 8 | const ( 9 | referer = "https://mixdrop.co/" 10 | uploadUrl = "https://ul.mixdrop.co/up" 11 | ) 12 | 13 | func upload(path string, size, byteLimit int64, headers, formMap map[string]string) (string, error) { 14 | respBody, err := utils.MultipartUpload(uploadUrl, path, "files", size, byteLimit, formMap, nil, nil) 15 | if err != nil { 16 | return "", err 17 | } 18 | defer respBody.Close() 19 | var obj UploadResp 20 | err = json.NewDecoder(respBody).Decode(&obj) 21 | if err != nil { 22 | return "", err 23 | } 24 | return referer + "f/" + obj.File.Ref, nil 25 | } 26 | 27 | func Run(args *utils.Args, path string) (string, error) { 28 | size, err := utils.CheckSize(path, "unlim") 29 | if err != nil { 30 | return "", err 31 | } 32 | headers := map[string]string{ 33 | "Referer": referer, 34 | } 35 | formMap := map[string]string{ 36 | "upload": "1", 37 | } 38 | fileUrl, err := upload(path, size, args.ByteLimit, headers, formMap) 39 | return fileUrl, err 40 | } 41 | -------------------------------------------------------------------------------- /hosts/mixdrop/structs.go: -------------------------------------------------------------------------------- 1 | package mixdrop 2 | 3 | type UploadResp struct { 4 | File struct { 5 | Ref string `json:"ref"` 6 | } `json:"file"` 7 | } 8 | -------------------------------------------------------------------------------- /hosts/onefichier/1fichier.go: -------------------------------------------------------------------------------- 1 | // Needs content length header (size of file + form). MultipartUpload func doesn't support this yet. 2 | 3 | package onefichier 4 | 5 | import ( 6 | "encoding/json" 7 | "errors" 8 | "main/utils" 9 | "strconv" 10 | ) 11 | 12 | const apiBase = "https://www.filemail.com/api/transfer/" 13 | 14 | func getServer(headers map[string]string) (string, string, error) { 15 | url := "https://api.1fichier.com/v1/upload/get_upload_server.cgi" 16 | headers["Content-Type"] = "application/json" 17 | respBody, err := utils.DoGet(url, nil, headers) 18 | if err != nil { 19 | return "", "", err 20 | } 21 | defer respBody.Close() 22 | var obj GetServer 23 | err = json.NewDecoder(respBody).Decode(&obj) 24 | if err != nil { 25 | return "", "", err 26 | } 27 | return "https://" + obj.URL, obj.ID, nil 28 | } 29 | 30 | func upload(uploadUrl, path, id string, size int64, headers map[string]string) error { 31 | uploadUrl += "/upload.cgi" 32 | params := map[string]string{ 33 | "id": id, 34 | } 35 | formMap := map[string]string{ 36 | "send_ssl": "on", 37 | "domain": "0", 38 | "mail": "", 39 | "dpass": "", 40 | "user": "", 41 | "mails": "", 42 | "message": "", 43 | "submit": "Send", 44 | } 45 | respBody, err := utils.MultipartUpload(uploadUrl, path, "file[]", size, formMap, params, headers) 46 | if err != nil { 47 | return err 48 | } 49 | respBody.Close() 50 | return nil 51 | } 52 | 53 | func finalizeUpload(finalizeUrl, id string, size int64, headers map[string]string) (string, error) { 54 | params := map[string]string{ 55 | "xid": id, 56 | } 57 | finalizeUrl += "/end.pl" 58 | headers["Content-Type"] = "application/json" 59 | respBody, err := utils.DoGet(finalizeUrl, params, headers) 60 | if err != nil { 61 | return "", err 62 | } 63 | defer respBody.Close() 64 | var obj Finalize 65 | err = json.NewDecoder(respBody).Decode(&obj) 66 | if err != nil { 67 | return "", err 68 | } 69 | returnedSize, err := strconv.ParseInt(obj.Links[0].Size, 10, 64) 70 | if err != nil { 71 | return "", err 72 | } 73 | if returnedSize != size { 74 | return "", errors.New("Byte count mismatch.") 75 | } 76 | return obj.Links[0].Download, nil 77 | } 78 | 79 | func Run(args *utils.Args, path string) (string, error) { 80 | size, err := utils.CheckSize(path, "300GB") 81 | if err != nil { 82 | return "", err 83 | } 84 | headers := map[string]string{ 85 | "Referer": "https://1fichier.com/", 86 | } 87 | server, id, err := getServer(headers) 88 | if err != nil { 89 | return "", err 90 | } 91 | err = upload(server, path, id, size, headers) 92 | if err != nil { 93 | return "", err 94 | } 95 | fileUrl, err := finalizeUpload(server, id, size, headers) 96 | return fileUrl, err 97 | } 98 | -------------------------------------------------------------------------------- /hosts/onefichier/structs.go: -------------------------------------------------------------------------------- 1 | package onefichier 2 | 3 | type GetServer struct { 4 | ID string `json:"id"` 5 | URL string `json:"url"` 6 | } 7 | 8 | type Finalize struct { 9 | Incoming int `json:"incoming"` 10 | Links []struct { 11 | Download string `json:"download"` 12 | Filename string `json:"filename"` 13 | Remove string `json:"remove"` 14 | Size string `json:"size"` 15 | Whirlpool string `json:"whirlpool"` 16 | } `json:"links"` 17 | } 18 | -------------------------------------------------------------------------------- /hosts/pixeldrain/pixeldrain.go: -------------------------------------------------------------------------------- 1 | package pixeldrain 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "main/utils" 7 | ) 8 | 9 | const ( 10 | referer = "https://pixeldrain.com/" 11 | uploadUrl = referer + "api/file" 12 | ) 13 | 14 | func upload(path string, size, byteLimit int64, headers map[string]string) (string, error) { 15 | respBody, err := utils.MultipartUpload(uploadUrl, path, "file", size, byteLimit, nil, nil, headers) 16 | if err != nil { 17 | return "", err 18 | } 19 | defer respBody.Close() 20 | var obj Upload 21 | err = json.NewDecoder(respBody).Decode(&obj) 22 | if err != nil { 23 | return "", err 24 | } 25 | if !obj.Success { 26 | return "", errors.New("Bad response.") 27 | } 28 | url := referer + "u/" + obj.ID 29 | return url, nil 30 | } 31 | 32 | func Run(args *utils.Args, path string) (string, error) { 33 | size, err := utils.CheckSize(path, "10GB") 34 | if err != nil { 35 | return "", err 36 | } 37 | headers := map[string]string{ 38 | "Referer": referer, 39 | } 40 | fileUrl, err := upload(path, size, args.ByteLimit, headers) 41 | return fileUrl, err 42 | } 43 | -------------------------------------------------------------------------------- /hosts/pixeldrain/structs.go: -------------------------------------------------------------------------------- 1 | package pixeldrain 2 | 3 | type Upload struct { 4 | Success bool `json:"success"` 5 | ID string `json:"id"` 6 | } 7 | -------------------------------------------------------------------------------- /hosts/racaty/racaty.go: -------------------------------------------------------------------------------- 1 | package racaty 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "main/utils" 8 | ) 9 | 10 | const ( 11 | referer = "https://racaty.net/" 12 | uloadUrlRegexStr = `
wc.Total { 61 | wc.Uploaded = wc.Total 62 | wc.Percentage = 100 63 | } 64 | toDivideBy := time.Now().UnixMilli() - wc.StartTime 65 | if toDivideBy != 0 { 66 | speed = int64(wc.Uploaded) / toDivideBy * 1000 67 | } 68 | fmt.Printf("\r%d%% @ %s/s, %s/%s ", wc.Percentage, humanize.Bytes(uint64(speed)), 69 | humanize.Bytes(uint64(wc.Uploaded)), wc.TotalStr) 70 | return n, nil 71 | } 72 | 73 | func getCsrfToken() (string, error) { 74 | req, err := client.Get(referer) 75 | if err != nil { 76 | return "", err 77 | } 78 | defer req.Body.Close() 79 | if req.StatusCode != http.StatusOK { 80 | return "", errors.New(req.Status) 81 | } 82 | bodyBytes, err := ioutil.ReadAll(req.Body) 83 | if err != nil { 84 | return "", err 85 | } 86 | regex := regexp.MustCompile(csrfTokenStr) 87 | match := regex.FindStringSubmatch(string(bodyBytes)) 88 | if match == nil { 89 | return "", errors.New("No regex match.") 90 | } 91 | return match[1], nil 92 | } 93 | 94 | func addHeaders(req *http.Request, csrfToken string) { 95 | req.Header.Add("Content-Type", "application/json") 96 | req.Header.Add("X-CSRF-Token", csrfToken) 97 | req.Header.Add("X-Requested-With", "XMLHttpRequest") 98 | } 99 | 100 | func initiate(csrfToken string, _file *File) (string, error) { 101 | _file.ItemType = "file" 102 | postData := InitPost{ 103 | DisplayName: _file.Name, 104 | Message: "", 105 | UILanguage: "en", 106 | Files: []File{*_file}, 107 | } 108 | m, err := json.Marshal(postData) 109 | if err != nil { 110 | return "", err 111 | } 112 | req, err := http.NewRequest(http.MethodPost, apiLinkUrl, bytes.NewBuffer(m)) 113 | if err != nil { 114 | return "", err 115 | } 116 | addHeaders(req, csrfToken) 117 | do, err := client.Do(req) 118 | if err != nil { 119 | return "", err 120 | } 121 | defer do.Body.Close() 122 | if do.StatusCode != http.StatusOK { 123 | return "", errors.New(do.Status) 124 | } 125 | var obj FileMetaResp 126 | err = json.NewDecoder(do.Body).Decode(&obj) 127 | if err != nil { 128 | return "", err 129 | } 130 | if obj.State != "processing" { 131 | return "", errors.New("Invalid state.") 132 | } 133 | return obj.ID, nil 134 | } 135 | 136 | func getFileId(csrfToken, transferId string, _file *File) (string, error) { 137 | _url := apiBase + transferId + "/files" 138 | m, err := json.Marshal(&_file) 139 | if err != nil { 140 | return "", err 141 | } 142 | req, err := http.NewRequest(http.MethodPost, _url, bytes.NewBuffer(m)) 143 | if err != nil { 144 | return "", err 145 | } 146 | addHeaders(req, csrfToken) 147 | do, err := client.Do(req) 148 | if err != nil { 149 | return "", err 150 | } 151 | defer do.Body.Close() 152 | if do.StatusCode != http.StatusOK { 153 | return "", errors.New(do.Status) 154 | } 155 | var obj File 156 | err = json.NewDecoder(do.Body).Decode(&obj) 157 | if err != nil { 158 | return "", err 159 | } 160 | return obj.ID, nil 161 | } 162 | 163 | func uploadChunks(csrfToken, transferId, fileId, filePath string, size int64) (int, error) { 164 | _url := apiBase + transferId + "/files/" + fileId + "/part-put-url" 165 | f, err := os.OpenFile(filePath, os.O_RDONLY, 0755) 166 | if err != nil { 167 | return -1, err 168 | } 169 | defer f.Close() 170 | reader := bufio.NewReader(f) 171 | chunk := make([]byte, defaultChunkSize) 172 | chunkNum := 0 173 | totalChunkSize := 0 174 | counter := &WriteCounter{ 175 | Total: size, 176 | TotalStr: humanize.Bytes(uint64(size)), 177 | StartTime: time.Now().UnixMilli(), 178 | } 179 | for { 180 | chunkSize, err := reader.Read(chunk) 181 | if chunkNum == 0 { 182 | defer fmt.Println("") 183 | counter.printProgress(0) 184 | } 185 | totalChunkSize += chunkSize 186 | if errors.Is(err, io.EOF) || chunkSize == 0 { 187 | break 188 | } else if err != nil { 189 | return -1, err 190 | } 191 | chunkNum++ 192 | postData := Chunk{ 193 | ChunkSize: chunkSize, 194 | ChunkNumber: chunkNum, 195 | ChunkCrc: crc32.ChecksumIEEE(chunk[:chunkSize]), 196 | Retries: 0, 197 | } 198 | m, err := json.Marshal(postData) 199 | if err != nil { 200 | return -1, err 201 | } 202 | req, err := http.NewRequest(http.MethodPost, _url, bytes.NewBuffer(m)) 203 | if err != nil { 204 | return -1, err 205 | } 206 | addHeaders(req, csrfToken) 207 | do, err := client.Do(req) 208 | if err != nil { 209 | return -1, err 210 | } 211 | if do.StatusCode != http.StatusOK { 212 | do.Body.Close() 213 | return -1, errors.New(do.Status) 214 | } 215 | var obj FilePut 216 | err = json.NewDecoder(do.Body).Decode(&obj) 217 | do.Body.Close() 218 | if err != nil { 219 | return -1, err 220 | } 221 | // Using TeeReader here causes 501s. 222 | req2, err := http.NewRequest(http.MethodPut, obj.URL, bytes.NewBuffer(chunk[:chunkSize])) 223 | if err != nil { 224 | return -1, err 225 | } 226 | req2.Header.Set("Content-Type", "binary/octet-stream") 227 | req2.Header.Set("Content-Length", strconv.Itoa(chunkSize)) 228 | do2, err := client.Do(req2) 229 | if err != nil { 230 | return -1, err 231 | } 232 | do2.Body.Close() 233 | if do2.StatusCode != http.StatusOK { 234 | return -1, errors.New(do2.Status) 235 | } 236 | counter.printProgress(chunkSize) 237 | } 238 | return chunkNum, nil 239 | } 240 | 241 | func finalise(csrfToken, transferId, fileId string, chunkCount int, fileSize int64) (string, error) { 242 | _url := apiBase + transferId + "/files/" + fileId + "/finalize-mpp" 243 | postData := FinaliseMppPost{ 244 | ChunkCount: chunkCount, 245 | } 246 | m, err := json.Marshal(postData) 247 | if err != nil { 248 | return "", err 249 | } 250 | req, err := http.NewRequest(http.MethodPut, _url, bytes.NewBuffer(m)) 251 | if err != nil { 252 | return "", err 253 | } 254 | addHeaders(req, csrfToken) 255 | do, err := client.Do(req) 256 | if err != nil { 257 | return "", err 258 | } 259 | defer do.Body.Close() 260 | if do.StatusCode != http.StatusOK { 261 | return "", errors.New(do.Status) 262 | } 263 | _url2 := apiBase + "/" + transferId + "/finalize" 264 | req2, err := http.NewRequest(http.MethodPut, _url2, nil) 265 | if err != nil { 266 | return "", err 267 | } 268 | addHeaders(req2, csrfToken) 269 | do2, err := client.Do(req2) 270 | if err != nil { 271 | return "", err 272 | } 273 | defer do2.Body.Close() 274 | if do2.StatusCode != http.StatusOK { 275 | return "", errors.New(do2.Status) 276 | } 277 | var obj FileMetaResp 278 | err = json.NewDecoder(do2.Body).Decode(&obj) 279 | if err != nil { 280 | return "", err 281 | } 282 | if obj.Files[0].Size != fileSize { 283 | return "", errors.New("Byte count mismatch.") 284 | } 285 | return obj.ShortenedURL.(string), nil 286 | } 287 | 288 | func Run(args *utils.Args, path string) (string, error) { 289 | size, err := utils.CheckSize(path, "2GB") 290 | if err != nil { 291 | return "", err 292 | } 293 | csrfToken, err := getCsrfToken() 294 | if err != nil { 295 | fmt.Println("Failed to get CSRF token.") 296 | return "", err 297 | } 298 | _file := &File{ 299 | Name: filepath.Base(path), 300 | Size: size, 301 | } 302 | transferId, err := initiate(csrfToken, _file) 303 | if err != nil { 304 | fmt.Println("Failed to initiate upload.") 305 | return "", err 306 | } 307 | fileId, err := getFileId(csrfToken, transferId, _file) 308 | if err != nil { 309 | fmt.Println("Failed to get file ID.") 310 | return "", err 311 | } 312 | chunkCount, err := uploadChunks(csrfToken, transferId, fileId, path, size) 313 | if err != nil { 314 | fmt.Println("Failed to upload file chunks.") 315 | return "", err 316 | } 317 | fileUrl, err := finalise(csrfToken, transferId, fileId, chunkCount, size) 318 | if err != nil { 319 | fmt.Println("Failed to get finalise upload.") 320 | return "", err 321 | } 322 | return fileUrl, err 323 | } 324 | -------------------------------------------------------------------------------- /hosts/workupload/structs.go: -------------------------------------------------------------------------------- 1 | package workupload 2 | 3 | type GetServerResp struct { 4 | Success bool `json:"success"` 5 | Data struct { 6 | Server string `json:"server"` 7 | } `json:"data"` 8 | } 9 | 10 | type UploadResp struct { 11 | Files []struct { 12 | Key string `json:"key"` 13 | Name string `json:"name"` 14 | Size int64 `json:"size"` 15 | Time struct { 16 | Date string `json:"date"` 17 | TimezoneType int `json:"timezone_type"` 18 | Timezone string `json:"timezone"` 19 | } `json:"time"` 20 | Type string `json:"type"` 21 | Downloads int `json:"downloads"` 22 | Permission int `json:"permission"` 23 | Expiration bool `json:"expiration"` 24 | Password bool `json:"password"` 25 | MaxDownloads bool `json:"maxDownloads"` 26 | Comment bool `json:"comment"` 27 | } `json:"files"` 28 | } 29 | -------------------------------------------------------------------------------- /hosts/workupload/workupload.go: -------------------------------------------------------------------------------- 1 | package workupload 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "main/utils" 8 | "net/url" 9 | ) 10 | 11 | const ( 12 | referer = "https://workupload.com/" 13 | getServerUrl = referer + "api/file/getUploadServer" 14 | finaliseUrl = referer + "generateLink" 15 | fileBagRegexStr = `name="filebag" value="([^"]+)"` 16 | ) 17 | 18 | // Token is also in the html. File bag is epoch big endian + ?. 19 | func getTokenAndBag() (string, string, error) { 20 | var token string 21 | respBody, err := utils.GetHtml(referer) 22 | if err != nil { 23 | return "", "", err 24 | } 25 | u, err := url.Parse(referer) 26 | if err != nil { 27 | return "", "", err 28 | } 29 | for _, c := range utils.GetCookies(u) { 30 | if c.Name == "token" { 31 | token = c.Value 32 | break 33 | } 34 | } 35 | if token == "" { 36 | return "", "", errors.New("The server didn't set the token cookie.") 37 | } 38 | match := utils.FindStringSubmatch(respBody, fileBagRegexStr) 39 | if match == nil { 40 | return "", "", errors.New("No regex match for file bag.") 41 | } 42 | return token, match[1], nil 43 | } 44 | 45 | func getServer() (string, error) { 46 | headers := map[string]string{ 47 | "Referer": referer, 48 | } 49 | respBody, err := utils.DoGet(getServerUrl, nil, headers) 50 | if err != nil { 51 | return "", err 52 | } 53 | defer respBody.Close() 54 | var obj GetServerResp 55 | err = json.NewDecoder(respBody).Decode(&obj) 56 | if err != nil { 57 | return "", err 58 | } 59 | if !obj.Success { 60 | return "", errors.New("Bad response.") 61 | } 62 | return obj.Data.Server, nil 63 | } 64 | 65 | func upload(uploadUrl, path string, size, byteLimit int64, headers, formMap map[string]string) (string, error) { 66 | respBody, err := utils.MultipartUpload(uploadUrl, path, "files[]", size, byteLimit, formMap, nil, headers) 67 | if err != nil { 68 | return "", err 69 | } 70 | defer respBody.Close() 71 | var obj UploadResp 72 | err = json.NewDecoder(respBody).Decode(&obj) 73 | if err != nil { 74 | return "", err 75 | } 76 | file := obj.Files[0] 77 | if file.Size != size { 78 | return "", errors.New("Byte count mismatch.") 79 | } 80 | return referer + "file/" + file.Key, nil 81 | } 82 | 83 | func finalise(headers, postMap map[string]string) error { 84 | postMap["email"] = "" 85 | postMap["emailText"] = "" 86 | postMap["g-recaptcha-response"] = "" 87 | postMap["password"] = "" 88 | postMap["maxDownloads"] = "" 89 | postMap["storagetime"] = "" 90 | respBody, err := utils.DoFormPost(finaliseUrl, postMap, headers) 91 | if err != nil { 92 | return err 93 | } 94 | respBody.Close() 95 | return nil 96 | } 97 | 98 | func Run(args *utils.Args, path string) (string, error) { 99 | size, err := utils.CheckSize(path, "2GB") 100 | if err != nil { 101 | return "", err 102 | } 103 | headers := map[string]string{ 104 | "Referer": referer, 105 | } 106 | token, fileBag, err := getTokenAndBag() 107 | if err != nil { 108 | fmt.Println("Failed to get token and/or file bag.") 109 | return "", err 110 | } 111 | uploadUrl, err := getServer() 112 | if err != nil { 113 | fmt.Println("Failed to get upload server URL.") 114 | return "", err 115 | } 116 | formMap := map[string]string{ 117 | "token": token, 118 | "filebag": fileBag, 119 | } 120 | fileUrl, err := upload(uploadUrl, path, size, args.ByteLimit, headers, formMap) 121 | if err != nil { 122 | return "", err 123 | } 124 | err = finalise(headers, formMap) 125 | if err != nil { 126 | fmt.Println("Failed to finalise upload.") 127 | return "", err 128 | } 129 | return fileUrl, err 130 | } 131 | -------------------------------------------------------------------------------- /hosts/zippyshare/zippyshare.go: -------------------------------------------------------------------------------- 1 | package zippyshare 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "main/utils" 7 | "path/filepath" 8 | ) 9 | 10 | const ( 11 | referer = "https://www.zippyshare.com/" 12 | serverRegex = `var server = \'(www\d{1,3})\';` 13 | urlRegex = `onclick="this.select\(\);" value="(https://www\d{1,3}.zippyshare` + 14 | `.com/v/[a-zA-Z\d]{8}/file.html)` 15 | ) 16 | 17 | func getServer() (string, error) { 18 | match, err := utils.FindHtmlSubmatch(referer, serverRegex) 19 | if err != nil { 20 | return "", err 21 | } 22 | if match == nil { 23 | return "", errors.New("No regex match.") 24 | } 25 | return match[1], nil 26 | } 27 | 28 | func extractUrl(html string) (string, error) { 29 | match := utils.FindStringSubmatch(html, urlRegex) 30 | if match == nil { 31 | return "", errors.New("No regex match.") 32 | } 33 | return match[1], nil 34 | } 35 | 36 | func upload(uploadUrl, path string, size, byteLimit int64, formMap, headers map[string]string) (string, error) { 37 | respBody, err := utils.MultipartUpload(uploadUrl, path, "file", size, byteLimit, formMap, nil, headers) 38 | if err != nil { 39 | return "", err 40 | } 41 | defer respBody.Close() 42 | bodyBytes, err := io.ReadAll(respBody) 43 | if err != nil { 44 | return "", err 45 | } 46 | fileUrl, err := extractUrl(string(bodyBytes)) 47 | return fileUrl, err 48 | } 49 | 50 | func Run(args *utils.Args, path string) (string, error) { 51 | server, err := getServer() 52 | if err != nil { 53 | return "", err 54 | } 55 | uploadUrl := "https://" + server + ".zippyshare.com/upload" 56 | size, err := utils.CheckSize(path, "500MB") 57 | if err != nil { 58 | return "", err 59 | } 60 | formMap := map[string]string{ 61 | "name": filepath.Base(path), 62 | "zipname": "", 63 | "ziphash": "", 64 | "embPlayerValues": "false", 65 | } 66 | headers := map[string]string{ 67 | "Referer": referer, 68 | } 69 | if args.Private { 70 | formMap["private"] = "true" 71 | } else { 72 | formMap["notprivate"] = "true" 73 | } 74 | fileUrl, err := upload(uploadUrl, path, size, args.ByteLimit, formMap, headers) 75 | return fileUrl, err 76 | } 77 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "main/hosts/anonfiles" 10 | "main/hosts/catbox" 11 | "main/hosts/fileio" 12 | "main/hosts/filemail" 13 | "main/hosts/ftp" 14 | "main/hosts/gofile" 15 | "main/hosts/krakenfiles" 16 | "main/hosts/letsupload" 17 | "main/hosts/megaup" 18 | "main/hosts/mixdrop" 19 | "main/hosts/pixeldrain" 20 | "main/hosts/racaty" 21 | "main/hosts/transfersh" 22 | "main/hosts/uguu" 23 | "main/hosts/wetransfer" 24 | "main/hosts/workupload" 25 | "main/hosts/zippyshare" 26 | "main/utils" 27 | "os" 28 | "path/filepath" 29 | "strings" 30 | "text/template" 31 | 32 | "github.com/alexflint/go-arg" 33 | "github.com/dustin/go-humanize" 34 | ) 35 | 36 | const megabyte = 1000000 37 | 38 | var ( 39 | funcMap = map[string]func(*utils.Args, string) (string, error){ 40 | "anonfiles": anonfiles.Run, 41 | "catbox": catbox.Run, 42 | "fileio": fileio.Run, 43 | "filemail": filemail.Run, 44 | "ftp": ftp.Run, 45 | "gofile": gofile.Run, 46 | "krakenfiles": krakenfiles.Run, 47 | "letsupload": letsupload.Run, 48 | "megaup": megaup.Run, 49 | "mixdrop": mixdrop.Run, 50 | "pixeldrain": pixeldrain.Run, 51 | "racaty": racaty.Run, 52 | "transfersh": transfersh.Run, 53 | "uguu": uguu.Run, 54 | "wetransfer": wetransfer.Run, 55 | "zippyshare": zippyshare.Run, 56 | "workupload": workupload.Run, 57 | } 58 | templateEscPairs = []utils.TemplateEscPair{ 59 | // Newline 60 | {From: []byte{'\x5C', '\x6E'}, To: []byte{'\x0A'}}, 61 | // Tab 62 | {From: []byte{'\x5C', '\x74'}, To: []byte{'\x09'}}, 63 | } 64 | ) 65 | 66 | func populateDirs(path string) ([]string, error) { 67 | var paths []string 68 | files, err := ioutil.ReadDir(path) 69 | if err != nil { 70 | return nil, err 71 | } 72 | for _, f := range files { 73 | if !f.IsDir() { 74 | filePath := filepath.Join(path, f.Name()) 75 | paths = append(paths, filePath) 76 | } 77 | } 78 | return paths, nil 79 | } 80 | 81 | func populateDirsRec(srcPath string) ([]string, error) { 82 | var dirs []string 83 | err := filepath.Walk(srcPath, func(path string, f os.FileInfo, err error) error { 84 | if !f.IsDir() { 85 | dirs = append(dirs, path) 86 | } 87 | return nil 88 | }) 89 | return dirs, err 90 | } 91 | 92 | func checkExists(path string, isDir bool) (bool, error) { 93 | f, err := os.Stat(path) 94 | if err == nil { 95 | if isDir { 96 | return f.IsDir(), nil 97 | } else { 98 | return !f.IsDir(), nil 99 | } 100 | } else if os.IsNotExist(err) { 101 | return false, nil 102 | } 103 | return false, err 104 | } 105 | 106 | func processDirs(args *utils.Args) error { 107 | var ( 108 | allDirs []string 109 | popPaths []string 110 | ) 111 | for _, dir := range args.Directories { 112 | exists, err := checkExists(dir, true) 113 | if err != nil { 114 | return err 115 | } 116 | if exists { 117 | if !foldContains(allDirs, dir) { 118 | allDirs = append(allDirs, dir) 119 | if args.Recursive { 120 | popPaths, err = populateDirsRec(dir) 121 | } else { 122 | popPaths, err = populateDirs(dir) 123 | } 124 | if err != nil { 125 | return err 126 | } 127 | args.Files = append(args.Files, popPaths...) 128 | } else { 129 | fmt.Println("Filtered duplicate directory:", dir) 130 | } 131 | 132 | } else { 133 | fmt.Println("Filtered non-existent directory:", dir) 134 | } 135 | } 136 | return nil 137 | } 138 | 139 | func foldContains(arr []string, value string) bool { 140 | for _, item := range arr { 141 | if strings.EqualFold(item, value) { 142 | return true 143 | } 144 | } 145 | return false 146 | } 147 | 148 | func filterHosts(hosts []string) []string { 149 | var filteredHosts []string 150 | for _, host := range hosts { 151 | if !foldContains(filteredHosts, host) { 152 | filteredHosts = append(filteredHosts, host) 153 | } 154 | } 155 | return filteredHosts 156 | } 157 | 158 | func filterPaths(paths []string) ([]string, error) { 159 | var filteredPaths []string 160 | wd, err := os.Getwd() 161 | if err != nil { 162 | return nil, err 163 | } 164 | for _, path := range paths { 165 | if !filepath.IsAbs(path) { 166 | path = filepath.Join(wd, path) 167 | } 168 | exists, err := checkExists(path, false) 169 | if err != nil { 170 | return nil, err 171 | } 172 | if exists { 173 | if !foldContains(filteredPaths, path) { 174 | filteredPaths = append(filteredPaths, path) 175 | } else { 176 | fmt.Println("Filtered duplicate file:", path) 177 | } 178 | } else { 179 | fmt.Println("Filtered non-existent file:", path) 180 | } 181 | } 182 | return filteredPaths, nil 183 | } 184 | 185 | func parseArgs() (*utils.Args, error) { 186 | var args utils.Args 187 | arg.MustParse(&args) 188 | if args.SpeedLimit != -1 && args.SpeedLimit <= 0 { 189 | return nil, errors.New("Invalid speed limit.") 190 | } 191 | if len(args.Files) == 0 && len(args.Directories) == 0 { 192 | return nil, errors.New("File path and/or directory required.") 193 | } 194 | args.ByteLimit = int64(megabyte * args.SpeedLimit) 195 | if args.SpeedLimit != -1 { 196 | fmt.Printf("Upload speed limiting is active, limit: %s/s.\n", 197 | humanize.Bytes(uint64(args.ByteLimit))) 198 | } 199 | if len(args.Directories) > 0 { 200 | err := processDirs(&args) 201 | if err != nil { 202 | return nil, err 203 | } 204 | } 205 | paths, err := filterPaths(args.Files) 206 | if err != nil { 207 | errString := fmt.Sprintf("Failed to filter paths.\n%s", err) 208 | return nil, errors.New(errString) 209 | } 210 | if len(paths) == 0 { 211 | return nil, errors.New("All files were filtered.") 212 | } 213 | hosts := filterHosts(args.Hosts) 214 | args.Hosts = hosts 215 | args.Files = paths 216 | return &args, nil 217 | } 218 | 219 | func escapeTemplate(template []byte) []byte { 220 | var escaped []byte 221 | for i, pair := range templateEscPairs { 222 | if i != 0 { 223 | template = escaped 224 | } 225 | escaped = bytes.ReplaceAll(template, pair.From, pair.To) 226 | } 227 | return escaped 228 | } 229 | 230 | func parseTemplate(templateText string, meta map[string]string) []byte { 231 | var buffer bytes.Buffer 232 | for { 233 | err := template.Must(template.New("").Parse(templateText)).Execute(&buffer, meta) 234 | if err == nil { 235 | break 236 | } 237 | fmt.Println("Failed to parse template. Default will be used instead.") 238 | templateText = "# {{.filename}}\n{{.fileUrl}}\n" 239 | buffer.Reset() 240 | } 241 | return escapeTemplate(buffer.Bytes()) 242 | } 243 | 244 | func writeTxt(path, filePath, fileUrl, templateText string) error { 245 | f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0755) 246 | if err != nil { 247 | return err 248 | } 249 | meta := map[string]string{ 250 | "filename": filepath.Base(filePath), 251 | "filePath": filePath, 252 | "fileUrl": fileUrl, 253 | } 254 | parsed := parseTemplate(templateText, meta) 255 | _, err = f.Write(parsed) 256 | f.Close() 257 | return err 258 | } 259 | 260 | func outSetup(path string, wipe bool) error { 261 | f, err := os.OpenFile(path, os.O_CREATE|os.O_RDONLY, 0755) 262 | if err != nil { 263 | return err 264 | } 265 | defer f.Close() 266 | if wipe { 267 | err = f.Truncate(0) 268 | if err != nil { 269 | return err 270 | } 271 | } 272 | return nil 273 | } 274 | 275 | func outSetupJob(path string) error { 276 | f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) 277 | if err != nil { 278 | return err 279 | } 280 | defer f.Close() 281 | jobs := &utils.UploadJobs{ 282 | Jobs: []utils.UploadJob{}, 283 | } 284 | 285 | m, err := json.MarshalIndent(&jobs, "", "\t") 286 | if err != nil { 287 | return err 288 | } 289 | _, err = f.Write(m) 290 | return err 291 | } 292 | 293 | func writeJob(jobPath, _url, host, filePath string, jobErr error) error { 294 | var ( 295 | ok = true 296 | errText string 297 | ) 298 | if jobErr != nil { 299 | ok = false 300 | errText = jobErr.Error() 301 | } 302 | job := &utils.UploadJob{ 303 | URL: _url, 304 | Host: host, 305 | Filename: filepath.Base(filePath), 306 | FilePath: filePath, 307 | Ok: ok, 308 | ErrorText: errText, 309 | } 310 | data, err := ioutil.ReadFile(jobPath) 311 | if err != nil { 312 | return err 313 | } 314 | var jobs utils.UploadJobs 315 | err = json.Unmarshal(data, &jobs) 316 | if err != nil { 317 | return err 318 | } 319 | jobs.Jobs = append(jobs.Jobs, *job) 320 | m, err := json.MarshalIndent(&jobs, "", "\t") 321 | if err != nil { 322 | return err 323 | } 324 | err = ioutil.WriteFile(jobPath, m, 0755) 325 | return err 326 | } 327 | 328 | func main() { 329 | args, err := parseArgs() 330 | if err != nil { 331 | panic(err) 332 | } 333 | outPath := args.OutPath 334 | if outPath != "" { 335 | err := outSetup(outPath, args.Wipe) 336 | if err != nil { 337 | panic(err) 338 | } 339 | } 340 | if args.JobOutPath != "" { 341 | err := outSetupJob(args.JobOutPath) 342 | if err != nil { 343 | panic(err) 344 | } 345 | } 346 | for i, host := range args.Hosts { 347 | lowerHost := strings.ToLower(host) 348 | hostFunc, ok := funcMap[lowerHost] 349 | if !ok { 350 | fmt.Println("Invalid host:", host) 351 | continue 352 | } 353 | if i != 0 { 354 | fmt.Println("") 355 | } 356 | fmt.Println("--" + lowerHost + "--") 357 | pathTotal := len(args.Files) 358 | for num, path := range args.Files { 359 | fmt.Printf("File %d of %d:\n", num+1, pathTotal) 360 | fmt.Println(path) 361 | fileUrl, err := hostFunc(args, path) 362 | if args.JobOutPath != "" { 363 | jobErr := writeJob(args.JobOutPath, fileUrl, host, path, err) 364 | if jobErr != nil { 365 | // Intentional. 366 | panic(jobErr) 367 | } 368 | } 369 | if err != nil { 370 | fmt.Println("Upload failed.\n" + err.Error()) 371 | continue 372 | } 373 | fmt.Println(fileUrl) 374 | if outPath != "" { 375 | err = writeTxt(outPath, path, fileUrl, args.Template) 376 | if err != nil { 377 | fmt.Println("Failed to write to output text file.\n" + err.Error()) 378 | } 379 | } 380 | } 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /utils/maps.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "math" 4 | 5 | var ( 6 | sizeMap = map[string]int64{ 7 | "50MB": 52428800, 8 | "100MB": 104857600, 9 | "125MB": 131072000, 10 | "128MB": 134217728, 11 | "200MB": 209715200, 12 | "250MB": 262144000, 13 | "300MB": 314572800, 14 | "500MB": 536870912, 15 | "1GB": 1073741824, 16 | "2GB": 2147483648, 17 | "5GB": 5368709120, 18 | "10GB": 10737418240, 19 | "15GB": 16106127360, 20 | "20GB": 21474836480, 21 | "25GB": 26843545600, 22 | "50GB": 53687091200, 23 | "100GB": 107374182400, 24 | "150GB": 161061273600, 25 | "200GB": 214748364800, 26 | "250GB": 268435456000, 27 | "300GB": 322122547200, 28 | "500GB": 536870912000, 29 | "unlim": math.MaxInt64, 30 | } 31 | mimeMap = map[string]string{ 32 | ".3g2": "video/3gpp2", 33 | ".3gp": "video/3gpp", 34 | ".7z": "application/x-7z-compressed", 35 | ".aac": "audio/aac", 36 | ".abw": "application/x-abiword", 37 | ".aif": "audio/x-aiff", 38 | ".ape": "audio/x-monkeys-audio", 39 | ".apk": "application/vnd.android.package-archive", 40 | ".arc": "application/x-freearc", 41 | ".avi": "video/x-msvideo", 42 | ".azw": "application/vnd.amazon.ebook", 43 | ".bin": "application/octet-stream", 44 | ".bmp": "image/bmp", 45 | ".bz": "application/x-bzip", 46 | ".bz2": "application/x-bzip2", 47 | ".cbr": "x-rar-compressed", 48 | ".cbz": "application/zip", 49 | ".cda": "application/x-cdf", 50 | ".csh": "application/x-csh", 51 | ".css": "text/css", 52 | ".csv": "text/csv", 53 | ".dmg": "application/x-apple-diskimage", 54 | ".doc": "application/msword", 55 | ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 56 | ".eot": "application/vnd.ms-fontobject", 57 | ".epub": "application/epub+zip", 58 | ".exe": "application/octet-stream", 59 | ".gif": "image/gif", 60 | ".gz": "application/gzip", 61 | ".htm": "text/html", 62 | ".html": "text/html", 63 | ".ico": "image/vnd.microsoft.icon", 64 | ".iso": "application/x-iso9660-image", 65 | ".jar": "application/java-archive", 66 | ".jpeg": "image/jpeg", 67 | ".jpg": "image/jpeg", 68 | ".js": "text/javascript", 69 | ".json": "application/json", 70 | ".jsonld": "application/ld+json", 71 | ".m4a": "audio/mpeg", 72 | ".mid": "audio/midi", 73 | ".midi": "audio/x-midi", 74 | ".mjs": "text/javascript", 75 | ".mp3": "audio/mpeg", 76 | ".mp4": "video/mp4", 77 | ".mpeg": "video/mpeg", 78 | ".mpkg": "application/vnd.apple.installer+xml", 79 | ".odp": "application/vnd.oasis.opendocument.presentation", 80 | ".ods": "application/vnd.oasis.opendocument.spreadsheet", 81 | ".odt": "application/vnd.oasis.opendocument.text", 82 | ".oga": "audio/ogg", 83 | ".ogv": "video/ogg", 84 | ".ogx": "application/ogg", 85 | ".opus": "audio/opus", 86 | ".otf": "font/otf", 87 | ".pdf": "application/pdf", 88 | ".php": "application/x-httpd-php", 89 | ".png": "image/png", 90 | ".ppt": "application/vnd.ms-powerpoint", 91 | ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", 92 | ".rar": "application/x-rar-compressed", 93 | ".rtf": "application/rtf", 94 | ".sh": "application/x-sh", 95 | ".svg": "image/svg+xml", 96 | ".swf": "application/x-shockwave-flash", 97 | ".tar": "application/x-tar", 98 | ".tif": "image/tiff", 99 | ".tiff": "image/tiff", 100 | ".torrent": "application/x-bittorrent", 101 | ".ts": "video/mp2t", 102 | ".ttf": "font/ttf", 103 | ".txt": "text/plain", 104 | ".vsd": "application/vnd.visio", 105 | ".wav": "audio/wav", 106 | ".weba": "audio/webm", 107 | ".webm": "video/webm", 108 | ".webp": "image/webp", 109 | ".woff": "font/woff", 110 | ".woff2": "font/woff2", 111 | ".xhtml": "application/xhtml+xml", 112 | ".xls": "application/vnd.ms-excel", 113 | ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 114 | ".xml": "application/xml", 115 | ".xul": "application/vnd.mozilla.xul+xml", 116 | ".zip": "application/zip", 117 | } 118 | ) 119 | -------------------------------------------------------------------------------- /utils/structs.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | type Args struct { 4 | Hosts []string `arg:"positional, required" help:"Which hosts to upload to."` 5 | OutPath string `arg:"-o" help:"Path of text file to write template to. It will be created if it doesn't already exist."` 6 | Wipe bool `arg:"-w" help:"Wipe output text file on startup."` 7 | Files []string `arg:"-f, help:"Paths of files to upload."` 8 | Private bool `arg:"-P" help:"*Set upload as private."` 9 | Template string `arg:"-t" default:"# {{.filename}}\\n{{.fileUrl}}\\n" help:"Output text file template. Vars: filename, filePath, fileUrl"` 10 | Overwrite bool `arg:"-O" help:"*Overwrite file on host if it already exists."` 11 | User string `arg:"-u" help:"*User form for FTP. Folders will be created recursively if they don't already exist."` 12 | Directories []string `arg:"-d, help:"Paths of folders to upload."` 13 | Recursive bool `arg:"-r" help:"Include subdirectories."` 14 | SpeedLimit float64 `arg:"-l" default:"-1" help:"*Upload speed limit in megabytes. Example: 0.5 = 500 kB/s, 1 = 1 MB/s, 1.5 = 1.5 MB/s."` 15 | ByteLimit int64 `arg:"-"` 16 | JobOutPath string `arg:"-j" help:"Path of JSON to write jobs to."` 17 | } 18 | 19 | type Transport struct{} 20 | 21 | type WriteCounter struct { 22 | Total int64 23 | TotalStr string 24 | Uploaded int64 25 | Percentage int 26 | StartTime int64 27 | } 28 | 29 | type TemplateEscPair struct { 30 | From []byte 31 | To []byte 32 | } 33 | 34 | type UploadJob struct { 35 | URL string `json:"url"` 36 | Host string `json:"host"` 37 | Filename string `json:"filename"` 38 | FilePath string `json:"file_path"` 39 | Ok bool `json:"ok"` 40 | ErrorText string `json:"error_text"` 41 | } 42 | 43 | type UploadJobs struct { 44 | Jobs []UploadJob `json:"jobs"` 45 | } 46 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "mime/multipart" 10 | "net/http" 11 | "net/http/cookiejar" 12 | "net/textproto" 13 | "net/url" 14 | "os" 15 | "path/filepath" 16 | "regexp" 17 | "strconv" 18 | "strings" 19 | "time" 20 | 21 | "github.com/dustin/go-humanize" 22 | ) 23 | 24 | const userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + 25 | "(KHTML, like Gecko) Chrome/102.0.5005.62 Safari/537.36" 26 | 27 | var ( 28 | jar, _ = cookiejar.New(nil) 29 | client = &http.Client{Transport: &Transport{}, Jar: jar} 30 | ) 31 | 32 | func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { 33 | req.Header.Add( 34 | "User-Agent", userAgent, 35 | ) 36 | return http.DefaultTransport.RoundTrip(req) 37 | } 38 | 39 | func (wc *WriteCounter) Write(p []byte) (int, error) { 40 | var speed int64 = 0 41 | n := len(p) 42 | wc.Uploaded += int64(n) 43 | percentage := float64(wc.Uploaded) / float64(wc.Total) * float64(100) 44 | wc.Percentage = int(percentage) 45 | // Because of form data size. 46 | if wc.Uploaded > wc.Total { 47 | wc.Uploaded = wc.Total 48 | wc.Percentage = 100 49 | } 50 | toDivideBy := time.Now().UnixMilli() - wc.StartTime 51 | if toDivideBy != 0 { 52 | speed = int64(wc.Uploaded) / toDivideBy * 1000 53 | } 54 | fmt.Printf("\r%d%% @ %s/s, %s/%s ", wc.Percentage, humanize.Bytes(uint64(speed)), 55 | humanize.Bytes(uint64(wc.Uploaded)), wc.TotalStr) 56 | return n, nil 57 | } 58 | 59 | func CheckSize(path, sizeString string) (int64, error) { 60 | resolved, ok := sizeMap[sizeString] 61 | if !ok { 62 | return -1, errors.New("Invalid size limit.") 63 | } 64 | stat, err := os.Stat(path) 65 | if err != nil { 66 | return -1, err 67 | } 68 | size := stat.Size() 69 | if size == 0 { 70 | return -1, errors.New("File is empty.") 71 | } else if size > resolved { 72 | errString := fmt.Sprintf("File exceeds %s size limit.", sizeString) 73 | return -1, errors.New(errString) 74 | } 75 | return size, nil 76 | } 77 | 78 | func makeFormPart(m *multipart.Writer, fileField, filename string) (io.Writer, error) { 79 | mimeType := guessMimeType(filename) 80 | header := make(textproto.MIMEHeader) 81 | disposition := fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fileField, filename) 82 | header.Set("Content-Disposition", disposition) 83 | header.Set("Content-Type", mimeType) 84 | return m.CreatePart(header) 85 | } 86 | 87 | func MultipartUpload(uploadUrl, path, fileField string, size, byteLimit int64, formMap, params, headers map[string]string) (io.ReadCloser, error) { 88 | filename := filepath.Base(path) 89 | r, w := io.Pipe() 90 | m := multipart.NewWriter(w) 91 | f, err := os.Open(path) 92 | if err != nil { 93 | w.Close() 94 | m.Close() 95 | return nil, err 96 | } 97 | defer f.Close() 98 | counter := &WriteCounter{ 99 | Total: size, 100 | TotalStr: humanize.Bytes(uint64(size)), 101 | StartTime: time.Now().UnixMilli(), 102 | } 103 | // Implement and get err channel working. Seems to hang. Implement content len. 104 | go func() { 105 | defer w.Close() 106 | defer m.Close() 107 | for k, v := range formMap { 108 | formField, err := m.CreateFormField(k) 109 | if err != nil { 110 | return 111 | } 112 | _, err = formField.Write([]byte(v)) 113 | if err != nil { 114 | return 115 | } 116 | } 117 | part, err := makeFormPart(m, fileField, filename) 118 | if err != nil { 119 | return 120 | } 121 | if byteLimit == -1000000 { 122 | _, err = io.Copy(part, f) 123 | if err != nil { 124 | return 125 | } 126 | } else { 127 | for range time.Tick(time.Second * 1) { 128 | _, err = io.CopyN(part, f, byteLimit) 129 | if errors.Is(err, io.EOF) { 130 | err = nil 131 | break 132 | } 133 | if err != nil { 134 | return 135 | } 136 | } 137 | } 138 | }() 139 | req, err := http.NewRequest(http.MethodPost, uploadUrl, io.TeeReader(r, counter)) 140 | if err != nil { 141 | return nil, err 142 | } 143 | if headers != nil { 144 | setHeaders(req, headers) 145 | } 146 | req.Header.Add("Content-Type", m.FormDataContentType()) 147 | if params != nil { 148 | setParams(req, params) 149 | } 150 | defer fmt.Println("") 151 | do, err := client.Do(req) 152 | if err != nil { 153 | return nil, err 154 | } 155 | if !(do.StatusCode == http.StatusOK || do.StatusCode == http.StatusCreated) { 156 | do.Body.Close() 157 | return nil, errors.New(do.Status) 158 | } 159 | return do.Body, nil 160 | } 161 | 162 | // func PutUpload(uploadUrl, path string, size int64, params, headers map[string]string) (io.ReadCloser, error) { 163 | // f, err := os.Open(path) 164 | // if err != nil { 165 | // return nil, err 166 | // } 167 | // defer f.Close() 168 | // counter := &WriteCounter{Total: size, TotalStr: humanize.Bytes(uint64(size))} 169 | // req, err := http.NewRequest(http.MethodPut, uploadUrl, io.TeeReader(f, counter)) 170 | // if err != nil { 171 | // return nil, err 172 | // } 173 | // if params != nil { 174 | // setParams(req, params) 175 | // } 176 | // if headers != nil { 177 | // setHeaders(req, headers) 178 | // } 179 | // mimeType := guessMimeType(path) 180 | // req.Header.Add("Content-Type", mimeType) 181 | // do, err := client.Do(req) 182 | // if err != nil { 183 | // return nil, err 184 | // } 185 | // if !(do.StatusCode == http.StatusOK || do.StatusCode == http.StatusCreated) { 186 | // do.Body.Close() 187 | // return nil, errors.New(do.Status) 188 | // } 189 | // return do.Body, nil 190 | // } 191 | 192 | // Do headers and params. 193 | func GetHtml(url string) (string, error) { 194 | reqBody, err := DoGet(url, nil, nil) 195 | if err != nil { 196 | return "", err 197 | } 198 | defer reqBody.Close() 199 | bodyBytes, err := io.ReadAll(reqBody) 200 | return string(bodyBytes), err 201 | } 202 | 203 | func FindStringSubmatch(text, regexString string) []string { 204 | regex := regexp.MustCompile(regexString) 205 | match := regex.FindStringSubmatch(text) 206 | return match 207 | } 208 | 209 | func FindStringSubmatches(text, regexString string) [][]string { 210 | regex := regexp.MustCompile(regexString) 211 | matches := regex.FindAllStringSubmatch(text, -1) 212 | return matches 213 | } 214 | 215 | func FindHtmlSubmatch(_url, regexString string) ([]string, error) { 216 | html, err := GetHtml(_url) 217 | if err != nil { 218 | return nil, err 219 | } 220 | match := FindStringSubmatch(html, regexString) 221 | return match, nil 222 | } 223 | 224 | func FindHtmlSubmatches(_url, regexString string) ([][]string, error) { 225 | html, err := GetHtml(_url) 226 | if err != nil { 227 | return nil, err 228 | } 229 | matches := FindStringSubmatches(html, regexString) 230 | return matches, nil 231 | } 232 | 233 | func setParams(req *http.Request, params map[string]string) { 234 | query := url.Values{} 235 | for k, v := range params { 236 | query.Set(k, v) 237 | } 238 | req.URL.RawQuery = query.Encode() 239 | } 240 | 241 | func setHeaders(req *http.Request, headers map[string]string) { 242 | for k, v := range headers { 243 | req.Header.Add(k, v) 244 | } 245 | } 246 | 247 | func DoGet(_url string, params, headers map[string]string) (io.ReadCloser, error) { 248 | req, err := http.NewRequest(http.MethodGet, _url, nil) 249 | if err != nil { 250 | return nil, err 251 | } 252 | if headers != nil { 253 | setHeaders(req, headers) 254 | } 255 | if params != nil { 256 | setParams(req, params) 257 | } 258 | do, err := client.Do(req) 259 | if err != nil { 260 | return nil, err 261 | } 262 | if do.StatusCode != http.StatusOK { 263 | do.Body.Close() 264 | return nil, errors.New(do.Status) 265 | } 266 | return do.Body, nil 267 | } 268 | 269 | func makeJsonReq(_url string, jsonMap map[string]interface{}) (*http.Request, error) { 270 | m, err := json.Marshal(jsonMap) 271 | if err != nil { 272 | return nil, err 273 | } 274 | req, err := http.NewRequest(http.MethodPost, _url, bytes.NewBuffer(m)) 275 | if err != nil { 276 | return nil, err 277 | } 278 | req.Header.Add("Content-Type", "application/json;charset=UTF-8") 279 | return req, nil 280 | } 281 | 282 | func DoPost(_url string, params, headers map[string]string, jsonMap map[string]interface{}) (io.ReadCloser, error) { 283 | var ( 284 | err error 285 | req *http.Request 286 | ) 287 | if jsonMap != nil { 288 | req, err = makeJsonReq(_url, jsonMap) 289 | } else { 290 | req, err = http.NewRequest(http.MethodPost, _url, nil) 291 | } 292 | if err != nil { 293 | return nil, err 294 | } 295 | if headers != nil { 296 | setHeaders(req, headers) 297 | } 298 | if params != nil { 299 | setParams(req, params) 300 | } 301 | do, err := client.Do(req) 302 | if err != nil { 303 | return nil, err 304 | } 305 | if do.StatusCode != http.StatusOK { 306 | do.Body.Close() 307 | return nil, errors.New(do.Status) 308 | } 309 | return do.Body, nil 310 | } 311 | 312 | func DoFormPost(_url string, params, headers map[string]string) (io.ReadCloser, error) { 313 | if headers == nil { 314 | headers = map[string]string{} 315 | } 316 | query := url.Values{} 317 | for k, v := range params { 318 | query.Set(k, v) 319 | } 320 | encQuery := query.Encode() 321 | headers["Content-Type"] = "application/x-www-form-urlencoded" 322 | headers["Content-Length"] = strconv.Itoa(len(encQuery)) 323 | req, err := http.NewRequest(http.MethodPost, _url, strings.NewReader(encQuery)) 324 | if err != nil { 325 | return nil, err 326 | } 327 | setHeaders(req, headers) 328 | do, err := client.Do(req) 329 | if err != nil { 330 | return nil, err 331 | } 332 | if do.StatusCode != http.StatusOK { 333 | do.Body.Close() 334 | return nil, errors.New(do.Status) 335 | } 336 | return do.Body, nil 337 | } 338 | 339 | // Unknown extensions and extensions with multiple periods in will be treated as octet. 340 | func guessMimeType(path string) string { 341 | octetMime := "application/octet-stream" 342 | lastIndex := strings.LastIndex(path, ".") 343 | if lastIndex == -1 { 344 | return octetMime 345 | } 346 | extension := path[lastIndex:] 347 | resolved, ok := mimeMap[extension] 348 | if !ok { 349 | return octetMime 350 | } 351 | return resolved 352 | } 353 | 354 | func GetCookies(u *url.URL) []*http.Cookie { 355 | return client.Jar.Cookies(u) 356 | } 357 | --------------------------------------------------------------------------------