ytgo/scraper/scraper.go
2024-12-11 00:11:58 +01:00

176 lines
4 KiB
Go

package scraper
import (
"context"
"fmt"
"time"
// "log"
"html"
"strings"
"ytgo/config"
"google.golang.org/api/option"
"google.golang.org/api/youtube/v3"
)
type Video struct {
Title string
URL string
Channel string
Duration string
Views string
Thumbnail string
UploadDate string
}
func formatViews(count uint64) string {
switch {
case count >= 1000000000:
return fmt.Sprintf("%.1fB views", float64(count)/1000000000)
case count >= 1000000:
return fmt.Sprintf("%.1fM views", float64(count)/1000000)
case count >= 1000:
return fmt.Sprintf("%.1fK views", float64(count)/1000)
default:
return fmt.Sprintf("%d views", count)
}
}
func formatDuration(duration string) string {
// Remove PT from the start
duration = strings.TrimPrefix(duration, "PT")
var result strings.Builder
// Handle hours
if i := strings.Index(duration, "H"); i != -1 {
result.WriteString(duration[:i])
result.WriteString(":")
duration = duration[i+1:]
}
// Handle minutes
if i := strings.Index(duration, "M"); i != -1 {
minutes := duration[:i]
if len(minutes) == 1 {
result.WriteString("0")
}
result.WriteString(minutes)
result.WriteString(":")
duration = duration[i+1:]
} else if result.Len() > 0 {
result.WriteString("00:")
}
// Handle seconds
if i := strings.Index(duration, "S"); i != -1 {
seconds := duration[:i]
if len(seconds) == 1 {
result.WriteString("0")
}
result.WriteString(seconds)
} else {
result.WriteString("00")
}
return result.String()
}
func formatUploadDate(uploadDate string) string {
t, err := time.Parse(time.RFC3339, uploadDate)
if err != nil {
return uploadDate
}
now := time.Now()
diff := now.Sub(t)
days := int(diff.Hours() / 24)
formattedDate := t.Format("02-01-2006")
// If video is less than 30 days old, add "X days ago"
if days < 30 {
var timeAgo string
switch {
case days == 0:
hours := int(diff.Hours())
if hours == 0 {
timeAgo = "just now"
} else {
timeAgo = fmt.Sprintf("%dh ago", hours)
}
case days == 1:
timeAgo = "1 day ago"
default:
timeAgo = fmt.Sprintf("%d days ago", days)
}
return fmt.Sprintf("%s (%s)", formattedDate, timeAgo)
}
return formattedDate
}
func FetchVideos(query string) ([]Video, error) {
cfg, err := config.LoadConfig()
if err != nil {
return nil, fmt.Errorf("error loading config: %w", err)
}
ctx := context.Background()
youtubeService, err := youtube.NewService(ctx, option.WithAPIKey(cfg.APIKey))
if err != nil {
return nil, fmt.Errorf("error creating YouTube client: %w", err)
}
// Make the search request
call := youtubeService.Search.List([]string{"snippet"}).
Q(query).
MaxResults(50).
Type("video").
VideoDuration("any")
response, err := call.Do()
if err != nil {
return nil, fmt.Errorf("error making search request: %w", err)
}
var videos []Video
for _, item := range response.Items {
video := Video{
Title: item.Snippet.Title,
URL: fmt.Sprintf("https://www.youtube.com/watch?v=%s", item.Id.VideoId),
Channel: item.Snippet.ChannelTitle,
Thumbnail: item.Snippet.Thumbnails.Default.Url,
UploadDate: item.Snippet.PublishedAt,
}
videos = append(videos, video)
}
// Get additional video details (duration, views) in a single request
videoIds := make([]string, len(response.Items))
for i, item := range response.Items {
videoIds[i] = item.Id.VideoId
}
// Get video statistics
statsCall := youtubeService.Videos.List([]string{"contentDetails", "statistics"}).
Id(videoIds...)
statsResponse, err := statsCall.Do()
if err != nil {
return nil, fmt.Errorf("error fetching video details: %w", err)
}
// Update videos with additional information
for i, stat := range statsResponse.Items {
if i < len(videos) {
videos[i].Duration = formatDuration(stat.ContentDetails.Duration)
videos[i].Views = formatViews(stat.Statistics.ViewCount)
videos[i].Title = html.UnescapeString(videos[i].Title)
videos[i].UploadDate = formatUploadDate(videos[i].UploadDate)
}
}
return videos, nil
}