-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
872 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,7 @@ | ||
# cowtransfer | ||
Client library for cowtransfer...shhh | ||
cowtransfer | ||
=========== | ||
This is a client for https://cowtransfer.cn | ||
|
||
Most code is from https://github.com/Mikubill/cowtransfer-uploader. I have removed dependency on | ||
`github.com/cheggaaa/pb` in favor of various progress event hooks. The intention is to decouple | ||
the CLI from the library API. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"os" | ||
"time" | ||
"strings" | ||
|
||
"github.com/imacks/cowtransfer" | ||
) | ||
|
||
var ( | ||
token string | ||
password string | ||
parallel int | ||
blksize int | ||
timeout int | ||
verbose bool | ||
verify bool | ||
maxRetry int | ||
) | ||
|
||
func init() { | ||
flag.StringVar(&token, "c", "", "Cookie token (optional)") | ||
flag.StringVar(&password, "p", "", "Password (optional)") | ||
flag.BoolVar(&verbose, "v", false, "Verbose mode") | ||
flag.BoolVar(&verify, "V", false, "Verify by hash") | ||
flag.IntVar(&timeout, "T", 10, "Timeout in seconds") | ||
flag.IntVar(&blksize, "b", 262144, "Block size for uploading") | ||
flag.IntVar(¶llel, "l", 4, "Parallel threads") | ||
flag.IntVar(&maxRetry, "r", 3, "Maximum retries") | ||
|
||
flag.Usage = func() { | ||
fmt.Fprintf(os.Stdout, "%s %s () %s\n", "cowtransfer", "1.0.0", "cowtransfer.cn client") | ||
fmt.Fprintln(os.Stdout, "") | ||
fmt.Fprintf(os.Stdout, "Usage: %s [<optional-params>] file1 file2... \n", os.Args[0]) | ||
fmt.Fprintf(os.Stdout, " %s [<optional-params>] url\n", os.Args[0]) | ||
fmt.Fprintln(os.Stdout, "") | ||
fmt.Fprintln(os.Stdout, "Parameters:") | ||
flag.PrintDefaults() | ||
} | ||
flag.Parse() | ||
} | ||
|
||
func main() { | ||
if blksize > 4194304 { | ||
fmt.Printf("WARNING! Block size too large\n") | ||
blksize = 524288 | ||
} | ||
|
||
files := flag.Args() | ||
if len(files) == 0 { | ||
fmt.Printf("FATAL! files not specified\n") | ||
os.Exit(1) | ||
} | ||
|
||
cow := cowtransfer.NewCowClient() | ||
cow.BlockSize = blksize | ||
cow.DownloadParallel = parallel | ||
cow.UploadParallel = parallel | ||
cow.MaxRetry = maxRetry | ||
cow.Password = password | ||
cow.Timeout = time.Duration(timeout) * time.Second | ||
cow.Token = token | ||
cow.VerifyHash = verify | ||
|
||
if len(files) == 1 && strings.HasPrefix(files[0], "https://") { | ||
fi, err := cow.Files(files[0]) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
for i, v := range fi { | ||
fmt.Fprintf(os.Stdout, "idx: %d\n", i) | ||
fmt.Fprintf(os.Stdout, "file: %s\n", v.FileName) | ||
fmt.Fprintf(os.Stdout, "url: %s\n", v.URL) | ||
fmt.Fprintf(os.Stdout, "size: %d\n", v.Size) | ||
if v.Error != nil { | ||
fmt.Fprintf(os.Stdout, "err: %s\n", v.Error.Error()) | ||
} | ||
|
||
fmt.Fprintf(os.Stdout, "\n") | ||
} | ||
|
||
return | ||
} | ||
|
||
var f []string | ||
for _, v := range files { | ||
if strings.HasPrefix(v, "https://") { | ||
fmt.Printf("FATAL! upload supports local files only\n") | ||
os.Exit(1) | ||
} | ||
f = append(f, v) | ||
} | ||
|
||
dl, err := cow.Upload(f) | ||
if err != nil { | ||
panic(err) | ||
} | ||
fmt.Fprintf(os.Stdout, "link: %s\n", dl) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package cowtransfer | ||
|
||
import ( | ||
"time" | ||
"os" | ||
"fmt" | ||
) | ||
|
||
const ( | ||
defaultUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36 Edg/85.0.564.51" | ||
defaultBlockSize = 4194304 | ||
) | ||
|
||
// ProgressHook is a hook for upload and download progress | ||
type ProgressHook struct { | ||
Start func(fi os.FileInfo) | ||
Finish func(fi os.FileInfo) | ||
Seal func(fi os.FileInfo) | ||
Block func(item int64, current, max int) | ||
Retry func(op string, count int, err error) | ||
RetryBlock func(op string, item int64, block int, err error) | ||
} | ||
|
||
// CowClient is a client for CowTransfer.cn | ||
type CowClient struct { | ||
Timeout time.Duration | ||
MaxRetry int | ||
UserAgent string | ||
Progress ProgressHook | ||
VerifyHash bool | ||
Password string | ||
UploadParallel int | ||
DownloadParallel int | ||
Token string | ||
BlockSize int | ||
} | ||
|
||
// NewCowClient creates a CowClient using default parameters | ||
func NewCowClient() *CowClient { | ||
progHook := ProgressHook{ | ||
Start: func(fi os.FileInfo) { | ||
fmt.Printf("[start] file %s | size %d\n", fi.Name(), fi.Size()) | ||
}, | ||
Finish: func(fi os.FileInfo) { | ||
fmt.Printf("[finish] file %s | size %d\n", fi.Name(), fi.Size()) | ||
}, | ||
Seal: func(fi os.FileInfo) { | ||
fmt.Printf("[seal] file %s | size %d\n", fi.Name(), fi.Size()) | ||
}, | ||
Block: func(item int64, current, max int) { | ||
fmt.Printf("[block] item %d | current %d | len %d\n", item, current, max) | ||
}, | ||
RetryBlock: func(op string, item int64, block int, err error) { | ||
fmt.Printf("[blkerror] %s | item %d | block %d | %v\n", op, item, block, err) | ||
}, | ||
Retry: func(op string, count int, err error) { | ||
fmt.Printf("[error] retry %s | attempt %d | %v\n", op, count, err) | ||
}, | ||
} | ||
|
||
return &CowClient{ | ||
UserAgent: defaultUA, | ||
BlockSize: defaultBlockSize, | ||
UploadParallel: 4, | ||
DownloadParallel: 4, | ||
Timeout: time.Duration(10) * time.Second, | ||
Token: "", | ||
Password: "", | ||
VerifyHash: true, | ||
MaxRetry: 3, | ||
Progress: progHook, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package cowtransfer | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"regexp" | ||
"time" | ||
"strconv" | ||
) | ||
|
||
const ( | ||
downloadDetailsURL = "https://cowtransfer.com/transfer/transferdetail?url=%s&treceive=undefined&passcode=%s" | ||
downloadConfigURL = "https://cowtransfer.com/transfer/download?guid=%s" | ||
) | ||
|
||
var regex = regexp.MustCompile("[0-9a-f]{14}") | ||
|
||
// CowFileInfo represents information about a file | ||
type CowFileInfo struct { | ||
FileName string `json:"fileName"` | ||
Size int64 `json:"size"` | ||
URL string `json:"url"` | ||
Error error `json:"error"` | ||
} | ||
|
||
type downloadDetailsResponse struct { | ||
GUID string `json:"guid"` | ||
DownloadName string `json:"downloadName"` | ||
Deleted bool `json:"deleted"` | ||
Uploaded bool `json:"uploaded"` | ||
Details []downloadDetailsBlock `json:"transferFileDtos"` | ||
} | ||
|
||
type downloadDetailsBlock struct { | ||
GUID string `json:"guid"` | ||
FileName string `json:"fileName"` | ||
Size string `json:"size"` | ||
} | ||
|
||
type downloadConfigResponse struct { | ||
Link string `json:"link"` | ||
} | ||
|
||
// Files return information on all files in a download link | ||
func (cc *CowClient) Files(link string) ([]CowFileInfo, error) { | ||
fileID := regex.FindString(link) | ||
if fileID == "" { | ||
return nil, fmt.Errorf("unknown download code format") | ||
} | ||
|
||
detailsURL := fmt.Sprintf(downloadDetailsURL, fileID, cc.Password) | ||
response, err := http.Get(detailsURL) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
req, err := http.NewRequest("GET", detailsURL, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
req.Header.Set("Referer", fmt.Sprintf("https://cowtransfer.com/s/%s", fileID)) | ||
req.Header.Set("Cookie", fmt.Sprintf("cf-cs-k-20181214=%d;", time.Now().UnixNano())) | ||
response, err = http.DefaultClient.Do(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
body, err := ioutil.ReadAll(response.Body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
_ = response.Body.Close() | ||
|
||
details := new(downloadDetailsResponse) | ||
if err := json.Unmarshal(body, details); err != nil { | ||
fmt.Printf("fsd %s\n", string(body)) | ||
return nil, err | ||
} | ||
|
||
if details.GUID == "" { | ||
return nil, fmt.Errorf("link invalid") | ||
} else if details.Deleted { | ||
return nil, fmt.Errorf("link deleted") | ||
} else if !details.Uploaded { | ||
return nil, fmt.Errorf("link not finish upload yet") | ||
} | ||
|
||
result := []CowFileInfo{} | ||
for _, item := range details.Details { | ||
cowfi := cc.getItemDownloadURL(item) | ||
result = append(result, cowfi) | ||
} | ||
|
||
return result, nil | ||
} | ||
|
||
func (cc *CowClient) getItemDownloadURL(item downloadDetailsBlock) CowFileInfo { | ||
result := CowFileInfo{ | ||
FileName: item.FileName, | ||
} | ||
|
||
configURL := fmt.Sprintf(downloadConfigURL, item.GUID) | ||
req, err := http.NewRequest("POST", configURL, nil) | ||
if err != nil { | ||
result.Error = err | ||
return result | ||
} | ||
|
||
response, err := http.DefaultClient.Do(addHeaders(req, cc.UserAgent)) | ||
if err != nil { | ||
result.Error = err | ||
return result | ||
} | ||
|
||
body, err := ioutil.ReadAll(response.Body) | ||
if err != nil { | ||
result.Error = err | ||
return result | ||
} | ||
_ = response.Body.Close() | ||
|
||
config := new(downloadConfigResponse) | ||
if err := json.Unmarshal(body, config); err != nil { | ||
result.Error = err | ||
return result | ||
} | ||
result.URL = config.Link | ||
|
||
numSize, err := strconv.ParseFloat(item.Size, 10) | ||
if err != nil { | ||
result.Error = err | ||
result.Size = 0 | ||
return result | ||
} | ||
|
||
result.Size = int64(numSize * 1024) | ||
return result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module github.com/imacks/cowtransfer | ||
|
||
go 1.14 | ||
|
||
require ( | ||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw= | ||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package cowtransfer | ||
|
||
import ( | ||
"os" | ||
) | ||
|
||
func isExist(path string) bool { | ||
_, err := os.Stat(path) | ||
if err != nil { | ||
if os.IsExist(err) { | ||
return true | ||
} | ||
if os.IsNotExist(err) { | ||
return false | ||
} | ||
|
||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
func isDir(path string) bool { | ||
s, err := os.Stat(path) | ||
if err != nil { | ||
return false | ||
} | ||
return s.IsDir() | ||
} | ||
|
||
func isFile(path string) bool { | ||
return !isDir(path) | ||
} |
Oops, something went wrong.