Files
Tomas Dvorak d27cf14110 first test
2026-02-08 14:14:55 +01:00

199 lines
5.6 KiB
Go

package services
import (
"fmt"
"time"
)
// YouTubeChannelService handles specific channel integrations
type YouTubeChannelService struct {
YouTubeService *YouTubeService
CacheService *YouTubeCacheService
}
// NewYouTubeChannelService creates a new instance of YouTubeChannelService
func NewYouTubeChannelService(youtubeService *YouTubeService, cacheService *YouTubeCacheService) *YouTubeChannelService {
return &YouTubeChannelService{
YouTubeService: youtubeService,
CacheService: cacheService,
}
}
// GetPredefinedChannels returns the list of predefined channels
func GetPredefinedChannels() []Channel {
return []Channel{
{
ID: "fireship",
Name: "Fireship",
Description: "Rapid web development tutorials and courses",
Thumbnail: "https://img.youtube.com/vi/UCsBjURrPoezykLs9EqgAJVQ/mqdefault.jpg",
},
{
ID: "networkchuck",
Name: "NetworkChuck",
Description: "Cybersecurity and networking tutorials",
Thumbnail: "https://img.youtube.com/vi/UCNlz1cb4DvEx7rTnT2s7B3A/mqdefault.jpg",
},
{
ID: "programmingwithmosh",
Name: "Programming with Mosh",
Description: "Comprehensive programming tutorials",
Thumbnail: "https://img.youtube.com/vi/UC8butUNob-8kuy47X7vH6ws/mqdefault.jpg",
},
{
ID: "traversymedia",
Name: "Traversy Media",
Description: "Web development and design tutorials",
Thumbnail: "https://img.youtube.com/vi/UC29J8QxEQ7QmM0TJ_8Jt_gQ/mqdefault.jpg",
},
{
ID: "thenewboston",
Name: "The New Boston",
Description: "Computer science and programming courses",
Thumbnail: "https://img.youtube.com/vi/UCrwkHaJ-9Sd74Kx1n-9Qjg/mqdefault.jpg",
},
}
}
// Channel represents a YouTube channel
type Channel struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Thumbnail string `json:"thumbnail"`
}
// GetFireshipVideos fetches latest videos from Fireship channel
func (ycs *YouTubeChannelService) GetFireshipVideos(limit int) ([]YouTubeVideo, error) {
// Use cached data to avoid rate limiting
response, err := ycs.CacheService.GetCachedChannelVideos("fireship", limit)
if err != nil {
if err.Error() == "YouTube is rate limiting us. Please try again later." {
// Return rate limiting error
return nil, fmt.Errorf("YouTube is rate limiting us. Please try again later.")
}
return nil, fmt.Errorf("failed to fetch Fireship videos: %w", err)
}
// Return all videos without filtering
return response.Videos, nil
}
// GetNetworkChuckVideos fetches latest videos from Network Chuck channel
func (ycs *YouTubeChannelService) GetNetworkChuckVideos(limit int) ([]YouTubeVideo, error) {
// Use cached data to avoid rate limiting
response, err := ycs.CacheService.GetCachedChannelVideos("networkchuck", limit)
if err != nil {
if err.Error() == "YouTube is rate limiting us. Please try again later." {
// Return rate limiting error
return nil, fmt.Errorf("YouTube is rate limiting us. Please try again later.")
}
return nil, fmt.Errorf("failed to fetch Network Chuck videos: %w", err)
}
// Return all videos without filtering
return response.Videos, nil
}
// GetChannelInfo fetches basic information about a channel
func (ycs *YouTubeChannelService) GetChannelInfo(channelID string) (*ChannelInfo, error) {
// For now, return basic info from predefined channels
channels := GetPredefinedChannels()
for _, channel := range channels {
if channel.ID == channelID {
return &ChannelInfo{
ID: channel.ID,
Title: channel.Name,
Description: channel.Description,
Thumbnail: channel.Thumbnail,
}, nil
}
}
return nil, fmt.Errorf("channel not found")
}
// ChannelInfo represents basic channel information
type ChannelInfo struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Thumbnail string `json:"thumbnail"`
}
// Helper functions
// parseYouTubeDuration converts ISO 8601 duration string to seconds
func parseYouTubeDuration(duration string) int {
// YouTube duration format: PT4M13S (4 minutes 13 seconds)
// Simple parser for common formats
seconds := 0
current := 0
for _, char := range duration {
switch char {
case 'H', 'h':
seconds += current * 3600
current = 0
case 'M', 'm':
seconds += current * 60
current = 0
case 'S', 's':
seconds += current
current = 0
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
current = current*10 + int(char-'0')
}
}
return seconds
}
func containsIgnoreCase(s, substr string) bool {
return len(s) >= len(substr) && (s == substr ||
(len(s) > len(substr) &&
(s[:len(substr)] == substr ||
s[len(s)-len(substr):] == substr ||
containsSubstringIgnoreCase(s, substr))))
}
func containsSubstringIgnoreCase(s, substr string) bool {
s = toLower(s)
substr = toLower(substr)
return contains(s, substr)
}
func toLower(s string) string {
result := make([]rune, len([]rune(s)))
for i, r := range []rune(s) {
if r >= 'A' && r <= 'Z' {
result[i] = r + ('a' - 'A')
} else {
result[i] = r
}
}
return string(result)
}
func contains(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
func isRecentVideo(publishedAt string, months int) bool {
// Parse the published date (ISO 8601 format)
layout := "2006-01-02T15:04:05Z"
publishedTime, err := time.Parse(layout, publishedAt)
if err != nil {
return false
}
// Check if the video is within the specified months
cutoffTime := time.Now().AddDate(0, -months, 0)
return publishedTime.After(cutoffTime)
}