171 lines
4 KiB
Go
171 lines
4 KiB
Go
package scraper
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
// "log"
|
|
"html"
|
|
"strings"
|
|
|
|
"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
|
|
}
|
|
|
|
// Replace with your actual API key
|
|
const API_KEY = "AIzaSyAzsihRkp8mYTOXLOkVN09yTqld9TJ4Nts"
|
|
|
|
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) {
|
|
ctx := context.Background()
|
|
youtubeService, err := youtube.NewService(ctx, option.WithAPIKey(API_KEY))
|
|
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
|
|
}
|