mirror of
				https://gitlab.com/foxixus/neomovies-api.git
				synced 2025-10-29 02:18:50 +05:00 
			
		
		
		
	
		
			
				
	
	
		
			367 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			367 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package handlers
 | ||
| 
 | ||
| import (
 | ||
| 	"encoding/json"
 | ||
| 	"net/http"
 | ||
| 	"strconv"
 | ||
| 	"strings"
 | ||
| 
 | ||
| 	"github.com/gorilla/mux"
 | ||
| 
 | ||
| 	"neomovies-api/pkg/models"
 | ||
| 	"neomovies-api/pkg/services"
 | ||
| )
 | ||
| 
 | ||
| type TorrentsHandler struct {
 | ||
| 	torrentService *services.TorrentService
 | ||
| 	tmdbService    *services.TMDBService
 | ||
| }
 | ||
| 
 | ||
| func NewTorrentsHandler(torrentService *services.TorrentService, tmdbService *services.TMDBService) *TorrentsHandler {
 | ||
| 	return &TorrentsHandler{
 | ||
| 		torrentService: torrentService,
 | ||
| 		tmdbService:    tmdbService,
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // SearchTorrents - поиск торрентов по IMDB ID
 | ||
| func (h *TorrentsHandler) SearchTorrents(w http.ResponseWriter, r *http.Request) {
 | ||
| 	vars := mux.Vars(r)
 | ||
| 	imdbID := vars["imdbId"]
 | ||
| 
 | ||
| 	if imdbID == "" {
 | ||
| 		http.Error(w, "IMDB ID is required", http.StatusBadRequest)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	// Параметры запроса
 | ||
| 	mediaType := r.URL.Query().Get("type")
 | ||
| 	if mediaType == "" {
 | ||
| 		mediaType = "movie"
 | ||
| 	}
 | ||
| 
 | ||
| 	// Создаем опции поиска
 | ||
| 	options := &models.TorrentSearchOptions{
 | ||
| 		ContentType: mediaType,
 | ||
| 	}
 | ||
| 
 | ||
| 	// Качество
 | ||
| 	if quality := r.URL.Query().Get("quality"); quality != "" {
 | ||
| 		options.Quality = strings.Split(quality, ",")
 | ||
| 	}
 | ||
| 
 | ||
| 	// Минимальное и максимальное качество
 | ||
| 	options.MinQuality = r.URL.Query().Get("minQuality")
 | ||
| 	options.MaxQuality = r.URL.Query().Get("maxQuality")
 | ||
| 
 | ||
| 	// Исключаемые качества
 | ||
| 	if excludeQualities := r.URL.Query().Get("excludeQualities"); excludeQualities != "" {
 | ||
| 		options.ExcludeQualities = strings.Split(excludeQualities, ",")
 | ||
| 	}
 | ||
| 
 | ||
| 	// HDR
 | ||
| 	if hdr := r.URL.Query().Get("hdr"); hdr != "" {
 | ||
| 		if hdrBool, err := strconv.ParseBool(hdr); err == nil {
 | ||
| 			options.HDR = &hdrBool
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// HEVC
 | ||
| 	if hevc := r.URL.Query().Get("hevc"); hevc != "" {
 | ||
| 		if hevcBool, err := strconv.ParseBool(hevc); err == nil {
 | ||
| 			options.HEVC = &hevcBool
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// Сортировка
 | ||
| 	options.SortBy = r.URL.Query().Get("sortBy")
 | ||
| 	if options.SortBy == "" {
 | ||
| 		options.SortBy = "seeders"
 | ||
| 	}
 | ||
| 
 | ||
| 	options.SortOrder = r.URL.Query().Get("sortOrder")
 | ||
| 	if options.SortOrder == "" {
 | ||
| 		options.SortOrder = "desc"
 | ||
| 	}
 | ||
| 
 | ||
| 	// Группировка
 | ||
| 	if groupByQuality := r.URL.Query().Get("groupByQuality"); groupByQuality == "true" {
 | ||
| 		options.GroupByQuality = true
 | ||
| 	}
 | ||
| 
 | ||
| 	if groupBySeason := r.URL.Query().Get("groupBySeason"); groupBySeason == "true" {
 | ||
| 		options.GroupBySeason = true
 | ||
| 	}
 | ||
| 
 | ||
| 	// Сезон для сериалов
 | ||
| 	if season := r.URL.Query().Get("season"); season != "" {
 | ||
| 		if seasonInt, err := strconv.Atoi(season); err == nil {
 | ||
| 			options.Season = &seasonInt
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// Поиск торрентов
 | ||
| 	results, err := h.torrentService.SearchTorrentsByIMDbID(h.tmdbService, imdbID, mediaType, options)
 | ||
| 	if err != nil {
 | ||
| 		http.Error(w, err.Error(), http.StatusInternalServerError)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	// Формируем ответ с группировкой если необходимо
 | ||
| 	response := map[string]interface{}{
 | ||
| 		"imdbId": imdbID,
 | ||
| 		"type":   mediaType,
 | ||
| 		"total":  results.Total,
 | ||
| 	}
 | ||
| 
 | ||
| 	if options.Season != nil {
 | ||
| 		response["season"] = *options.Season
 | ||
| 	}
 | ||
| 
 | ||
| 	// Применяем группировку если запрошена
 | ||
| 	if options.GroupByQuality && options.GroupBySeason {
 | ||
| 		// Группируем сначала по сезонам, затем по качеству внутри каждого сезона
 | ||
| 		seasonGroups := h.torrentService.GroupBySeason(results.Results)
 | ||
| 		finalGroups := make(map[string]map[string][]models.TorrentResult)
 | ||
| 		
 | ||
| 		for season, torrents := range seasonGroups {
 | ||
| 			qualityGroups := h.torrentService.GroupByQuality(torrents)
 | ||
| 			finalGroups[season] = qualityGroups
 | ||
| 		}
 | ||
| 		
 | ||
| 		response["grouped"] = true
 | ||
| 		response["groups"] = finalGroups
 | ||
| 	} else if options.GroupByQuality {
 | ||
| 		groups := h.torrentService.GroupByQuality(results.Results)
 | ||
| 		response["grouped"] = true
 | ||
| 		response["groups"] = groups
 | ||
| 	} else if options.GroupBySeason {
 | ||
| 		groups := h.torrentService.GroupBySeason(results.Results)
 | ||
| 		response["grouped"] = true
 | ||
| 		response["groups"] = groups
 | ||
| 	} else {
 | ||
| 		response["grouped"] = false
 | ||
| 		response["results"] = results.Results
 | ||
| 	}
 | ||
| 
 | ||
| 	if len(results.Results) == 0 {
 | ||
| 		response["error"] = "No torrents found for this IMDB ID"
 | ||
| 		w.Header().Set("Content-Type", "application/json")
 | ||
| 		w.WriteHeader(http.StatusNotFound)
 | ||
| 		json.NewEncoder(w).Encode(response)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	w.Header().Set("Content-Type", "application/json")
 | ||
| 	json.NewEncoder(w).Encode(models.APIResponse{
 | ||
| 		Success: true,
 | ||
| 		Data:    response,
 | ||
| 	})
 | ||
| }
 | ||
| 
 | ||
| // SearchMovies - поиск фильмов по названию
 | ||
| func (h *TorrentsHandler) SearchMovies(w http.ResponseWriter, r *http.Request) {
 | ||
| 	title := r.URL.Query().Get("title")
 | ||
| 	originalTitle := r.URL.Query().Get("originalTitle")
 | ||
| 	year := r.URL.Query().Get("year")
 | ||
| 
 | ||
| 	if title == "" && originalTitle == "" {
 | ||
| 		http.Error(w, "Title or original title is required", http.StatusBadRequest)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	results, err := h.torrentService.SearchMovies(title, originalTitle, year)
 | ||
| 	if err != nil {
 | ||
| 		http.Error(w, err.Error(), http.StatusInternalServerError)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	response := map[string]interface{}{
 | ||
| 		"title":         title,
 | ||
| 		"originalTitle": originalTitle,
 | ||
| 		"year":          year,
 | ||
| 		"type":          "movie",
 | ||
| 		"total":         results.Total,
 | ||
| 		"results":       results.Results,
 | ||
| 	}
 | ||
| 
 | ||
| 	w.Header().Set("Content-Type", "application/json")
 | ||
| 	json.NewEncoder(w).Encode(models.APIResponse{
 | ||
| 		Success: true,
 | ||
| 		Data:    response,
 | ||
| 	})
 | ||
| }
 | ||
| 
 | ||
| // SearchSeries - поиск сериалов по названию с поддержкой сезонов
 | ||
| func (h *TorrentsHandler) SearchSeries(w http.ResponseWriter, r *http.Request) {
 | ||
| 	title := r.URL.Query().Get("title")
 | ||
| 	originalTitle := r.URL.Query().Get("originalTitle")
 | ||
| 	year := r.URL.Query().Get("year")
 | ||
| 
 | ||
| 	if title == "" && originalTitle == "" {
 | ||
| 		http.Error(w, "Title or original title is required", http.StatusBadRequest)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	var season *int
 | ||
| 	if seasonStr := r.URL.Query().Get("season"); seasonStr != "" {
 | ||
| 		if seasonInt, err := strconv.Atoi(seasonStr); err == nil {
 | ||
| 			season = &seasonInt
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	results, err := h.torrentService.SearchSeries(title, originalTitle, year, season)
 | ||
| 	if err != nil {
 | ||
| 		http.Error(w, err.Error(), http.StatusInternalServerError)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	response := map[string]interface{}{
 | ||
| 		"title":         title,
 | ||
| 		"originalTitle": originalTitle,
 | ||
| 		"year":          year,
 | ||
| 		"type":          "series",
 | ||
| 		"total":         results.Total,
 | ||
| 		"results":       results.Results,
 | ||
| 	}
 | ||
| 
 | ||
| 	if season != nil {
 | ||
| 		response["season"] = *season
 | ||
| 	}
 | ||
| 
 | ||
| 	w.Header().Set("Content-Type", "application/json")
 | ||
| 	json.NewEncoder(w).Encode(models.APIResponse{
 | ||
| 		Success: true,
 | ||
| 		Data:    response,
 | ||
| 	})
 | ||
| }
 | ||
| 
 | ||
| // SearchAnime - поиск аниме по названию
 | ||
| func (h *TorrentsHandler) SearchAnime(w http.ResponseWriter, r *http.Request) {
 | ||
| 	title := r.URL.Query().Get("title")
 | ||
| 	originalTitle := r.URL.Query().Get("originalTitle")
 | ||
| 	year := r.URL.Query().Get("year")
 | ||
| 
 | ||
| 	if title == "" && originalTitle == "" {
 | ||
| 		http.Error(w, "Title or original title is required", http.StatusBadRequest)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	results, err := h.torrentService.SearchAnime(title, originalTitle, year)
 | ||
| 	if err != nil {
 | ||
| 		http.Error(w, err.Error(), http.StatusInternalServerError)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	response := map[string]interface{}{
 | ||
| 		"title":         title,
 | ||
| 		"originalTitle": originalTitle,
 | ||
| 		"year":          year,
 | ||
| 		"type":          "anime",
 | ||
| 		"total":         results.Total,
 | ||
| 		"results":       results.Results,
 | ||
| 	}
 | ||
| 
 | ||
| 	w.Header().Set("Content-Type", "application/json")
 | ||
| 	json.NewEncoder(w).Encode(models.APIResponse{
 | ||
| 		Success: true,
 | ||
| 		Data:    response,
 | ||
| 	})
 | ||
| }
 | ||
| 
 | ||
| // GetAvailableSeasons - получение доступных сезонов для сериала
 | ||
| func (h *TorrentsHandler) GetAvailableSeasons(w http.ResponseWriter, r *http.Request) {
 | ||
| 	title := r.URL.Query().Get("title")
 | ||
| 	originalTitle := r.URL.Query().Get("originalTitle")
 | ||
| 	year := r.URL.Query().Get("year")
 | ||
| 
 | ||
| 	if title == "" && originalTitle == "" {
 | ||
| 		http.Error(w, "Title or original title is required", http.StatusBadRequest)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	seasons, err := h.torrentService.GetAvailableSeasons(title, originalTitle, year)
 | ||
| 	if err != nil {
 | ||
| 		http.Error(w, err.Error(), http.StatusInternalServerError)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	response := map[string]interface{}{
 | ||
| 		"title":         title,
 | ||
| 		"originalTitle": originalTitle,
 | ||
| 		"year":          year,
 | ||
| 		"seasons":       seasons,
 | ||
| 		"total":         len(seasons),
 | ||
| 	}
 | ||
| 
 | ||
| 	w.Header().Set("Content-Type", "application/json")
 | ||
| 	json.NewEncoder(w).Encode(models.APIResponse{
 | ||
| 		Success: true,
 | ||
| 		Data:    response,
 | ||
| 	})
 | ||
| }
 | ||
| 
 | ||
| // SearchByQuery - универсальный поиск торрентов
 | ||
| func (h *TorrentsHandler) SearchByQuery(w http.ResponseWriter, r *http.Request) {
 | ||
| 	query := r.URL.Query().Get("query")
 | ||
| 	if query == "" {
 | ||
| 		http.Error(w, "Query is required", http.StatusBadRequest)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	contentType := r.URL.Query().Get("type")
 | ||
| 	if contentType == "" {
 | ||
| 		contentType = "movie"
 | ||
| 	}
 | ||
| 
 | ||
| 	year := r.URL.Query().Get("year")
 | ||
| 
 | ||
| 	// Формируем параметры поиска
 | ||
| 	params := map[string]string{
 | ||
| 		"query": query,
 | ||
| 	}
 | ||
| 
 | ||
| 	if year != "" {
 | ||
| 		params["year"] = year
 | ||
| 	}
 | ||
| 
 | ||
| 	// Устанавливаем тип контента и категорию
 | ||
| 	switch contentType {
 | ||
| 	case "movie":
 | ||
| 		params["is_serial"] = "1"
 | ||
| 		params["category"] = "2000"
 | ||
| 	case "series", "tv":
 | ||
| 		params["is_serial"] = "2"
 | ||
| 		params["category"] = "5000"
 | ||
| 	case "anime":
 | ||
| 		params["is_serial"] = "5"
 | ||
| 		params["category"] = "5070"
 | ||
| 	}
 | ||
| 
 | ||
| 	results, err := h.torrentService.SearchTorrents(params)
 | ||
| 	if err != nil {
 | ||
| 		http.Error(w, err.Error(), http.StatusInternalServerError)
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	// Применяем фильтрацию по типу контента
 | ||
| 	options := &models.TorrentSearchOptions{
 | ||
| 		ContentType: contentType,
 | ||
| 	}
 | ||
| 	results.Results = h.torrentService.FilterByContentType(results.Results, options.ContentType)
 | ||
| 	results.Total = len(results.Results)
 | ||
| 
 | ||
| 	response := map[string]interface{}{
 | ||
| 		"query":   query,
 | ||
| 		"type":    contentType,
 | ||
| 		"year":    year,
 | ||
| 		"total":   results.Total,
 | ||
| 		"results": results.Results,
 | ||
| 	}
 | ||
| 
 | ||
| 	w.Header().Set("Content-Type", "application/json")
 | ||
| 	json.NewEncoder(w).Encode(models.APIResponse{
 | ||
| 		Success: true,
 | ||
| 		Data:    response,
 | ||
| 	})
 | ||
| } |