From 2dca979e7aeaf2a7b38e6de7497df9c071d61759 Mon Sep 17 00:00:00 2001 From: horsaen Date: Wed, 20 Mar 2024 12:16:05 -0500 Subject: [PATCH] add concurrency --- go.mod | 12 +++- go.sum | 28 +++++++++ main.go | 20 ++++--- plugins/afreeca/afreeca.go | 30 ++++++++-- plugins/afreeca/download.go | 96 ++++++++++++++++++++++++++++++- plugins/afreeca/verify.go | 9 +++ plugins/bigo/bigo.go | 16 ++++++ plugins/bigo/download.go | 97 +++++++++++++++++++++++++++----- plugins/bigo/verify.go | 9 +++ plugins/concurrent/concurrent.go | 62 ++++++++++++++++++++ plugins/concurrent/table.go | 20 +++++++ plugins/flex/download.go | 92 +++++++++++++++++++++++++++--- plugins/flex/flex.go | 12 ++++ plugins/flex/verify.go | 9 +++ plugins/kick/download.go | 95 ++++++++++++++++++++++++++----- plugins/kick/kick.go | 12 ++++ plugins/kick/verify.go | 9 +++ plugins/panda/download.go | 94 ++++++++++++++++++++++++++----- plugins/panda/panda.go | 19 +++++++ plugins/panda/verify.go | 9 +++ tools/cli.go | 20 +++++++ tools/version.go | 12 ++++ 22 files changed, 718 insertions(+), 64 deletions(-) create mode 100644 plugins/concurrent/concurrent.go create mode 100644 plugins/concurrent/table.go create mode 100644 tools/cli.go create mode 100644 tools/version.go diff --git a/go.mod b/go.mod index d67e471..bcf69ef 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,14 @@ module horsaen/afreeca-downloader go 1.22.0 -require github.com/andybalholm/brotli v1.1.0 +require ( + github.com/andybalholm/brotli v1.1.0 + github.com/fatih/color v1.16.0 + github.com/rodaine/table v1.1.1 +) + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.14.0 // indirect +) diff --git a/go.sum b/go.sum index 8769ccf..c997863 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,30 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rodaine/table v1.1.1 h1:zBliy3b4Oj6JRmncse2Z85WmoQvDrXOYuy0JXCt8Qz8= +github.com/rodaine/table v1.1.1/go.mod h1:iqTRptjn+EVcrVBYtNMlJ2wrJZa3MpULUmcXFpfcziA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 3d91d2f..a340e2a 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "fmt" "horsaen/afreeca-downloader/plugins/afreeca" "horsaen/afreeca-downloader/plugins/bigo" + "horsaen/afreeca-downloader/plugins/concurrent" "horsaen/afreeca-downloader/plugins/flex" "horsaen/afreeca-downloader/plugins/kick" "horsaen/afreeca-downloader/plugins/panda" @@ -20,27 +21,30 @@ func main() { mode := flag.String("mode", "afreeca", "Mode") userArg := flag.String("username", "", "Streamer username") - playlist := flag.Bool("playlist", false, "Download playlist") - vod := flag.Bool("vod", false, "Download vod") + playlist := flag.Bool("playlist", false, "Download bot playlists") + concurrently := flag.Bool("concurrent", false, "Download streams concurrently") + vod := flag.Bool("vod", false, "Download Afreeca vod") version := flag.Bool("version", false, "Print version") flag.Parse() if *version { - fmt.Println("https://github.com/horsaen/afreeca-downloader") - fmt.Println("v2.0.2") - os.Exit(0) + tools.Version() } - if *userArg != "" || *playlist || *vod { + tools.Exists("downloads") + + if *concurrently { + concurrent.Start() + } + + if *userArg != "" || *playlist || *vod || *concurrently { username = *userArg } else { fmt.Println("Enter username:") fmt.Scan(&username) } - tools.Exists("downloads") - switch *mode { case "afreeca": tools.Exists("downloads/Afreeca") diff --git a/plugins/afreeca/afreeca.go b/plugins/afreeca/afreeca.go index ef76902..be65d22 100644 --- a/plugins/afreeca/afreeca.go +++ b/plugins/afreeca/afreeca.go @@ -4,14 +4,26 @@ import ( "fmt" "horsaen/afreeca-downloader/tools" "os" + "strconv" ) -func Playlist() { - inputPlaylists := GetPlaylists() +func Concurrent(user *[]string) { + tools.Exists("downloads/Afreeca") - playlists := ParsePlaylists(inputPlaylists) + if !CheckExists((*user)[0]) { + (*user)[2] = "Not found" + (*user)[3] = "Not found" + (*user)[4] = "Not found" + return + } - DownloadPlaylists(playlists) + if ConcurrentCheck((*user)[0]) { + nickname, broad_no := UserData((*user)[0]) + + url := GetStream((*user)[0], broad_no, "") + + ConcurrentDownload(user, nickname, strconv.Itoa(broad_no), url) + } } func Start(bjId string) { @@ -25,10 +37,18 @@ func Start(bjId string) { url := GetStream(bjId, broad_no, "") - Download(bjId, nickname, url) + Download(bjId, nickname, strconv.Itoa(broad_no), url) } } +func Playlist() { + inputPlaylists := GetPlaylists() + + playlists := ParsePlaylists(inputPlaylists) + + DownloadPlaylists(playlists) +} + func Vod(TitleNo string) { tools.Exists("downloads/Afreeca/Vods") diff --git a/plugins/afreeca/download.go b/plugins/afreeca/download.go index 9291e6a..1aea98f 100644 --- a/plugins/afreeca/download.go +++ b/plugins/afreeca/download.go @@ -15,7 +15,99 @@ import ( "time" ) -func Download(bjId string, nickname string, playlist string) bool { +func ConcurrentDownload(user *[]string, nickname string, broad_no string, playlist string) { + bjId := (*user)[0] + tools.Exists("downloads/Afreeca/" + bjId) + + client := &http.Client{} + + req, _ := http.NewRequest("GET", playlist, nil) + + parsedUrl, _ := url.Parse(playlist) + + pathSegments := strings.Split(parsedUrl.Path, "/") + + newPath := strings.Join(pathSegments[:len(pathSegments)-1], "/") + + filename := nickname + "-" + bjId + "-" + broad_no + "-" + time.Now().Format("200601021504") + "-afreeca.ts" + + out, _ := os.Create("downloads/Afreeca/" + bjId + "/" + filename) + + playlistUrls := make(map[string]bool) + + var bytes int64 = 0 + var start_time = time.Now() + + for { + resp, err := client.Do(req) + + if err != nil { + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + bodyBytes, _ := io.ReadAll(resp.Body) + + bodyText := string(bodyBytes) + + if !strings.Contains(bodyText, ".TS") { + (*user)[2] = "Offline" + (*user)[3] = "Offline" + (*user)[4] = "Offline" + Concurrent(user) + } + + scanner := bufio.NewScanner(strings.NewReader(bodyText)) + + for scanner.Scan() { + line := scanner.Text() + if strings.HasSuffix(line, ".TS") { + + playlistUrl := fmt.Sprintf("%s://%s%s/%s", parsedUrl.Scheme, parsedUrl.Host, newPath, line) + + if !playlistUrls[playlistUrl] { + + resp, err := http.Get(playlistUrl) + + if err != nil { + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + bytes += resp.ContentLength + elapsed_time := time.Since(start_time) + + (*user)[2] = tools.FormatBytes(bytes) + (*user)[3] = tools.FormatTime(elapsed_time) + (*user)[4] = filename + + _, err = io.Copy(out, resp.Body) + + if err != nil { + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + playlistUrls[playlistUrl] = true + } + } + } + + if err := scanner.Err(); err != nil { + fmt.Println(err) + } + + time.Sleep(3 * time.Second) + } +} + +func Download(bjId string, nickname string, broad_no string, playlist string) bool { tools.Exists("downloads/Afreeca/" + bjId) client := &http.Client{} @@ -32,7 +124,7 @@ func Download(bjId string, nickname string, playlist string) bool { newPath := strings.Join(pathSegments[:len(pathSegments)-1], "/") - filename := nickname + "-" + bjId + "-" + time.Now().Format("200601021504") + "-afreeca.ts" + filename := nickname + "-" + bjId + "-" + broad_no + "-" + time.Now().Format("200601021504") + "-afreeca.ts" out, _ := os.Create("downloads/Afreeca/" + bjId + "/" + filename) diff --git a/plugins/afreeca/verify.go b/plugins/afreeca/verify.go index 6835975..f0172c3 100644 --- a/plugins/afreeca/verify.go +++ b/plugins/afreeca/verify.go @@ -46,6 +46,15 @@ func CheckExists(bjId string) bool { } } +func ConcurrentCheck(bjId string) bool { + for { + if CheckOnline(bjId) { + return true + } + time.Sleep(3 * time.Minute) + } +} + func DvrCheck(bjId string) bool { for { if CheckOnline(bjId) { diff --git a/plugins/bigo/bigo.go b/plugins/bigo/bigo.go index 413947c..b0cbb37 100644 --- a/plugins/bigo/bigo.go +++ b/plugins/bigo/bigo.go @@ -6,6 +6,22 @@ import ( "os" ) +func Concurrent(user *[]string) { + tools.Exists("downloads/Bigo") + + if !CheckExists((*user)[0]) { + (*user)[2] = "Not found" + (*user)[3] = "Not found" + (*user)[4] = "Not found" + return + } + + if ConcurrentCheck((*user)[0]) { + _, nickname, url := GetStreamData((*user)[0]) + ConcurrentDownload(user, nickname, url) + } +} + func Start(userId string) { tools.Exists("downloads/Bigo") diff --git a/plugins/bigo/download.go b/plugins/bigo/download.go index 94d948f..0783c6f 100644 --- a/plugins/bigo/download.go +++ b/plugins/bigo/download.go @@ -5,7 +5,6 @@ import ( "fmt" "horsaen/afreeca-downloader/tools" "io" - "log" "net/http" "net/url" "os" @@ -13,16 +12,13 @@ import ( "time" ) -func Download(playlist string, nickname string, userId string) bool { +func ConcurrentDownload(user *[]string, nickname string, playlist string) { + userId := (*user)[0] tools.Exists("downloads/Bigo/" + userId) client := &http.Client{} - req, err := http.NewRequest("GET", playlist, nil) - - if err != nil { - log.Fatal(err) - } + req, _ := http.NewRequest("GET", playlist, nil) parsedUrl, _ := url.Parse(playlist) @@ -39,15 +35,94 @@ func Download(playlist string, nickname string, userId string) bool { resp, err := client.Do(req) if err != nil { - fmt.Println(err) + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + bodyBytes, _ := io.ReadAll(resp.Body) + + bodyText := string(bodyBytes) + + if !strings.Contains(bodyText, ".ts") { + (*user)[2] = "Offline" + (*user)[3] = "Offline" + (*user)[4] = "Offline" + Concurrent(user) + } + + scanner := bufio.NewScanner(strings.NewReader(bodyText)) + + for scanner.Scan() { + line := scanner.Text() + if strings.HasSuffix(line, ".ts") { + + playlistUrl := fmt.Sprintf("%s://%s/%s", parsedUrl.Scheme, parsedUrl.Host, line) + + if !playlistUrls[playlistUrl] { + + resp, err := http.Get(playlistUrl) + + if err != nil { + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + bytes += resp.ContentLength + elapsed_time := time.Since(start_time) + + (*user)[2] = tools.FormatBytes(bytes) + (*user)[3] = tools.FormatTime(elapsed_time) + (*user)[4] = filename + + _, err = io.Copy(out, resp.Body) + + if err != nil { + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + playlistUrls[playlistUrl] = true + } + } } - bodyBytes, err := io.ReadAll(resp.Body) + time.Sleep(3 * time.Second) + } +} + +func Download(playlist string, nickname string, userId string) bool { + tools.Exists("downloads/Bigo/" + userId) + + client := &http.Client{} + + req, _ := http.NewRequest("GET", playlist, nil) + + parsedUrl, _ := url.Parse(playlist) + + filename := nickname + "-" + userId + "-" + time.Now().Format("200601021504") + "-bigo.ts" + + out, _ := os.Create("downloads/Bigo/" + userId + "/" + filename) + + playlistUrls := make(map[string]bool) + + var bytes int64 = 0 + var start_time = time.Now() + + for { + resp, err := client.Do(req) if err != nil { fmt.Println(err) } + bodyBytes, _ := io.ReadAll(resp.Body) + bodyText := string(bodyBytes) if !strings.Contains(bodyText, ".ts") { @@ -86,10 +161,6 @@ func Download(playlist string, nickname string, userId string) bool { } } - if err := scanner.Err(); err != nil { - fmt.Println(err) - } - time.Sleep(3 * time.Second) } } diff --git a/plugins/bigo/verify.go b/plugins/bigo/verify.go index c473782..8d7d51f 100644 --- a/plugins/bigo/verify.go +++ b/plugins/bigo/verify.go @@ -66,6 +66,15 @@ func CheckExists(userId string) bool { } } +func ConcurrentCheck(userId string) bool { + for { + if CheckOnline(userId) { + return true + } + time.Sleep(3 * time.Minute) + } +} + func DvrCheck(userId string) bool { for { if CheckOnline(userId) { diff --git a/plugins/concurrent/concurrent.go b/plugins/concurrent/concurrent.go new file mode 100644 index 0000000..332020c --- /dev/null +++ b/plugins/concurrent/concurrent.go @@ -0,0 +1,62 @@ +package concurrent + +import ( + "bufio" + "horsaen/afreeca-downloader/plugins/afreeca" + "horsaen/afreeca-downloader/plugins/bigo" + "horsaen/afreeca-downloader/plugins/flex" + "horsaen/afreeca-downloader/plugins/kick" + "horsaen/afreeca-downloader/plugins/panda" + "horsaen/afreeca-downloader/tools" + "os" + "strings" + "time" +) + +func GetUsers() [][]string { + var users [][]string + + home, _ := os.UserHomeDir() + + file, _ := os.Open(home + "/.afreeca-downloader/users") + + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + line := scanner.Text() + split := strings.Split(line, ", ") + + init := []string{"Offline", "Offline", "Offline"} + + split = append(split, init...) + + users = append(users, split) + } + + return users +} + +func Start() { + users := GetUsers() + + for i := range users { + switch users[i][1] { + case "afreeca": + go afreeca.Concurrent(&users[i]) + case "bigo": + go bigo.Concurrent(&users[i]) + case "kick": + go kick.Concurrent(&users[i]) + case "flex": + go flex.Concurrent(&users[i]) + case "panda": + go panda.Concurrent(&users[i]) + } + } + + for { + Table(users) + time.Sleep(1 * time.Second) + tools.ClearCli() + } +} diff --git a/plugins/concurrent/table.go b/plugins/concurrent/table.go new file mode 100644 index 0000000..d61ee49 --- /dev/null +++ b/plugins/concurrent/table.go @@ -0,0 +1,20 @@ +package concurrent + +import ( + "github.com/fatih/color" + "github.com/rodaine/table" +) + +func Table(users [][]string) { + headerFmt := color.New(color.FgMagenta, color.Underline).SprintfFunc() + columnFmt := color.New(color.FgMagenta).SprintfFunc() + + tbl := table.New("Platform", "Name", "Size", "Duration", "Path") + tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) + + for _, user := range users { + tbl.AddRow(user[1], user[0], user[2], user[3], user[4]) + } + + tbl.Print() +} diff --git a/plugins/flex/download.go b/plugins/flex/download.go index e971974..4cf86c0 100644 --- a/plugins/flex/download.go +++ b/plugins/flex/download.go @@ -12,6 +12,88 @@ import ( "time" ) +func ConcurrentDownload(user *[]string, playlist string, nickname string, userId string) { + tools.Exists("downloads/Flex/" + userId) + + tr := http.Transport{ + DisableCompression: true, + } + + client := &http.Client{Transport: &tr} + + req, _ := http.NewRequest("GET", playlist, nil) + + filename := nickname + "-" + userId + "-" + time.Now().Format("200601021504") + "-flex.ts" + + out, _ := os.Create("downloads/Flex/" + userId + "/" + filename) + + playlistUrls := make(map[string]bool) + + var bytes int64 = 0 + var start_time = time.Now() + + for { + resp, err := client.Do(req) + + if err != nil { + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + bodyBytes, _ := io.ReadAll(resp.Body) + + bodyText := string(bodyBytes) + + if !strings.Contains(bodyText, ".ts") { + (*user)[2] = "Offline" + (*user)[3] = "Offline" + (*user)[4] = "Offline" + Concurrent(user) + } + + scanner := bufio.NewScanner(strings.NewReader(bodyText)) + + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "https://") { + + if !playlistUrls[line] { + + resp, err := http.Get(line) + + if err != nil { + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + // since flex doesn't actually return a content-length header for go just use this instead of having to read the segments in ram + filesize, _ := os.Stat("downloads/Flex/" + userId + "/" + filename) + bytes = filesize.Size() + elapsed_time := time.Since(start_time) + + (*user)[2] = tools.FormatBytes(bytes) + (*user)[3] = tools.FormatTime(elapsed_time) + (*user)[4] = filename + + _, err = io.Copy(out, resp.Body) + + if err != nil { + fmt.Println(err) + } + + playlistUrls[line] = true + } + } + } + + time.Sleep(3 * time.Second) + } +} + func Download(playlist string, nickname string, userId string) bool { tools.Exists("downloads/Flex/" + userId) @@ -43,11 +125,7 @@ func Download(playlist string, nickname string, userId string) bool { fmt.Println(err) } - bodyBytes, err := io.ReadAll(resp.Body) - - if err != nil { - fmt.Println(err) - } + bodyBytes, _ := io.ReadAll(resp.Body) bodyText := string(bodyBytes) @@ -87,10 +165,6 @@ func Download(playlist string, nickname string, userId string) bool { } } - if err := scanner.Err(); err != nil { - fmt.Println(err) - } - time.Sleep(3 * time.Second) } } diff --git a/plugins/flex/flex.go b/plugins/flex/flex.go index ab4fc10..bb31a2c 100644 --- a/plugins/flex/flex.go +++ b/plugins/flex/flex.go @@ -5,6 +5,18 @@ import ( "horsaen/afreeca-downloader/tools" ) +func Concurrent(user *[]string) { + tools.Exists("downloads/Flex") + + if ConcurrentCheck((*user)[0]) { + masterPlaylist, nickname, id := StreamData((*user)[0]) + + url := ParseMaster(masterPlaylist) + + ConcurrentDownload(user, url, nickname, id) + } +} + func Start(userId string) { tools.Exists("downloads/Flex") diff --git a/plugins/flex/verify.go b/plugins/flex/verify.go index bb34667..cc8c763 100644 --- a/plugins/flex/verify.go +++ b/plugins/flex/verify.go @@ -10,6 +10,15 @@ import ( "time" ) +func ConcurrentCheck(userId string) bool { + for { + if CheckOnline(userId) { + return true + } + time.Sleep(3 * time.Minute) + } +} + func DvrCheck(userId string) bool { for { if CheckOnline(userId) { diff --git a/plugins/kick/download.go b/plugins/kick/download.go index 46dcfa6..add4494 100644 --- a/plugins/kick/download.go +++ b/plugins/kick/download.go @@ -5,23 +5,19 @@ import ( "fmt" "horsaen/afreeca-downloader/tools" "io" - "log" "net/http" "os" "strings" "time" ) -func Download(playlist string, userId string) bool { +func ConcurrentDownload(user *[]string, playlist string) { + userId := (*user)[0] tools.Exists("downloads/Kick/" + userId) client := &http.Client{} - req, err := http.NewRequest("GET", playlist, nil) - - if err != nil { - log.Fatal(err) - } + req, _ := http.NewRequest("GET", playlist, nil) filename := userId + "-" + time.Now().Format("200601021504") + "-kick.ts" @@ -36,15 +32,92 @@ func Download(playlist string, userId string) bool { resp, err := client.Do(req) if err != nil { - fmt.Println(err) + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + bodyBytes, _ := io.ReadAll(resp.Body) + + bodyText := string(bodyBytes) + + if !strings.Contains(bodyText, ".ts") { + (*user)[2] = "Offline" + (*user)[3] = "Offline" + (*user)[4] = "Offline" + Concurrent(user) + } + + scanner := bufio.NewScanner(strings.NewReader(bodyText)) + + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "https://") { + + if !playlistUrls[line] { + + resp, err := http.Get(line) + + if err != nil { + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + // since kick doesn't actually return a content-length header for go just use this instead of having to read the segments in ram + filesize, _ := os.Stat("downloads/Kick/" + userId + "/" + filename) + bytes = filesize.Size() + elapsed_time := time.Since(start_time) + + (*user)[2] = tools.FormatBytes(bytes) + (*user)[3] = tools.FormatTime(elapsed_time) + (*user)[4] = filename + + _, err = io.Copy(out, resp.Body) + + if err != nil { + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + playlistUrls[line] = true + } + } } - bodyBytes, err := io.ReadAll(resp.Body) + time.Sleep(3 * time.Second) + } +} + +func Download(playlist string, userId string) bool { + tools.Exists("downloads/Kick/" + userId) + + client := &http.Client{} + + req, _ := http.NewRequest("GET", playlist, nil) + + filename := userId + "-" + time.Now().Format("200601021504") + "-kick.ts" + + out, _ := os.Create("downloads/Kick/" + userId + "/" + filename) + + playlistUrls := make(map[string]bool) + + var bytes int64 = 0 + var start_time = time.Now() + + for { + resp, err := client.Do(req) if err != nil { fmt.Println(err) } + bodyBytes, _ := io.ReadAll(resp.Body) + bodyText := string(bodyBytes) if !strings.Contains(bodyText, ".ts") { @@ -83,10 +156,6 @@ func Download(playlist string, userId string) bool { } } - if err := scanner.Err(); err != nil { - fmt.Println(err) - } - time.Sleep(3 * time.Second) } } diff --git a/plugins/kick/kick.go b/plugins/kick/kick.go index a69df33..a83199b 100644 --- a/plugins/kick/kick.go +++ b/plugins/kick/kick.go @@ -5,6 +5,18 @@ import ( "horsaen/afreeca-downloader/tools" ) +func Concurrent(user *[]string) { + tools.Exists("downloads/Kick") + + if ConcurrentCheck((*user)[0]) { + masterPlaylist := GetMaster((*user)[0]) + + playlist := ParseMaster(masterPlaylist) + + ConcurrentDownload(user, playlist) + } +} + func Start(userId string) { tools.Exists("downloads/Kick") diff --git a/plugins/kick/verify.go b/plugins/kick/verify.go index c426fef..51ba47c 100644 --- a/plugins/kick/verify.go +++ b/plugins/kick/verify.go @@ -10,6 +10,15 @@ import ( "time" ) +func ConcurrentCheck(userId string) bool { + for { + if CheckOnline(userId) { + return true + } + time.Sleep(3 * time.Minute) + } +} + func DvrCheck(userId string) bool { for { if CheckOnline(userId) { diff --git a/plugins/panda/download.go b/plugins/panda/download.go index 3fa37a0..68a7420 100644 --- a/plugins/panda/download.go +++ b/plugins/panda/download.go @@ -5,23 +5,19 @@ import ( "fmt" "horsaen/afreeca-downloader/tools" "io" - "log" "net/http" "os" "strings" "time" ) -func Download(userId string, playlist string) bool { +func ConcurrentDownload(user *[]string, playlist string) { + userId := (*user)[0] tools.Exists("downloads/Panda/" + userId) client := &http.Client{} - req, err := http.NewRequest("GET", playlist, nil) - - if err != nil { - log.Fatal(err) - } + req, _ := http.NewRequest("GET", playlist, nil) filename := userId + "-" + time.Now().Format("200601021504") + "-panda.ts" @@ -36,15 +32,91 @@ func Download(userId string, playlist string) bool { resp, err := client.Do(req) if err != nil { - fmt.Println(err) + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + bodyBytes, _ := io.ReadAll(resp.Body) + + bodyText := string(bodyBytes) + + if !strings.Contains(bodyText, ".ts") { + (*user)[2] = "Offline" + (*user)[3] = "Offline" + (*user)[4] = "Offline" + Concurrent(user) } - bodyBytes, err := io.ReadAll(resp.Body) + scanner := bufio.NewScanner(strings.NewReader(bodyText)) + + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "https://") { + if !playlistUrls[line] { + + resp, err := http.Get(line) + + if err != nil { + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + // since panda doesn't actually return a content-length header for go just use this instead of having to read the segments in ram + filesize, _ := os.Stat("downloads/Panda/" + userId + "/" + filename) + bytes = filesize.Size() + elapsed_time := time.Since(start_time) + + (*user)[2] = tools.FormatBytes(bytes) + (*user)[3] = tools.FormatTime(elapsed_time) + (*user)[4] = filename + + _, err = io.Copy(out, resp.Body) + + if err != nil { + (*user)[2] = "error" + (*user)[3] = "error" + (*user)[4] = err.Error() + return + } + + playlistUrls[line] = true + } + } + } + + time.Sleep(3 * time.Second) + } +} + +func Download(userId string, playlist string) bool { + tools.Exists("downloads/Panda/" + userId) + + client := &http.Client{} + + req, _ := http.NewRequest("GET", playlist, nil) + + filename := userId + "-" + time.Now().Format("200601021504") + "-panda.ts" + + out, _ := os.Create("downloads/Panda/" + userId + "/" + filename) + + playlistUrls := make(map[string]bool) + + var bytes int64 = 0 + var start_time = time.Now() + + for { + resp, err := client.Do(req) if err != nil { fmt.Println(err) } + bodyBytes, _ := io.ReadAll(resp.Body) + bodyText := string(bodyBytes) if !strings.Contains(bodyText, ".ts") { @@ -82,10 +154,6 @@ func Download(userId string, playlist string) bool { } } - if err := scanner.Err(); err != nil { - fmt.Println(err) - } - time.Sleep(3 * time.Second) } } diff --git a/plugins/panda/panda.go b/plugins/panda/panda.go index 8749f1e..f0b402e 100644 --- a/plugins/panda/panda.go +++ b/plugins/panda/panda.go @@ -6,6 +6,25 @@ import ( "os" ) +func Concurrent(user *[]string) { + tools.Exists("downloads/Panda") + + if !CheckExists((*user)[0]) { + (*user)[2] = "Not found" + (*user)[3] = "Not found" + (*user)[4] = "Not found" + return + } + + if ConcurrentCheck((*user)[0]) { + masterPlaylist := GetMaster((*user)[0]) + + url := ParseMaster(masterPlaylist) + + ConcurrentDownload(user, url) + } +} + func Start(userId string) { tools.Exists("downloads/Panda") diff --git a/plugins/panda/verify.go b/plugins/panda/verify.go index d3c8bc8..ce989fe 100644 --- a/plugins/panda/verify.go +++ b/plugins/panda/verify.go @@ -44,6 +44,15 @@ func CheckExists(userId string) bool { } } +func ConcurrentCheck(userId string) bool { + for { + if CheckOnline(userId) { + return true + } + time.Sleep(3 * time.Minute) + } +} + func DvrCheck(userId string) bool { for { if CheckOnline(userId) { diff --git a/tools/cli.go b/tools/cli.go new file mode 100644 index 0000000..6f2988d --- /dev/null +++ b/tools/cli.go @@ -0,0 +1,20 @@ +package tools + +import ( + "os" + "os/exec" + "runtime" +) + +func ClearCli() { + switch runtime.GOOS { + case "linux", "darwin": + cmd := exec.Command("clear") + cmd.Stdout = os.Stdout + cmd.Run() + case "windows": + cmd := exec.Command("cls") + cmd.Stdout = os.Stdout + cmd.Run() + } +} diff --git a/tools/version.go b/tools/version.go new file mode 100644 index 0000000..fea99b9 --- /dev/null +++ b/tools/version.go @@ -0,0 +1,12 @@ +package tools + +import ( + "fmt" + "os" +) + +func Version() { + fmt.Println("https://github.com/horsaen/afreeca-downloader") + fmt.Println("v2.1.0") + os.Exit(0) +}