Info Fetching completed
This commit is contained in:
parent
b5f8fb0118
commit
f129d1912b
|
@ -26,4 +26,4 @@ tmp/
|
||||||
# Logs
|
# Logs
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# Any other temporary or build files specific to your project
|
__*
|
|
@ -35,13 +35,7 @@ func init() {
|
||||||
func runInfoCommand() error {
|
func runInfoCommand() error {
|
||||||
album, err := bunkr.FetchAlbumInfo(albumURL)
|
album, err := bunkr.FetchAlbumInfo(albumURL)
|
||||||
|
|
||||||
// Print
|
fmt.Println(album.ToString())
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
7
go.mod
7
go.mod
|
@ -3,10 +3,13 @@ module tea.chunkbyte.com/kato/grabrr
|
||||||
go 1.23.2
|
go 1.23.2
|
||||||
|
|
||||||
require (
|
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/andybalholm/cascadia v1.3.3 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // 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
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
golang.org/x/net v0.39.0 // indirect
|
golang.org/x/net v0.39.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"tea.chunkbyte.com/kato/grabrr/lib/helper"
|
||||||
strux "tea.chunkbyte.com/kato/grabrr/lib/strux"
|
strux "tea.chunkbyte.com/kato/grabrr/lib/strux"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
"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)
|
return nil, fmt.Errorf("failed to parse URL: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
album.ID = helper.GetLastPathElement(album.URL)
|
||||||
// Title
|
// Title
|
||||||
album.Title = strings.TrimSpace(doc.Find("h1.truncate").First().Text())
|
album.Title = strings.TrimSpace(doc.Find("h1.truncate").First().Text())
|
||||||
|
|
||||||
|
// Views
|
||||||
|
if albumViews, err := album.FetchAlbumViews(); err == nil {
|
||||||
|
album.Views = albumViews
|
||||||
|
}
|
||||||
|
|
||||||
// Files
|
// Files
|
||||||
doc.Find(".grid-images .theItem").Each(func(i int, s *goquery.Selection) {
|
doc.Find(".grid-images .theItem").Each(func(i int, s *goquery.Selection) {
|
||||||
linkTag := s.Find("a")
|
linkTag := s.Find("a")
|
||||||
|
@ -58,7 +64,7 @@ func FetchAlbumInfo(rawURL string) (*strux.Album, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if theSizeEl := s.Find(".theSize"); theSizeEl != nil {
|
if theSizeEl := s.Find(".theSize"); theSizeEl != nil {
|
||||||
fileSize, err = ParseSizeToKB(theSizeEl.Text())
|
fileSize, err = helper.ParseSizeToKB(theSizeEl.Text())
|
||||||
}
|
}
|
||||||
|
|
||||||
if href[0] == '/' {
|
if href[0] == '/' {
|
||||||
|
@ -72,38 +78,18 @@ func FetchAlbumInfo(rawURL string) (*strux.Album, error) {
|
||||||
URL: fileURL,
|
URL: fileURL,
|
||||||
Filename: fileTitle,
|
Filename: fileTitle,
|
||||||
SizeKB: fileSize,
|
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.Files = append(album.Files, file)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
album.SizeKB = album.GetKbSize()
|
||||||
|
|
||||||
return album, nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
MAIN_HOST_URL = "s.bunkr.ru"
|
||||||
|
)
|
|
@ -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)
|
||||||
|
}
|
|
@ -1,18 +1,22 @@
|
||||||
package structs
|
package structs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"tea.chunkbyte.com/kato/grabrr/lib/constants"
|
||||||
|
"tea.chunkbyte.com/kato/grabrr/lib/helper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// File represents a single file within a Bunkkr album.
|
// AlbumStatsResponse represents the JSON response from the Bunkr API.
|
||||||
type File struct {
|
type AlbumStatsResponse struct {
|
||||||
Title string `json:"title"`
|
Slug string `json:"slug"`
|
||||||
SizeKB int64 `json:"size_kb"`
|
ViewCount int `json:"viewCount"`
|
||||||
DateUploaded time.Time `json:"date_uploaded"`
|
|
||||||
URL *url.URL `json:"url"`
|
|
||||||
Filename string `json:"filename"`
|
|
||||||
Format string `json:"format"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Album represents a Bunkkr album.
|
// Album represents a Bunkkr album.
|
||||||
|
@ -24,3 +28,136 @@ type Album struct {
|
||||||
URL *url.URL `json:"url"`
|
URL *url.URL `json:"url"`
|
||||||
SizeKB int64 `json:"size_kb"`
|
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()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue