mirror of
https://gitlab.com/foxixus/neomovies-api.git
synced 2025-12-16 04:26:09 +05:00
v3.0.1
This commit is contained in:
@@ -18,6 +18,12 @@ ALLOHA_TOKEN=your_alloha_token
|
|||||||
REDAPI_BASE_URL=http://redapi.cfhttp.top
|
REDAPI_BASE_URL=http://redapi.cfhttp.top
|
||||||
REDAPI_KEY=your_redapi_key
|
REDAPI_KEY=your_redapi_key
|
||||||
|
|
||||||
|
COLLAPS_API_HOST=https://api.bhcesh.me
|
||||||
|
COLLAPS_TOKEN=your_collaps_token
|
||||||
|
|
||||||
|
VEOVEO_HOST=https://api.veoveo.app
|
||||||
|
VEOVEO_TOKEN=
|
||||||
|
|
||||||
JWT_SECRET=your_jwt_secret_key
|
JWT_SECRET=your_jwt_secret_key
|
||||||
|
|
||||||
GMAIL_USER=your_gmail@gmail.com
|
GMAIL_USER=your_gmail@gmail.com
|
||||||
@@ -28,7 +34,7 @@ GOOGLE_CLIENT_SECRET=your_google_client_secret
|
|||||||
GOOGLE_REDIRECT_URL=http://localhost:3000/api/v1/auth/google/callback
|
GOOGLE_REDIRECT_URL=http://localhost:3000/api/v1/auth/google/callback
|
||||||
|
|
||||||
BASE_URL=http://localhost:3000
|
BASE_URL=http://localhost:3000
|
||||||
FRONTEND_URL=http://localhost:3001
|
FRONTEND_URL=http://localhost:5173
|
||||||
|
|
||||||
PORT=3000
|
PORT=3000
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -34,19 +33,15 @@ func initializeApp() {
|
|||||||
var err error
|
var err error
|
||||||
globalDB, err = database.Connect(globalCfg.MongoURI, globalCfg.MongoDBName)
|
globalDB, err = database.Connect(globalCfg.MongoURI, globalCfg.MongoDBName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to connect to database: %v", err)
|
|
||||||
initError = err
|
initError = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Successfully connected to database")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
initOnce.Do(initializeApp)
|
initOnce.Do(initializeApp)
|
||||||
|
|
||||||
if initError != nil {
|
if initError != nil {
|
||||||
log.Printf("Initialization error: %v", initError)
|
|
||||||
http.Error(w, "Application initialization failed: "+initError.Error(), http.StatusInternalServerError)
|
http.Error(w, "Application initialization failed: "+initError.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -114,6 +109,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
|||||||
api.HandleFunc("/torrents/series", torrentsHandler.SearchSeries).Methods("GET")
|
api.HandleFunc("/torrents/series", torrentsHandler.SearchSeries).Methods("GET")
|
||||||
api.HandleFunc("/torrents/anime", torrentsHandler.SearchAnime).Methods("GET")
|
api.HandleFunc("/torrents/anime", torrentsHandler.SearchAnime).Methods("GET")
|
||||||
api.HandleFunc("/torrents/seasons", torrentsHandler.GetAvailableSeasons).Methods("GET")
|
api.HandleFunc("/torrents/seasons", torrentsHandler.GetAvailableSeasons).Methods("GET")
|
||||||
|
api.HandleFunc("/torrents/by-title", torrentsHandler.SearchByTitle).Methods("GET")
|
||||||
api.HandleFunc("/torrents/search", torrentsHandler.SearchByQuery).Methods("GET")
|
api.HandleFunc("/torrents/search", torrentsHandler.SearchByQuery).Methods("GET")
|
||||||
|
|
||||||
api.HandleFunc("/reactions/{mediaType}/{mediaId}/counts", reactionsHandler.GetReactionCounts).Methods("GET")
|
api.HandleFunc("/reactions/{mediaType}/{mediaId}/counts", reactionsHandler.GetReactionCounts).Methods("GET")
|
||||||
|
|||||||
18
main.go
18
main.go
@@ -2,6 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -18,6 +20,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Инициализация логирования в файл
|
||||||
|
logFile, err := os.OpenFile("neomovies-api.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to open log file: %v\n", err)
|
||||||
|
} else {
|
||||||
|
// Пишем логи одновременно в файл и в консоль
|
||||||
|
multiWriter := io.MultiWriter(os.Stdout, logFile)
|
||||||
|
log.SetOutput(multiWriter)
|
||||||
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
defer logFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
if err := godotenv.Load(); err != nil {
|
if err := godotenv.Load(); err != nil {
|
||||||
_ = err
|
_ = err
|
||||||
}
|
}
|
||||||
@@ -84,13 +98,15 @@ func main() {
|
|||||||
api.HandleFunc("/players/vidlink/movie/{imdb_id}", playersHandler.GetVidlinkMoviePlayer).Methods("GET")
|
api.HandleFunc("/players/vidlink/movie/{imdb_id}", playersHandler.GetVidlinkMoviePlayer).Methods("GET")
|
||||||
api.HandleFunc("/players/vidlink/tv/{tmdb_id}", playersHandler.GetVidlinkTVPlayer).Methods("GET")
|
api.HandleFunc("/players/vidlink/tv/{tmdb_id}", playersHandler.GetVidlinkTVPlayer).Methods("GET")
|
||||||
api.HandleFunc("/players/hdvb/{id_type}/{id}", playersHandler.GetHDVBPlayer).Methods("GET")
|
api.HandleFunc("/players/hdvb/{id_type}/{id}", playersHandler.GetHDVBPlayer).Methods("GET")
|
||||||
|
api.HandleFunc("/players/collaps/{id_type}/{id}", playersHandler.GetCollapsPlayer).Methods("GET")
|
||||||
|
|
||||||
|
api.HandleFunc("/torrents/search/by-title", torrentsHandler.SearchByTitle).Methods("GET")
|
||||||
|
api.HandleFunc("/torrents/search", torrentsHandler.SearchByQuery).Methods("GET")
|
||||||
api.HandleFunc("/torrents/search/{imdbId}", torrentsHandler.SearchTorrents).Methods("GET")
|
api.HandleFunc("/torrents/search/{imdbId}", torrentsHandler.SearchTorrents).Methods("GET")
|
||||||
api.HandleFunc("/torrents/movies", torrentsHandler.SearchMovies).Methods("GET")
|
api.HandleFunc("/torrents/movies", torrentsHandler.SearchMovies).Methods("GET")
|
||||||
api.HandleFunc("/torrents/series", torrentsHandler.SearchSeries).Methods("GET")
|
api.HandleFunc("/torrents/series", torrentsHandler.SearchSeries).Methods("GET")
|
||||||
api.HandleFunc("/torrents/anime", torrentsHandler.SearchAnime).Methods("GET")
|
api.HandleFunc("/torrents/anime", torrentsHandler.SearchAnime).Methods("GET")
|
||||||
api.HandleFunc("/torrents/seasons", torrentsHandler.GetAvailableSeasons).Methods("GET")
|
api.HandleFunc("/torrents/seasons", torrentsHandler.GetAvailableSeasons).Methods("GET")
|
||||||
api.HandleFunc("/torrents/search", torrentsHandler.SearchByQuery).Methods("GET")
|
|
||||||
|
|
||||||
api.HandleFunc("/reactions/{mediaType}/{mediaId}/counts", reactionsHandler.GetReactionCounts).Methods("GET")
|
api.HandleFunc("/reactions/{mediaType}/{mediaId}/counts", reactionsHandler.GetReactionCounts).Methods("GET")
|
||||||
|
|
||||||
|
|||||||
1988
neomovies-api.log
Normal file
1988
neomovies-api.log
Normal file
File diff suppressed because one or more lines are too long
@@ -1,78 +1,82 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"os"
|
||||||
"os"
|
)
|
||||||
)
|
|
||||||
|
type Config struct {
|
||||||
type Config struct {
|
MongoURI string
|
||||||
MongoURI string
|
MongoDBName string
|
||||||
MongoDBName string
|
TMDBAccessToken string
|
||||||
TMDBAccessToken string
|
JWTSecret string
|
||||||
JWTSecret string
|
Port string
|
||||||
Port string
|
BaseURL string
|
||||||
BaseURL string
|
NodeEnv string
|
||||||
NodeEnv string
|
GmailUser string
|
||||||
GmailUser string
|
GmailPassword string
|
||||||
GmailPassword string
|
LumexURL string
|
||||||
LumexURL string
|
AllohaToken string
|
||||||
AllohaToken string
|
RedAPIBaseURL string
|
||||||
RedAPIBaseURL string
|
RedAPIKey string
|
||||||
RedAPIKey string
|
GoogleClientID string
|
||||||
GoogleClientID string
|
GoogleClientSecret string
|
||||||
GoogleClientSecret string
|
GoogleRedirectURL string
|
||||||
GoogleRedirectURL string
|
FrontendURL string
|
||||||
FrontendURL string
|
VibixHost string
|
||||||
VibixHost string
|
VibixToken string
|
||||||
VibixToken string
|
KPAPIKey string
|
||||||
KPAPIKey string
|
HDVBToken string
|
||||||
HDVBToken string
|
KPAPIBaseURL string
|
||||||
KPAPIBaseURL string
|
CollapsAPIHost string
|
||||||
}
|
CollapsToken string
|
||||||
|
VeoVeoHost string
|
||||||
func New() *Config {
|
VeoVeoToken string
|
||||||
mongoURI := getMongoURI()
|
}
|
||||||
|
|
||||||
return &Config{
|
func New() *Config {
|
||||||
MongoURI: mongoURI,
|
mongoURI := getMongoURI()
|
||||||
MongoDBName: getEnv(EnvMongoDBName, DefaultMongoDBName),
|
|
||||||
TMDBAccessToken: getEnv(EnvTMDBAccessToken, ""),
|
return &Config{
|
||||||
JWTSecret: getEnv(EnvJWTSecret, DefaultJWTSecret),
|
MongoURI: mongoURI,
|
||||||
Port: getEnv(EnvPort, DefaultPort),
|
MongoDBName: getEnv(EnvMongoDBName, DefaultMongoDBName),
|
||||||
BaseURL: getEnv(EnvBaseURL, DefaultBaseURL),
|
TMDBAccessToken: getEnv(EnvTMDBAccessToken, ""),
|
||||||
NodeEnv: getEnv(EnvNodeEnv, DefaultNodeEnv),
|
JWTSecret: getEnv(EnvJWTSecret, DefaultJWTSecret),
|
||||||
GmailUser: getEnv(EnvGmailUser, ""),
|
Port: getEnv(EnvPort, DefaultPort),
|
||||||
GmailPassword: getEnv(EnvGmailPassword, ""),
|
BaseURL: getEnv(EnvBaseURL, DefaultBaseURL),
|
||||||
LumexURL: getEnv(EnvLumexURL, ""),
|
NodeEnv: getEnv(EnvNodeEnv, DefaultNodeEnv),
|
||||||
AllohaToken: getEnv(EnvAllohaToken, ""),
|
GmailUser: getEnv(EnvGmailUser, ""),
|
||||||
RedAPIBaseURL: getEnv(EnvRedAPIBaseURL, DefaultRedAPIBase),
|
GmailPassword: getEnv(EnvGmailPassword, ""),
|
||||||
RedAPIKey: getEnv(EnvRedAPIKey, ""),
|
LumexURL: getEnv(EnvLumexURL, ""),
|
||||||
GoogleClientID: getEnv(EnvGoogleClientID, ""),
|
AllohaToken: getEnv(EnvAllohaToken, ""),
|
||||||
GoogleClientSecret: getEnv(EnvGoogleClientSecret, ""),
|
RedAPIBaseURL: getEnv(EnvRedAPIBaseURL, DefaultRedAPIBase),
|
||||||
GoogleRedirectURL: getEnv(EnvGoogleRedirectURL, ""),
|
RedAPIKey: getEnv(EnvRedAPIKey, ""),
|
||||||
FrontendURL: getEnv(EnvFrontendURL, ""),
|
GoogleClientID: getEnv(EnvGoogleClientID, ""),
|
||||||
VibixHost: getEnv(EnvVibixHost, DefaultVibixHost),
|
GoogleClientSecret: getEnv(EnvGoogleClientSecret, ""),
|
||||||
VibixToken: getEnv(EnvVibixToken, ""),
|
GoogleRedirectURL: getEnv(EnvGoogleRedirectURL, ""),
|
||||||
KPAPIKey: getEnv(EnvKPAPIKey, ""),
|
FrontendURL: getEnv(EnvFrontendURL, ""),
|
||||||
HDVBToken: getEnv(EnvHDVBToken, ""),
|
VibixHost: getEnv(EnvVibixHost, DefaultVibixHost),
|
||||||
KPAPIBaseURL: getEnv("KPAPI_BASE_URL", DefaultKPAPIBase),
|
KPAPIKey: getEnv(EnvKPAPIKey, ""),
|
||||||
}
|
HDVBToken: getEnv(EnvHDVBToken, ""),
|
||||||
}
|
KPAPIBaseURL: getEnv("KPAPI_BASE_URL", DefaultKPAPIBase),
|
||||||
|
CollapsAPIHost: getEnv("COLLAPS_API_HOST", ""),
|
||||||
func getMongoURI() string {
|
CollapsToken: getEnv("COLLAPS_TOKEN", ""),
|
||||||
for _, envVar := range []string{"MONGO_URI", "MONGODB_URI", "DATABASE_URL", "MONGO_URL"} {
|
VeoVeoHost: getEnv("VEOVEO_HOST", ""),
|
||||||
if value := os.Getenv(envVar); value != "" {
|
VeoVeoToken: getEnv("VEOVEO_TOKEN", ""),
|
||||||
log.Printf("DEBUG: Using %s for MongoDB connection", envVar)
|
}
|
||||||
return value
|
}
|
||||||
}
|
|
||||||
}
|
func getMongoURI() string {
|
||||||
log.Printf("DEBUG: No MongoDB URI environment variable found")
|
for _, envVar := range []string{"MONGO_URI", "MONGODB_URI", "DATABASE_URL", "MONGO_URL"} {
|
||||||
return ""
|
if value := os.Getenv(envVar); value != "" {
|
||||||
}
|
return value
|
||||||
|
}
|
||||||
func getEnv(key, defaultValue string) string {
|
}
|
||||||
if value := os.Getenv(key); value != "" {
|
return ""
|
||||||
return value
|
}
|
||||||
}
|
|
||||||
return defaultValue
|
func getEnv(key, defaultValue string) string {
|
||||||
}
|
if value := os.Getenv(key); value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,207 +1,207 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"neomovies-api/pkg/models"
|
"neomovies-api/pkg/models"
|
||||||
"neomovies-api/pkg/services"
|
"neomovies-api/pkg/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TorrentsHandler struct {
|
type TorrentsHandler struct {
|
||||||
torrentService *services.TorrentService
|
torrentService *services.TorrentService
|
||||||
tmdbService *services.TMDBService
|
tmdbService *services.TMDBService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTorrentsHandler(torrentService *services.TorrentService, tmdbService *services.TMDBService) *TorrentsHandler {
|
func NewTorrentsHandler(torrentService *services.TorrentService, tmdbService *services.TMDBService) *TorrentsHandler {
|
||||||
return &TorrentsHandler{
|
return &TorrentsHandler{
|
||||||
torrentService: torrentService,
|
torrentService: torrentService,
|
||||||
tmdbService: tmdbService,
|
tmdbService: tmdbService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchTorrents - поиск торрентов по IMDB ID
|
// SearchTorrents - поиск торрентов по IMDB ID
|
||||||
func (h *TorrentsHandler) SearchTorrents(w http.ResponseWriter, r *http.Request) {
|
func (h *TorrentsHandler) SearchTorrents(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
imdbID := vars["imdbId"]
|
imdbID := vars["imdbId"]
|
||||||
|
|
||||||
if imdbID == "" {
|
if imdbID == "" {
|
||||||
http.Error(w, "IMDB ID is required", http.StatusBadRequest)
|
http.Error(w, "IMDB ID is required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Параметры запроса
|
// Параметры запроса
|
||||||
mediaType := r.URL.Query().Get("type")
|
mediaType := r.URL.Query().Get("type")
|
||||||
if mediaType == "" {
|
if mediaType == "" {
|
||||||
mediaType = "movie"
|
mediaType = "movie"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем опции поиска
|
// Создаем опции поиска
|
||||||
options := &models.TorrentSearchOptions{
|
options := &models.TorrentSearchOptions{
|
||||||
ContentType: mediaType,
|
ContentType: mediaType,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Качество
|
// Качество
|
||||||
if quality := r.URL.Query().Get("quality"); quality != "" {
|
if quality := r.URL.Query().Get("quality"); quality != "" {
|
||||||
options.Quality = strings.Split(quality, ",")
|
options.Quality = strings.Split(quality, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Минимальное и максимальное качество
|
// Минимальное и максимальное качество
|
||||||
options.MinQuality = r.URL.Query().Get("minQuality")
|
options.MinQuality = r.URL.Query().Get("minQuality")
|
||||||
options.MaxQuality = r.URL.Query().Get("maxQuality")
|
options.MaxQuality = r.URL.Query().Get("maxQuality")
|
||||||
|
|
||||||
// Исключаемые качества
|
// Исключаемые качества
|
||||||
if excludeQualities := r.URL.Query().Get("excludeQualities"); excludeQualities != "" {
|
if excludeQualities := r.URL.Query().Get("excludeQualities"); excludeQualities != "" {
|
||||||
options.ExcludeQualities = strings.Split(excludeQualities, ",")
|
options.ExcludeQualities = strings.Split(excludeQualities, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// HDR
|
// HDR
|
||||||
if hdr := r.URL.Query().Get("hdr"); hdr != "" {
|
if hdr := r.URL.Query().Get("hdr"); hdr != "" {
|
||||||
if hdrBool, err := strconv.ParseBool(hdr); err == nil {
|
if hdrBool, err := strconv.ParseBool(hdr); err == nil {
|
||||||
options.HDR = &hdrBool
|
options.HDR = &hdrBool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HEVC
|
// HEVC
|
||||||
if hevc := r.URL.Query().Get("hevc"); hevc != "" {
|
if hevc := r.URL.Query().Get("hevc"); hevc != "" {
|
||||||
if hevcBool, err := strconv.ParseBool(hevc); err == nil {
|
if hevcBool, err := strconv.ParseBool(hevc); err == nil {
|
||||||
options.HEVC = &hevcBool
|
options.HEVC = &hevcBool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сортировка
|
// Сортировка
|
||||||
options.SortBy = r.URL.Query().Get("sortBy")
|
options.SortBy = r.URL.Query().Get("sortBy")
|
||||||
if options.SortBy == "" {
|
if options.SortBy == "" {
|
||||||
options.SortBy = "seeders"
|
options.SortBy = "seeders"
|
||||||
}
|
}
|
||||||
|
|
||||||
options.SortOrder = r.URL.Query().Get("sortOrder")
|
options.SortOrder = r.URL.Query().Get("sortOrder")
|
||||||
if options.SortOrder == "" {
|
if options.SortOrder == "" {
|
||||||
options.SortOrder = "desc"
|
options.SortOrder = "desc"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Группировка
|
// Группировка
|
||||||
if groupByQuality := r.URL.Query().Get("groupByQuality"); groupByQuality == "true" {
|
if groupByQuality := r.URL.Query().Get("groupByQuality"); groupByQuality == "true" {
|
||||||
options.GroupByQuality = true
|
options.GroupByQuality = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if groupBySeason := r.URL.Query().Get("groupBySeason"); groupBySeason == "true" {
|
if groupBySeason := r.URL.Query().Get("groupBySeason"); groupBySeason == "true" {
|
||||||
options.GroupBySeason = true
|
options.GroupBySeason = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сезон для сериалов
|
// Сезон для сериалов
|
||||||
if season := r.URL.Query().Get("season"); season != "" {
|
if season := r.URL.Query().Get("season"); season != "" {
|
||||||
if seasonInt, err := strconv.Atoi(season); err == nil {
|
if seasonInt, err := strconv.Atoi(season); err == nil {
|
||||||
options.Season = &seasonInt
|
options.Season = &seasonInt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Поиск торрентов
|
// Поиск торрентов
|
||||||
results, err := h.torrentService.SearchTorrentsByIMDbID(h.tmdbService, imdbID, mediaType, options)
|
results, err := h.torrentService.SearchTorrentsByIMDbID(h.tmdbService, imdbID, mediaType, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Формируем ответ с группировкой если необходимо
|
// Формируем ответ с группировкой если необходимо
|
||||||
response := map[string]interface{}{
|
response := map[string]interface{}{
|
||||||
"imdbId": imdbID,
|
"imdbId": imdbID,
|
||||||
"type": mediaType,
|
"type": mediaType,
|
||||||
"total": results.Total,
|
"total": results.Total,
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.Season != nil {
|
if options.Season != nil {
|
||||||
response["season"] = *options.Season
|
response["season"] = *options.Season
|
||||||
}
|
}
|
||||||
|
|
||||||
// Применяем группировку если запрошена
|
// Применяем группировку если запрошена
|
||||||
if options.GroupByQuality && options.GroupBySeason {
|
if options.GroupByQuality && options.GroupBySeason {
|
||||||
// Группируем сначала по сезонам, затем по качеству внутри каждого сезона
|
// Группируем сначала по сезонам, затем по качеству внутри каждого сезона
|
||||||
seasonGroups := h.torrentService.GroupBySeason(results.Results)
|
seasonGroups := h.torrentService.GroupBySeason(results.Results)
|
||||||
finalGroups := make(map[string]map[string][]models.TorrentResult)
|
finalGroups := make(map[string]map[string][]models.TorrentResult)
|
||||||
|
|
||||||
for season, torrents := range seasonGroups {
|
for season, torrents := range seasonGroups {
|
||||||
qualityGroups := h.torrentService.GroupByQuality(torrents)
|
qualityGroups := h.torrentService.GroupByQuality(torrents)
|
||||||
finalGroups[season] = qualityGroups
|
finalGroups[season] = qualityGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
response["grouped"] = true
|
response["grouped"] = true
|
||||||
response["groups"] = finalGroups
|
response["groups"] = finalGroups
|
||||||
} else if options.GroupByQuality {
|
} else if options.GroupByQuality {
|
||||||
groups := h.torrentService.GroupByQuality(results.Results)
|
groups := h.torrentService.GroupByQuality(results.Results)
|
||||||
response["grouped"] = true
|
response["grouped"] = true
|
||||||
response["groups"] = groups
|
response["groups"] = groups
|
||||||
} else if options.GroupBySeason {
|
} else if options.GroupBySeason {
|
||||||
groups := h.torrentService.GroupBySeason(results.Results)
|
groups := h.torrentService.GroupBySeason(results.Results)
|
||||||
response["grouped"] = true
|
response["grouped"] = true
|
||||||
response["groups"] = groups
|
response["groups"] = groups
|
||||||
} else {
|
} else {
|
||||||
response["grouped"] = false
|
response["grouped"] = false
|
||||||
response["results"] = results.Results
|
response["results"] = results.Results
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(results.Results) == 0 {
|
if len(results.Results) == 0 {
|
||||||
response["error"] = "No torrents found for this IMDB ID"
|
response["error"] = "No torrents found for this IMDB ID"
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(models.APIResponse{
|
json.NewEncoder(w).Encode(models.APIResponse{
|
||||||
Success: true,
|
Success: true,
|
||||||
Data: response,
|
Data: response,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchMovies - поиск фильмов по названию
|
// SearchMovies - поиск фильмов по названию
|
||||||
func (h *TorrentsHandler) SearchMovies(w http.ResponseWriter, r *http.Request) {
|
func (h *TorrentsHandler) SearchMovies(w http.ResponseWriter, r *http.Request) {
|
||||||
title := r.URL.Query().Get("title")
|
title := r.URL.Query().Get("title")
|
||||||
originalTitle := r.URL.Query().Get("originalTitle")
|
originalTitle := r.URL.Query().Get("originalTitle")
|
||||||
year := r.URL.Query().Get("year")
|
year := r.URL.Query().Get("year")
|
||||||
|
|
||||||
if title == "" && originalTitle == "" {
|
if title == "" && originalTitle == "" {
|
||||||
http.Error(w, "Title or original title is required", http.StatusBadRequest)
|
http.Error(w, "Title or original title is required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
results, err := h.torrentService.SearchMovies(title, originalTitle, year)
|
results, err := h.torrentService.SearchMovies(title, originalTitle, year)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := map[string]interface{}{
|
response := map[string]interface{}{
|
||||||
"title": title,
|
"title": title,
|
||||||
"originalTitle": originalTitle,
|
"originalTitle": originalTitle,
|
||||||
"year": year,
|
"year": year,
|
||||||
"type": "movie",
|
"type": "movie",
|
||||||
"total": results.Total,
|
"total": results.Total,
|
||||||
"results": results.Results,
|
"results": results.Results,
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(models.APIResponse{
|
json.NewEncoder(w).Encode(models.APIResponse{
|
||||||
Success: true,
|
Success: true,
|
||||||
Data: response,
|
Data: response,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchSeries - поиск сериалов по названию с поддержкой сезонов
|
// SearchSeries - поиск сериалов по названию с поддержкой сезонов
|
||||||
func (h *TorrentsHandler) SearchSeries(w http.ResponseWriter, r *http.Request) {
|
func (h *TorrentsHandler) SearchSeries(w http.ResponseWriter, r *http.Request) {
|
||||||
title := r.URL.Query().Get("title")
|
title := r.URL.Query().Get("title")
|
||||||
originalTitle := r.URL.Query().Get("originalTitle")
|
originalTitle := r.URL.Query().Get("originalTitle")
|
||||||
year := r.URL.Query().Get("year")
|
year := r.URL.Query().Get("year")
|
||||||
|
|
||||||
if title == "" && originalTitle == "" {
|
if title == "" && originalTitle == "" {
|
||||||
http.Error(w, "Title or original title is required", http.StatusBadRequest)
|
http.Error(w, "Title or original title is required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var season *int
|
var season *int
|
||||||
if seasonStr := r.URL.Query().Get("season"); seasonStr != "" {
|
if seasonStr := r.URL.Query().Get("season"); seasonStr != "" {
|
||||||
@@ -365,3 +365,57 @@ func (h *TorrentsHandler) SearchByQuery(w http.ResponseWriter, r *http.Request)
|
|||||||
Data: response,
|
Data: response,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchByTitle - поиск торрентов по названию фильма/сериала (новый метод)
|
||||||
|
func (h *TorrentsHandler) SearchByTitle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
title := r.URL.Query().Get("title")
|
||||||
|
originalTitle := r.URL.Query().Get("originalTitle")
|
||||||
|
yearStr := r.URL.Query().Get("year")
|
||||||
|
contentType := r.URL.Query().Get("type")
|
||||||
|
|
||||||
|
if title == "" && originalTitle == "" {
|
||||||
|
http.Error(w, "Title or originalTitle is required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = "movie"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Парсим год
|
||||||
|
year := 0
|
||||||
|
if yearStr != "" {
|
||||||
|
if y, err := strconv.Atoi(yearStr); err == nil {
|
||||||
|
year = y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Определяем является ли это сериалом
|
||||||
|
isSerial := contentType == "series" || contentType == "tv"
|
||||||
|
|
||||||
|
// Поиск торрентов
|
||||||
|
results, err := h.torrentService.SearchTorrentsByTitle(title, originalTitle, year, isSerial)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяем фильтрацию по типу контента
|
||||||
|
results.Results = h.torrentService.FilterByContentType(results.Results, contentType)
|
||||||
|
results.Total = len(results.Results)
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"title": title,
|
||||||
|
"originalTitle": originalTitle,
|
||||||
|
"year": year,
|
||||||
|
"type": contentType,
|
||||||
|
"total": results.Total,
|
||||||
|
"results": results.Results,
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(models.APIResponse{
|
||||||
|
Success: true,
|
||||||
|
Data: response,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ type TorrentSearchResponse struct {
|
|||||||
// RedAPI специфичные структуры
|
// RedAPI специфичные структуры
|
||||||
type RedAPIResponse struct {
|
type RedAPIResponse struct {
|
||||||
Results []RedAPITorrent `json:"Results"`
|
Results []RedAPITorrent `json:"Results"`
|
||||||
|
Jacred bool `json:"jacred,omitempty"` // Флаг для определения источника
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedAPITorrent struct {
|
type RedAPITorrent struct {
|
||||||
@@ -277,15 +278,22 @@ type RedAPITorrent struct {
|
|||||||
MagnetUri string `json:"MagnetUri"`
|
MagnetUri string `json:"MagnetUri"`
|
||||||
PublishDate string `json:"PublishDate"`
|
PublishDate string `json:"PublishDate"`
|
||||||
CategoryDesc string `json:"CategoryDesc"`
|
CategoryDesc string `json:"CategoryDesc"`
|
||||||
|
Category []int `json:"Category,omitempty"` // Для jacred
|
||||||
Details string `json:"Details"`
|
Details string `json:"Details"`
|
||||||
Info *RedAPITorrentInfo `json:"Info,omitempty"`
|
Info *RedAPITorrentInfo `json:"Info,omitempty"`
|
||||||
|
Languages []string `json:"languages,omitempty"` // Для jacred
|
||||||
|
FFprobe []interface{} `json:"ffprobe,omitempty"` // Для jacred
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedAPITorrentInfo struct {
|
type RedAPITorrentInfo struct {
|
||||||
Quality interface{} `json:"quality,omitempty"` // Может быть string или number
|
Quality interface{} `json:"quality,omitempty"` // Может быть string или number
|
||||||
Voices []string `json:"voices,omitempty"`
|
VideoType string `json:"videotype,omitempty"`
|
||||||
Types []string `json:"types,omitempty"`
|
Voices []string `json:"voices,omitempty"`
|
||||||
Seasons []int `json:"seasons,omitempty"`
|
Types []string `json:"types,omitempty"`
|
||||||
|
Seasons []int `json:"seasons,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
OriginalName string `json:"originalname,omitempty"`
|
||||||
|
Released int `json:"relased,omitempty"` // Примечание: опечатка в API
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alloha API структуры для получения информации о фильмах
|
// Alloha API структуры для получения информации о фильмах
|
||||||
|
|||||||
@@ -82,11 +82,70 @@ func (s *TorrentService) SearchTorrents(params map[string]string) (*models.Torre
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchTorrentsByTitle - поиск торрентов по названию фильма/сериала
|
||||||
|
func (s *TorrentService) SearchTorrentsByTitle(title, originalTitle string, year int, isSerial bool) (*models.TorrentSearchResponse, error) {
|
||||||
|
searchParams := url.Values{}
|
||||||
|
|
||||||
|
// Добавляем основное название
|
||||||
|
if title != "" {
|
||||||
|
searchParams.Add("title", title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем оригинальное название
|
||||||
|
if originalTitle != "" {
|
||||||
|
searchParams.Add("title_original", originalTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем год
|
||||||
|
if year > 0 {
|
||||||
|
searchParams.Add("year", strconv.Itoa(year))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Указываем тип контента
|
||||||
|
if isSerial {
|
||||||
|
searchParams.Add("is_serial", "2")
|
||||||
|
} else {
|
||||||
|
searchParams.Add("is_serial", "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.apiKey != "" {
|
||||||
|
searchParams.Add("apikey", s.apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
searchURL := fmt.Sprintf("%s/api/v2.0/indexers/all/results?%s", s.baseURL, searchParams.Encode())
|
||||||
|
|
||||||
|
resp, err := s.client.Get(searchURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to search torrents by title: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var redAPIResponse models.RedAPIResponse
|
||||||
|
if err := json.Unmarshal(body, &redAPIResponse); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
results := s.parseRedAPIResults(redAPIResponse)
|
||||||
|
|
||||||
|
return &models.TorrentSearchResponse{
|
||||||
|
Query: title,
|
||||||
|
Results: results,
|
||||||
|
Total: len(results),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseRedAPIResults преобразует результаты RedAPI в наш формат
|
// parseRedAPIResults преобразует результаты RedAPI в наш формат
|
||||||
func (s *TorrentService) parseRedAPIResults(data models.RedAPIResponse) []models.TorrentResult {
|
func (s *TorrentService) parseRedAPIResults(data models.RedAPIResponse) []models.TorrentResult {
|
||||||
var results []models.TorrentResult
|
var results []models.TorrentResult
|
||||||
|
|
||||||
for _, torrent := range data.Results {
|
for i, torrent := range data.Results {
|
||||||
|
_ = i // для избежания неиспользуемой переменной
|
||||||
|
|
||||||
var sizeStr string
|
var sizeStr string
|
||||||
switch v := torrent.Size.(type) {
|
switch v := torrent.Size.(type) {
|
||||||
case string:
|
case string:
|
||||||
@@ -112,14 +171,27 @@ func (s *TorrentService) parseRedAPIResults(data models.RedAPIResponse) []models
|
|||||||
Source: "RedAPI",
|
Source: "RedAPI",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обработка Info для обоих форматов (RedAPI и jacred)
|
||||||
if torrent.Info != nil {
|
if torrent.Info != nil {
|
||||||
switch v := torrent.Info.Quality.(type) {
|
// Обработка качества
|
||||||
case string:
|
if torrent.Info.Quality != nil {
|
||||||
result.Quality = v
|
switch v := torrent.Info.Quality.(type) {
|
||||||
case float64:
|
case string:
|
||||||
result.Quality = fmt.Sprintf("%.0fp", v)
|
result.Quality = v
|
||||||
case int:
|
case float64:
|
||||||
result.Quality = fmt.Sprintf("%dp", v)
|
// Для jacred качество приходит как число (1080, 720 и т.д.)
|
||||||
|
if v >= 2160 {
|
||||||
|
result.Quality = "4K"
|
||||||
|
} else {
|
||||||
|
result.Quality = fmt.Sprintf("%.0fp", v)
|
||||||
|
}
|
||||||
|
case int:
|
||||||
|
if v >= 2160 {
|
||||||
|
result.Quality = "4K"
|
||||||
|
} else {
|
||||||
|
result.Quality = fmt.Sprintf("%dp", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Voice = torrent.Info.Voices
|
result.Voice = torrent.Info.Voices
|
||||||
@@ -127,10 +199,10 @@ func (s *TorrentService) parseRedAPIResults(data models.RedAPIResponse) []models
|
|||||||
result.Seasons = torrent.Info.Seasons
|
result.Seasons = torrent.Info.Seasons
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Если качество все еще не определено, извлекаем из названия
|
||||||
if result.Quality == "" {
|
if result.Quality == "" {
|
||||||
result.Quality = s.ExtractQuality(result.Title)
|
result.Quality = s.ExtractQuality(result.Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, result)
|
results = append(results, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user