diff --git a/README.md b/README.md index cf810f2..b7b26f8 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Planned features for cobalt-cli: - [x] Display progress bar to track download progress (when supported by cobalt). - [ ] Hability to use custom downloader program (wget, curl, got, etc); - [ ] Translations. +- [X] Benchmarking. ## Usage @@ -59,7 +60,8 @@ usage: cobalt-cli [-h|--help] [url ""] [-c|--video-codec (av1|vp9|h264)] [-m|--mode (auto|audio|mute)] [-x|--proxy] [-d|--disable-metadata] [-t|--tiktok-h265] [-T|--tiktok-full-audio] [-g|--gif] [-s|--save] [-a|--api - ""] [-i|--instances] [-v|--verbose] + ""] [-i|--instances] [-v|--verbose] [-k|--key + ""] [-b|--benchmark] save what you want, directly from the terminal, no unwanted distractions involved. powered by cobalt's api @@ -106,6 +108,11 @@ Arguments: https://cobalt-backend.canine.tools -i --instances Show community instances and exit. Default: false -v --verbose Enable verbose logging. Default: false + -k --key API key by the instance owner. You may need to + provide one to use download. Can be set with + COBALT_API_KEY environment variable. Default: + -b --benchmark Run a benchmark to test the download speed and + integrity. Default: false ``` ### Instances diff --git a/go.mod b/go.mod index d2683ab..f9b72ab 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,9 @@ go 1.22 toolchain go1.23.1 require ( - github.com/jedib0t/go-pretty/v6 v6.6.0 - github.com/lostdusty/gobalt/v2 v2.0.3 + github.com/jedib0t/go-pretty/v6 v6.6.1 + github.com/lostdusty/gobalt/v2 v2.0.4 + github.com/schollz/progressbar/v3 v3.16.1 github.com/sirupsen/logrus v1.9.3 ) @@ -14,7 +15,6 @@ require ( github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/schollz/progressbar/v3 v3.16.1 // indirect github.com/stretchr/testify v1.9.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/term v0.25.0 // indirect diff --git a/go.sum b/go.sum index 34f821a..a3933d7 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jedib0t/go-pretty/v6 v6.6.0 h1:wmZVuAcEkZRT+Aq1xXpE8IGat4vE5WXOMmBpbQqERXw= github.com/jedib0t/go-pretty/v6 v6.6.0/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= +github.com/jedib0t/go-pretty/v6 v6.6.1 h1:iJ65Xjb680rHcikRj6DSIbzCex2huitmc7bDtxYVWyc= +github.com/jedib0t/go-pretty/v6 v6.6.1/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/lostdusty/gobalt/v2 v2.0.3 h1:jCV/VXyz6wbK0lLQRosDsuSR7bBUPjdGhTu9OJeC3N4= github.com/lostdusty/gobalt/v2 v2.0.3/go.mod h1:LiuOQrhZ81oUD8EtVIOXvdI0+iibamw0XYUeOhe8ibw= +github.com/lostdusty/gobalt/v2 v2.0.4 h1:MkIPh4zuHUDo0qlqFPpxDtyK3NUEI2cIGmjlLmLXar8= +github.com/lostdusty/gobalt/v2 v2.0.4/go.mod h1:LiuOQrhZ81oUD8EtVIOXvdI0+iibamw0XYUeOhe8ibw= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= diff --git a/main.go b/main.go index d4a4b15..0f3c6bb 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,8 @@ package main import ( + "bytes" + "crypto/sha256" "fmt" "io" "net/http" @@ -9,6 +11,7 @@ import ( "runtime" "strconv" "strings" + "time" "github.com/jedib0t/go-pretty/v6/table" "github.com/lostdusty/gobalt/v2" @@ -17,7 +20,7 @@ import ( "github.com/tgoncuoglu/argparse" ) -var version = "2.0.0-Alpha" +var version = "2.0.1" var useragent = fmt.Sprintf("cobalt-cli/%v (+https://github.com/lostdusty/cobalt; go/%v; %v/%v)", version, runtime.Version(), runtime.GOOS, runtime.GOARCH) func main() { @@ -116,6 +119,16 @@ func main() { Help: "Enable verbose logging", Default: false, }) + apiKey := cobaltParser.String("k", "key", &argparse.Options{ + Required: false, + Help: "API key by the instance owner. You may need to provide one to use download. Can be set with COBALT_API_KEY environment variable", + Default: gobalt.ApiKey, + }) + flagBenchmark := cobaltParser.Flag("b", "benchmark", &argparse.Options{ + Required: false, + Help: "Run a benchmark to test the download speed and integrity", + Default: false, + }) err := cobaltParser.Parse(os.Args) if err != nil { @@ -144,9 +157,32 @@ func main() { return } + if *apiKey != "" { + log.Debug("API key was provided via flag, setting it to gobalt") + gobalt.ApiKey = *apiKey + log.Debugf("Key from flag: %v | Key from Gobalt: %v | Key from COBALT_API_KEY: %v", *apiKey, gobalt.ApiKey, os.Getenv("COBALT_API_KEY")) + } + + gobalt.CobaltApi = *apiUrl + + if *flagBenchmark { + log.Debug("Flag to run benchmark is set, running benchmark") + result, err := doBenchmark() + if err != nil { + log.Fatal(err) + } + mapBool := map[bool]string{true: "Yes!", false: "No :("} + benchmarkTable := table.NewWriter() + benchmarkTable.SetOutputMirror(os.Stdout) + benchmarkTable.AppendHeader(table.Row{"Instance", "Time to download", "Download speed (KB/s)", "File size (KB)", "File hash matches?"}) + benchmarkTable.AppendRow(table.Row{result.Name, result.TimeToDownload, result.DownloadSpeed, result.FileSize, mapBool[result.HashMatches]}) + benchmarkTable.SetStyle(table.StyleLight) + benchmarkTable.Render() + return + } + newDownload := gobalt.CreateDefaultSettings() log.Debugf("Creating new cobalt download with default options: %v", newDownload) - gobalt.CobaltApi = *apiUrl newDownload.Url = *urlToDownload switch *youtubeVideoCodec { case "av1": @@ -260,6 +296,7 @@ func fetchContent(options gobalt.Settings, save bool) error { ) io.Copy(io.MultiWriter(f, bar), responseDownload.Body) f.Sync() + fmt.Println() log.Info("File downloaded successfully!") } @@ -281,3 +318,68 @@ func communityInstances() { instancesTable.SetStyle(table.StyleRounded) instancesTable.Render() } + +type Benchmark struct { + Name string // Instance name + TimeToDownload time.Duration // Time to download the file + DownloadSpeed int // Download speed in KB/s + FileSize int // File size in KB + FileHash string // File hash in SHA256 + HashMatches bool // If the hash matches the known good hash +} + +func doBenchmark() (*Benchmark, error) { + //Know good hash: a092e6e57ff79077b5b3a6db97739cd925b462662bac82236f9de4227ac84757 + cobaltBench := &Benchmark{ + Name: gobalt.CobaltApi, + } + + log.Info("Starting benchmark...") + downloadBenchmark := gobalt.CreateDefaultSettings() + downloadBenchmark.Url = "https://x.com/lostydust/status/1720929746987425821" + downloadBenchmark.Proxy = true + downloadBenchmark.VideoQuality = 1080 + + log.Debug("Running benchmark with the following options: ", downloadBenchmark) + log.Debugf("API: %s | Key: %s", gobalt.CobaltApi, gobalt.ApiKey) + grabUrl, err := gobalt.Run(downloadBenchmark) + if err != nil { + return nil, err + } + log.Debug("Ok, got tunnel url: ", grabUrl.URL) + requestDownload, err := http.NewRequest("GET", grabUrl.URL, nil) + requestDownload.Header.Set("User-Agent", useragent) + if err != nil { + return nil, err + } + fileBuffer := bytes.NewBuffer(nil) + log.Debug("Starting download now...") + start := time.Now() + responseDownload, err := gobalt.Client.Do(requestDownload) + if err != nil { + return nil, err + } + defer responseDownload.Body.Close() + if responseDownload.StatusCode != http.StatusOK { + err = fmt.Errorf("got http status %v while benchmarking the file", responseDownload.Status) + return nil, err + } + _, err = io.Copy(fileBuffer, responseDownload.Body) + if err != nil { + return nil, err + } + + elapsed := time.Since(start) + log.Debug("Downloaded file in ", elapsed.Seconds(), " seconds") + hashfile := sha256.New() + hashfile.Write(fileBuffer.Bytes()) + cobaltBench.TimeToDownload = elapsed + cobaltBench.FileSize = fileBuffer.Len() / 1024 + cobaltBench.DownloadSpeed = int(float64(cobaltBench.FileSize) / elapsed.Seconds()) + cobaltBench.FileHash = fmt.Sprintf("%x", hashfile.Sum(nil)) + cobaltBench.HashMatches = cobaltBench.FileHash == "a092e6e57ff79077b5b3a6db97739cd925b462662bac82236f9de4227ac84757" + log.Debugf("File hash: %s", cobaltBench.FileHash) + log.Debugf("Hash matches? %v", cobaltBench.FileHash == "a092e6e57ff79077b5b3a6db97739cd925b462662bac82236f9de4227ac84757") + log.Info("[PASS] Benchmark finished!") + return cobaltBench, nil +}