From f129d1912bf970c4fef4618c0276f2f60c37bc8f Mon Sep 17 00:00:00 2001 From: Daniel Legt Date: Wed, 23 Jul 2025 16:36:22 +0300 Subject: [PATCH] Info Fetching completed --- .gitignore | 2 +- cmd/info.go | 8 +- go.mod | 7 +- lib/bunkr/bunkr.go | 46 ++++------- lib/constants/constants.go | 5 ++ lib/helper/helper.go | 98 ++++++++++++++++++++++++ lib/strux/structs.go | 153 +++++++++++++++++++++++++++++++++++-- main.go | 4 - 8 files changed, 271 insertions(+), 52 deletions(-) create mode 100644 lib/constants/constants.go create mode 100644 lib/helper/helper.go diff --git a/.gitignore b/.gitignore index ebdde87..7fcc86a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,4 @@ tmp/ # Logs *.log -# Any other temporary or build files specific to your project \ No newline at end of file +__* \ No newline at end of file diff --git a/cmd/info.go b/cmd/info.go index e223c68..d123e7e 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -35,13 +35,7 @@ func init() { func runInfoCommand() error { album, err := bunkr.FetchAlbumInfo(albumURL) - // Print - fmt.Printf("Album Title: %s\n", album.Title) - fmt.Printf("Album URL: %s\n", album.URL.String()) - fmt.Printf("Files (%d):\n", len(album.Files)) - for _, f := range album.Files { - fmt.Printf(" - %s (%s)\n", f.Filename, f.URL.String()) - } + fmt.Println(album.ToString()) return err } diff --git a/go.mod b/go.mod index 0cb7f76..7aa1487 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,13 @@ module tea.chunkbyte.com/kato/grabrr go 1.23.2 require ( - github.com/PuerkitoBio/goquery v1.10.3 // indirect + github.com/PuerkitoBio/goquery v1.10.3 + github.com/spf13/cobra v1.9.1 +) + +require ( github.com/andybalholm/cascadia v1.3.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/spf13/cobra v1.9.1 // indirect github.com/spf13/pflag v1.0.6 // indirect golang.org/x/net v0.39.0 // indirect ) diff --git a/lib/bunkr/bunkr.go b/lib/bunkr/bunkr.go index 11da74e..b1ad3c9 100644 --- a/lib/bunkr/bunkr.go +++ b/lib/bunkr/bunkr.go @@ -4,9 +4,9 @@ import ( "fmt" "net/http" "net/url" - "strconv" "strings" + "tea.chunkbyte.com/kato/grabrr/lib/helper" strux "tea.chunkbyte.com/kato/grabrr/lib/strux" "github.com/PuerkitoBio/goquery" @@ -39,9 +39,15 @@ func FetchAlbumInfo(rawURL string) (*strux.Album, error) { return nil, fmt.Errorf("failed to parse URL: %w", err) } + album.ID = helper.GetLastPathElement(album.URL) // Title album.Title = strings.TrimSpace(doc.Find("h1.truncate").First().Text()) + // Views + if albumViews, err := album.FetchAlbumViews(); err == nil { + album.Views = albumViews + } + // Files doc.Find(".grid-images .theItem").Each(func(i int, s *goquery.Selection) { linkTag := s.Find("a") @@ -58,7 +64,7 @@ func FetchAlbumInfo(rawURL string) (*strux.Album, error) { } if theSizeEl := s.Find(".theSize"); theSizeEl != nil { - fileSize, err = ParseSizeToKB(theSizeEl.Text()) + fileSize, err = helper.ParseSizeToKB(theSizeEl.Text()) } if href[0] == '/' { @@ -72,38 +78,18 @@ func FetchAlbumInfo(rawURL string) (*strux.Album, error) { URL: fileURL, Filename: fileTitle, SizeKB: fileSize, - Format: fileURL.Path[strings.LastIndex(fileURL.Path, ".")+1:], + Format: helper.GetMimeTypeFromFilename(fileTitle), + } + + uploadTimeStampRaw := strings.TrimSpace(s.Find(".theDate").First().Text()) + if newDate, err := helper.ParseCustomDateTime(uploadTimeStampRaw); err == nil { + file.DateUploaded = newDate } album.Files = append(album.Files, file) }) + album.SizeKB = album.GetKbSize() + return album, nil } - -func ParseSizeToKB(sizeStr string) (int64, error) { - s := strings.TrimSpace(strings.ToLower(sizeStr)) - - var multiplier float64 - switch { - case strings.HasSuffix(s, "gb"): - multiplier = 1024 * 1024 - s = strings.TrimSuffix(s, "gb") - case strings.HasSuffix(s, "mb"): - multiplier = 1024 - s = strings.TrimSuffix(s, "mb") - case strings.HasSuffix(s, "kb"): - multiplier = 1 - s = strings.TrimSuffix(s, "kb") - default: - return 0, fmt.Errorf("unrecognized unit in %q", sizeStr) - } - - s = strings.TrimSpace(s) - value, err := strconv.ParseFloat(s, 64) - if err != nil { - return 0, fmt.Errorf("invalid number %q: %w", s, err) - } - - return int64(value * multiplier), nil -} diff --git a/lib/constants/constants.go b/lib/constants/constants.go new file mode 100644 index 0000000..1bd1a5c --- /dev/null +++ b/lib/constants/constants.go @@ -0,0 +1,5 @@ +package constants + +const ( + MAIN_HOST_URL = "s.bunkr.ru" +) diff --git a/lib/helper/helper.go b/lib/helper/helper.go new file mode 100644 index 0000000..ebd870f --- /dev/null +++ b/lib/helper/helper.go @@ -0,0 +1,98 @@ +package helper + +import ( + "fmt" + "mime" + "net/url" + "path/filepath" + "strconv" + "strings" + "time" +) + +func ParseSizeToKB(sizeStr string) (int64, error) { + s := strings.TrimSpace(strings.ToLower(sizeStr)) + + var multiplier float64 + switch { + case strings.HasSuffix(s, "gb"): + multiplier = 1024 * 1024 + s = strings.TrimSuffix(s, "gb") + case strings.HasSuffix(s, "mb"): + multiplier = 1024 + s = strings.TrimSuffix(s, "mb") + case strings.HasSuffix(s, "kb"): + multiplier = 1 + s = strings.TrimSuffix(s, "kb") + default: + return 0, fmt.Errorf("unrecognized unit in %q", sizeStr) + } + + s = strings.TrimSpace(s) + value, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0, fmt.Errorf("invalid number %q: %w", s, err) + } + + return int64(value * multiplier), nil +} + +func FormatSizeFromKB(sizeKB int64) (string, error) { + if sizeKB < 0 { + return "", fmt.Errorf("size must be non-negative") + } + + units := []string{"Kb", "Mb", "Gb", "Tb"} //Add more if needed. + unitIndex := 0 + + sizeFloat := float64(sizeKB) + + for sizeFloat >= 1024 && unitIndex < len(units)-1 { + sizeFloat /= 1024 + unitIndex++ + } + + return fmt.Sprintf("%.2f %s", sizeFloat, units[unitIndex]), nil +} + +func GetLastPathElement(u *url.URL) string { + + path := u.Path + if path == "" { + return "" // Handle empty path + } + + parts := strings.Split(path, "/") + if len(parts) == 0 { + return "" + } + + return parts[len(parts)-1] +} + +func GetMimeTypeFromFilename(filename string) string { + ext := strings.ToLower(filepath.Ext(filename)) + if ext == "" { + return "application/octet-stream" // fallback + } + + mimeType := mime.TypeByExtension(ext) + if mimeType != "" { + return mimeType + } + + // handle common missing types + switch ext { + case ".mov": + return "video/quicktime" + case ".rar": + return "application/x-rar-compressed" + default: + return "application/octet-stream" + } +} + +func ParseCustomDateTime(input string) (time.Time, error) { + layout := "15:04:05 02/01/2006" + return time.Parse(layout, input) +} diff --git a/lib/strux/structs.go b/lib/strux/structs.go index 35cbc23..f59b0c1 100644 --- a/lib/strux/structs.go +++ b/lib/strux/structs.go @@ -1,18 +1,22 @@ package structs import ( + "encoding/json" + "fmt" + "io" + "net/http" "net/url" + "strings" "time" + + "tea.chunkbyte.com/kato/grabrr/lib/constants" + "tea.chunkbyte.com/kato/grabrr/lib/helper" ) -// File represents a single file within a Bunkkr album. -type File struct { - Title string `json:"title"` - SizeKB int64 `json:"size_kb"` - DateUploaded time.Time `json:"date_uploaded"` - URL *url.URL `json:"url"` - Filename string `json:"filename"` - Format string `json:"format"` +// AlbumStatsResponse represents the JSON response from the Bunkr API. +type AlbumStatsResponse struct { + Slug string `json:"slug"` + ViewCount int `json:"viewCount"` } // Album represents a Bunkkr album. @@ -24,3 +28,136 @@ type Album struct { URL *url.URL `json:"url"` SizeKB int64 `json:"size_kb"` } + +// File represents a single file within a Bunkkr album. +type File struct { + Title string `json:"title"` + SizeKB int64 `json:"size_kb"` + DateUploaded time.Time `json:"date_uploaded"` + URL *url.URL `json:"url"` + Filename string `json:"filename"` + Format string `json:"format"` +} + +func (a *Album) GetSize() string { + albumSize, err := helper.FormatSizeFromKB(a.SizeKB) + if err != nil { + fmt.Printf("error: There has been an error parsing the size, defaulting to variable value [%v]", err) + albumSize = fmt.Sprintf("Unknown Size (%v Kb)", a.SizeKB) + } + + return albumSize +} + +func (f *File) GetSize() string { + fileSize, err := helper.FormatSizeFromKB(f.SizeKB) + if err != nil { + fmt.Printf("error: There has been an error parsing the size, defaulting to variable value [%v]", err) + fileSize = fmt.Sprintf("Unknown Size (%v Kb)", f.SizeKB) + } + + return fileSize +} + +// FUNCTIONS + +func (album *Album) FetchAlbumViews() (int, error) { + url := fmt.Sprintf("https://%s/api/album/stats/%s", constants.MAIN_HOST_URL, album.ID) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return -1, fmt.Errorf("error creating request: %w", err) + } + + // ✅ https://s.bunkr.ru/api/album/stats/p2WBVS3a + // 🔴 https://bunkr.cr/api/album/stats/p2WBVS3a + + req.Header.Add("Accept-Language", "en-US,en;q=0.6") + req.Header.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return -1, fmt.Errorf("error making request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return -1, fmt.Errorf("request failed with status code: %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return -1, fmt.Errorf("error reading response body: %w", err) + } + + var albumStats AlbumStatsResponse + err = json.Unmarshal(body, &albumStats) + if err != nil { + return -1, fmt.Errorf("error unmarshaling JSON: %w", err) + } + + return albumStats.ViewCount, nil +} + +func (a *Album) GetKbSize() int64 { + var albumSize int64 = 0 + for _, v := range a.Files { + albumSize += v.SizeKB + } + + return albumSize +} + +func (a *Album) ToString() string { + var builder strings.Builder + builder.WriteString("🆔 ID: ") + builder.WriteString(a.ID) + builder.WriteString("\n") + builder.WriteString("📂 Album: ") + builder.WriteString(a.Title) + builder.WriteString("\n") + builder.WriteString("👁️ Views: ") + builder.WriteString(fmt.Sprintf("%d", a.Views)) + builder.WriteString("\n") + builder.WriteString("🌐 URL: ") + builder.WriteString(a.URL.String()) + builder.WriteString("\n") + builder.WriteString("💾 Size: ") + builder.WriteString(a.GetSize()) + builder.WriteString("\n") + + builder.WriteString("## File List: ##\n") + for _, file := range a.Files { + fString := strings.ReplaceAll(file.ToString(), "\n", "\n\t") + + builder.WriteString("\t") + builder.WriteString(fString) + builder.WriteString("\n\n") + } + + return builder.String() +} + +func (f *File) ToString() string { + var builder strings.Builder + builder.WriteString("📄 Title: ") + builder.WriteString(f.Title) + builder.WriteString("\n") + builder.WriteString("💾 Size: ") + builder.WriteString(f.GetSize()) + builder.WriteString("\n") + builder.WriteString("📅 Uploaded: ") + builder.WriteString(f.DateUploaded.Format(time.RFC3339)) + builder.WriteString("\n") + builder.WriteString("🌐 URL: ") + builder.WriteString(f.URL.String()) + builder.WriteString("\n") + builder.WriteString("🔘 filename: ") + builder.WriteString(f.Filename) + builder.WriteString("\n") + builder.WriteString("🟫 format: ") + builder.WriteString(f.Format) + + return builder.String() +} diff --git a/main.go b/main.go index 8d4e5b1..79a51d8 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,3 @@ -/* -Copyright © 2025 NAME HERE - -*/ package main import "tea.chunkbyte.com/kato/grabrr/cmd"