package services import ( "encoding/json" "fmt" "net/http" "net/url" "strconv" "neomovies-api/pkg/models" ) type TMDBService struct { accessToken string baseURL string client *http.Client } func NewTMDBService(accessToken string) *TMDBService { return &TMDBService{ accessToken: accessToken, baseURL: "https://api.themoviedb.org/3", client: &http.Client{}, } } func (s *TMDBService) makeRequest(endpoint string, target interface{}) error { req, err := http.NewRequest("GET", endpoint, nil) if err != nil { return err } // Используем Bearer токен вместо API key в query параметрах req.Header.Set("Authorization", "Bearer "+s.accessToken) req.Header.Set("Accept", "application/json") resp, err := s.client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("TMDB API error: %d", resp.StatusCode) } return json.NewDecoder(resp.Body).Decode(target) } func (s *TMDBService) SearchMovies(query string, page int, language, region string, year int) (*models.TMDBResponse, error) { params := url.Values{} params.Set("query", query) params.Set("page", strconv.Itoa(page)) params.Set("include_adult", "false") if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } if region != "" { params.Set("region", region) } if year > 0 { params.Set("year", strconv.Itoa(year)) } endpoint := fmt.Sprintf("%s/search/movie?%s", s.baseURL, params.Encode()) var response models.TMDBResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) SearchMulti(query string, page int, language string) (*models.MultiSearchResponse, error) { params := url.Values{} params.Set("query", query) params.Set("page", strconv.Itoa(page)) params.Set("include_adult", "false") if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/search/multi?%s", s.baseURL, params.Encode()) var response models.MultiSearchResponse err := s.makeRequest(endpoint, &response) if err != nil { return nil, err } // Фильтруем результаты: убираем "person", и без названия filteredResults := make([]models.MultiSearchResult, 0) for _, result := range response.Results { if result.MediaType == "person" { continue } hasTitle := false if result.MediaType == "movie" && result.Title != "" { hasTitle = true } else if result.MediaType == "tv" && result.Name != "" { hasTitle = true } if hasTitle { filteredResults = append(filteredResults, result) } } response.Results = filteredResults response.TotalResults = len(filteredResults) return &response, nil } // Алиас для совместимости с новым WebTorrent handler func (s *TMDBService) SearchTV(query string, page int, language string, firstAirDateYear int) (*models.TMDBTVResponse, error) { return s.SearchTVShows(query, page, language, firstAirDateYear) } func (s *TMDBService) SearchTVShows(query string, page int, language string, firstAirDateYear int) (*models.TMDBTVResponse, error) { params := url.Values{} params.Set("query", query) params.Set("page", strconv.Itoa(page)) params.Set("include_adult", "false") if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } if firstAirDateYear > 0 { params.Set("first_air_date_year", strconv.Itoa(firstAirDateYear)) } endpoint := fmt.Sprintf("%s/search/tv?%s", s.baseURL, params.Encode()) var response models.TMDBTVResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetMovie(id int, language string) (*models.Movie, error) { params := url.Values{} if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/movie/%d?%s", s.baseURL, id, params.Encode()) var movie models.Movie err := s.makeRequest(endpoint, &movie) return &movie, err } func (s *TMDBService) GetTVShow(id int, language string) (*models.TVShow, error) { params := url.Values{} if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/tv/%d?%s", s.baseURL, id, params.Encode()) var tvShow models.TVShow err := s.makeRequest(endpoint, &tvShow) return &tvShow, err } func (s *TMDBService) GetGenres(mediaType string, language string) (*models.GenresResponse, error) { params := url.Values{} if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/genre/%s/list?%s", s.baseURL, mediaType, params.Encode()) var response models.GenresResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetAllGenres() (*models.GenresResponse, error) { // Получаем жанры фильмов movieGenres, err := s.GetGenres("movie", "ru-RU") if err != nil { return nil, err } // Получаем жанры сериалов tvGenres, err := s.GetGenres("tv", "ru-RU") if err != nil { return nil, err } // Объединяем жанры, убирая дубликаты allGenres := make(map[int]models.Genre) for _, genre := range movieGenres.Genres { allGenres[genre.ID] = genre } for _, genre := range tvGenres.Genres { allGenres[genre.ID] = genre } // Преобразуем обратно в слайс var genres []models.Genre for _, genre := range allGenres { genres = append(genres, genre) } return &models.GenresResponse{Genres: genres}, nil } func (s *TMDBService) GetPopularMovies(page int, language, region string) (*models.TMDBResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } if region != "" { params.Set("region", region) } endpoint := fmt.Sprintf("%s/movie/popular?%s", s.baseURL, params.Encode()) var response models.TMDBResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetTopRatedMovies(page int, language, region string) (*models.TMDBResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } if region != "" { params.Set("region", region) } endpoint := fmt.Sprintf("%s/movie/top_rated?%s", s.baseURL, params.Encode()) var response models.TMDBResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetUpcomingMovies(page int, language, region string) (*models.TMDBResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } if region != "" { params.Set("region", region) } endpoint := fmt.Sprintf("%s/movie/upcoming?%s", s.baseURL, params.Encode()) var response models.TMDBResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetNowPlayingMovies(page int, language, region string) (*models.TMDBResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } if region != "" { params.Set("region", region) } endpoint := fmt.Sprintf("%s/movie/now_playing?%s", s.baseURL, params.Encode()) var response models.TMDBResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetMovieRecommendations(id, page int, language string) (*models.TMDBResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/movie/%d/recommendations?%s", s.baseURL, id, params.Encode()) var response models.TMDBResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetSimilarMovies(id, page int, language string) (*models.TMDBResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/movie/%d/similar?%s", s.baseURL, id, params.Encode()) var response models.TMDBResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetPopularTVShows(page int, language string) (*models.TMDBTVResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/tv/popular?%s", s.baseURL, params.Encode()) var response models.TMDBTVResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetTopRatedTVShows(page int, language string) (*models.TMDBTVResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/tv/top_rated?%s", s.baseURL, params.Encode()) var response models.TMDBTVResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetOnTheAirTVShows(page int, language string) (*models.TMDBTVResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/tv/on_the_air?%s", s.baseURL, params.Encode()) var response models.TMDBTVResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetAiringTodayTVShows(page int, language string) (*models.TMDBTVResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/tv/airing_today?%s", s.baseURL, params.Encode()) var response models.TMDBTVResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetTVRecommendations(id, page int, language string) (*models.TMDBTVResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/tv/%d/recommendations?%s", s.baseURL, id, params.Encode()) var response models.TMDBTVResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetSimilarTVShows(id, page int, language string) (*models.TMDBTVResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/tv/%d/similar?%s", s.baseURL, id, params.Encode()) var response models.TMDBTVResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetMovieExternalIDs(id int) (*models.ExternalIDs, error) { endpoint := fmt.Sprintf("%s/movie/%d/external_ids", s.baseURL, id) var ids models.ExternalIDs err := s.makeRequest(endpoint, &ids) return &ids, err } func (s *TMDBService) GetTVExternalIDs(id int) (*models.ExternalIDs, error) { endpoint := fmt.Sprintf("%s/tv/%d/external_ids", s.baseURL, id) var ids models.ExternalIDs err := s.makeRequest(endpoint, &ids) return &ids, err } func (s *TMDBService) DiscoverMoviesByGenre(genreID, page int, language string) (*models.TMDBResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) params.Set("with_genres", strconv.Itoa(genreID)) params.Set("sort_by", "popularity.desc") if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/discover/movie?%s", s.baseURL, params.Encode()) var response models.TMDBResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) DiscoverTVByGenre(genreID, page int, language string) (*models.TMDBResponse, error) { params := url.Values{} params.Set("page", strconv.Itoa(page)) params.Set("with_genres", strconv.Itoa(genreID)) params.Set("sort_by", "popularity.desc") if language != "" { params.Set("language", language) } else { params.Set("language", "ru-RU") } endpoint := fmt.Sprintf("%s/discover/tv?%s", s.baseURL, params.Encode()) var response models.TMDBResponse err := s.makeRequest(endpoint, &response) return &response, err } func (s *TMDBService) GetTVSeason(tvID, seasonNumber int, language string) (*models.SeasonDetails, error) { if language == "" { language = "ru-RU" } endpoint := fmt.Sprintf("%s/tv/%d/season/%d?language=%s", s.baseURL, tvID, seasonNumber, language) var season models.SeasonDetails err := s.makeRequest(endpoint, &season) return &season, err }