diff --git a/.env.example b/.env.example index 70ee0e3..d97cc69 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,7 @@ GMAIL_APP_PASSWORD=your_gmail_app_password # Players LUMEX_URL= ALLOHA_TOKEN=your_alloha_token +VIBIX_TOKEN # Torrents (RedAPI) REDAPI_BASE_URL=http://redapi.cfhttp.top diff --git a/README.md b/README.md index 7193ce3..b3eeb1f 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ GMAIL_APP_PASSWORD=your_gmail_app_password # Плееры LUMEX_URL= ALLOHA_TOKEN=your_alloha_token +VIBIX_TOKEN=your_vibix_token # Торренты (RedAPI) REDAPI_BASE_URL=http://redapi.cfhttp.top @@ -122,6 +123,7 @@ GET /api/v1/tv/{id}/similar # Похожие # Плееры GET /api/v1/players/alloha/{imdb_id} # Alloha плеер по IMDb ID GET /api/v1/players/lumex/{imdb_id} # Lumex плеер по IMDb ID +GET /api/v1/players/vibix/{imdb_id} # Vibix плеер по IMDb ID # Торренты GET /api/v1/torrents/search/{imdbId} # Поиск торрентов diff --git a/api/index.go b/api/index.go index 219136d..fa1b4e8 100644 --- a/api/index.go +++ b/api/index.go @@ -91,6 +91,7 @@ func Handler(w http.ResponseWriter, r *http.Request) { api.HandleFunc("/players/alloha/{imdb_id}", playersHandler.GetAllohaPlayer).Methods("GET") api.HandleFunc("/players/lumex/{imdb_id}", playersHandler.GetLumexPlayer).Methods("GET") + api.HandleFunc("/players/vibix/{imdb_id}", playersHandler.GetVibixPlayer).Methods("GET") api.HandleFunc("/torrents/search/{imdbId}", torrentsHandler.SearchTorrents).Methods("GET") api.HandleFunc("/torrents/movies", torrentsHandler.SearchMovies).Methods("GET") diff --git a/main.go b/main.go index 8426f38..a87bc61 100644 --- a/main.go +++ b/main.go @@ -71,6 +71,7 @@ func main() { api.HandleFunc("/players/alloha/{imdb_id}", playersHandler.GetAllohaPlayer).Methods("GET") api.HandleFunc("/players/lumex/{imdb_id}", playersHandler.GetLumexPlayer).Methods("GET") + api.HandleFunc("/players/vibix/{imdb_id}", playersHandler.GetVibixPlayer).Methods("GET") api.HandleFunc("/torrents/search/{imdbId}", torrentsHandler.SearchTorrents).Methods("GET") api.HandleFunc("/torrents/movies", torrentsHandler.SearchMovies).Methods("GET") diff --git a/pkg/config/config.go b/pkg/config/config.go index eda0414..c7a9376 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -23,6 +23,8 @@ type Config struct { GoogleClientSecret string GoogleRedirectURL string FrontendURL string + VibixHost string + VibixToken string } func New() *Config { @@ -46,6 +48,8 @@ func New() *Config { GoogleClientSecret: getEnv(EnvGoogleClientSecret, ""), GoogleRedirectURL: getEnv(EnvGoogleRedirectURL, ""), FrontendURL: getEnv(EnvFrontendURL, ""), + VibixHost: getEnv(EnvVibixHost, DefaultVibixHost), + VibixToken: getEnv(EnvVibixToken, ""), } } diff --git a/pkg/config/vars.go b/pkg/config/vars.go index 4541e9c..56ac949 100644 --- a/pkg/config/vars.go +++ b/pkg/config/vars.go @@ -18,7 +18,9 @@ const ( EnvGoogleClientSecret= "GOOGLE_CLIENT_SECRET" EnvGoogleRedirectURL = "GOOGLE_REDIRECT_URL" EnvFrontendURL = "FRONTEND_URL" - + EnvVibixHost = "VIBIX_HOST" + EnvVibixToken = "VIBIX_TOKEN" + // Default values DefaultJWTSecret = "your-secret-key" DefaultPort = "3000" @@ -26,6 +28,7 @@ const ( DefaultNodeEnv = "development" DefaultRedAPIBase = "http://redapi.cfhttp.top" DefaultMongoDBName = "database" + DefaultVibixHost = "https://vibix.org" // Static constants TMDBImageBaseURL = "https://image.tmdb.org/t/p" diff --git a/pkg/handlers/docs.go b/pkg/handlers/docs.go index 6d6531b..bb7a75f 100644 --- a/pkg/handlers/docs.go +++ b/pkg/handlers/docs.go @@ -251,6 +251,32 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, }, }, + "/api/v1/players/vibix/{imdb_id}": map[string]interface{}{ + "get": map[string]interface{}{ + "summary": "Vibix плеер по IMDb ID", + "description": "Возвращает HTML-страницу с iframe Vibix для указанного IMDb ID", + "tags": []string{"Players"}, + "parameters": []map[string]interface{}{ + { + "name": "imdb_id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "string"}, + "description": "IMDb ID, например tt0133093", + }, + }, + "responses": map[string]interface{}{ + "200": map[string]interface{}{ + "description": "HTML со встроенным Vibix плеером", + "content": map[string]interface{}{ + "text/html": map[string]interface{}{}, + }, + }, + "404": map[string]interface{}{"description": "Фильм не найден"}, + "503": map[string]interface{}{"description": "VIBIX_TOKEN не настроен"}, + }, + }, + }, "/api/v1/torrents/search/{imdbId}": map[string]interface{}{ "get": map[string]interface{}{ "summary": "Поиск торрентов", @@ -553,8 +579,7 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, }, }, - // --- Добавленный блок для DELETE-запроса --- - "delete": map[string]interface{}{ + "delete": map[string]interface{}{ "summary": "Удалить аккаунт пользователя", "description": "Полное и безвозвратное удаление аккаунта пользователя и всех связанных с ним данных (избранное, реакции)", "tags": []string{"Authentication"}, @@ -584,7 +609,6 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, }, }, - // ------------------------------------------ }, "/api/v1/movies/search": map[string]interface{}{ "get": map[string]interface{}{ diff --git a/pkg/handlers/players.go b/pkg/handlers/players.go index 48a0a59..5fea0f0 100644 --- a/pkg/handlers/players.go +++ b/pkg/handlers/players.go @@ -3,140 +3,125 @@ package handlers import ( "encoding/json" "fmt" - "io" - "log" "net/http" - "net/url" - "strings" + "time" + + "github.com/gorilla/mux" "neomovies-api/pkg/config" - "github.com/gorilla/mux" ) type PlayersHandler struct { - config *config.Config + cfg *config.Config } func NewPlayersHandler(cfg *config.Config) *PlayersHandler { - return &PlayersHandler{ - config: cfg, - } + return &PlayersHandler{cfg: cfg} } func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request) { - log.Printf("GetAllohaPlayer called: %s %s", r.Method, r.URL.Path) - vars := mux.Vars(r) - log.Printf("Route vars: %+v", vars) - imdbID := vars["imdb_id"] if imdbID == "" { - log.Printf("Error: imdb_id is empty") - http.Error(w, "imdb_id path param is required", http.StatusBadRequest) + http.Error(w, "imdb_id is required", http.StatusBadRequest) return } - - log.Printf("Processing imdb_id: %s", imdbID) - - if h.config.AllohaToken == "" { - log.Printf("Error: ALLOHA_TOKEN is missing") - http.Error(w, "Server misconfiguration: ALLOHA_TOKEN missing", http.StatusInternalServerError) + if h.cfg.AllohaToken == "" { + http.Error(w, "ALLOHA_TOKEN is not configured", http.StatusServiceUnavailable) return } - - idParam := fmt.Sprintf("imdb=%s", url.QueryEscape(imdbID)) - apiURL := fmt.Sprintf("https://api.alloha.tv/?token=%s&%s", h.config.AllohaToken, idParam) - log.Printf("Calling Alloha API: %s", apiURL) - - resp, err := http.Get(apiURL) - if err != nil { - log.Printf("Error calling Alloha API: %v", err) - http.Error(w, "Failed to fetch from Alloha API", http.StatusInternalServerError) - return - } - defer resp.Body.Close() - - log.Printf("Alloha API response status: %d", resp.StatusCode) - - if resp.StatusCode != http.StatusOK { - http.Error(w, fmt.Sprintf("Alloha API error: %d", resp.StatusCode), http.StatusBadGateway) - return - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Printf("Error reading Alloha response: %v", err) - http.Error(w, "Failed to read Alloha response", http.StatusInternalServerError) - return - } - - log.Printf("Alloha API response body: %s", string(body)) - - var allohaResponse struct { - Status string `json:"status"` - Data struct { - Iframe string `json:"iframe"` - } `json:"data"` - } - - if err := json.Unmarshal(body, &allohaResponse); err != nil { - log.Printf("Error unmarshaling JSON: %v", err) - http.Error(w, "Invalid JSON from Alloha", http.StatusBadGateway) - return - } - - if allohaResponse.Status != "success" || allohaResponse.Data.Iframe == "" { - log.Printf("Video not found or empty iframe") - http.Error(w, "Video not found", http.StatusNotFound) - return - } - - iframeCode := allohaResponse.Data.Iframe - if !strings.Contains(iframeCode, "<") { - iframeCode = fmt.Sprintf(``, iframeCode) - } - - htmlDoc := fmt.Sprintf(`Alloha Player%s`, iframeCode) - - // Авто-исправление экранированных кавычек - htmlDoc = strings.ReplaceAll(htmlDoc, `\"`, `"`) - htmlDoc = strings.ReplaceAll(htmlDoc, `\'`, `'`) - - w.Header().Set("Content-Type", "text/html") - w.Write([]byte(htmlDoc)) - - log.Printf("Successfully served Alloha player for imdb_id: %s", imdbID) + + // Примерная ссылка встраивания. При необходимости скорректируйте под фактический эндпоинт Alloha + iframeSrc := fmt.Sprintf("https://alloha.tv/embed?imdb_id=%s&token=%s", imdbID, h.cfg.AllohaToken) + renderIframePage(w, "Alloha Player", iframeSrc) } func (h *PlayersHandler) GetLumexPlayer(w http.ResponseWriter, r *http.Request) { - log.Printf("GetLumexPlayer called: %s %s", r.Method, r.URL.Path) - vars := mux.Vars(r) - log.Printf("Route vars: %+v", vars) - imdbID := vars["imdb_id"] if imdbID == "" { - log.Printf("Error: imdb_id is empty") - http.Error(w, "imdb_id path param is required", http.StatusBadRequest) + http.Error(w, "imdb_id is required", http.StatusBadRequest) return } - - log.Printf("Processing imdb_id: %s", imdbID) - - if h.config.LumexURL == "" { - log.Printf("Error: LUMEX_URL is missing") - http.Error(w, "Server misconfiguration: LUMEX_URL missing", http.StatusInternalServerError) + if h.cfg.LumexURL == "" { + http.Error(w, "LUMEX_URL is not configured", http.StatusServiceUnavailable) return } - - url := fmt.Sprintf("%s?imdb_id=%s", h.config.LumexURL, url.QueryEscape(imdbID)) - log.Printf("Generated Lumex URL: %s", url) - - iframe := fmt.Sprintf(``, url) - htmlDoc := fmt.Sprintf(`Lumex Player%s`, iframe) - - w.Header().Set("Content-Type", "text/html") - w.Write([]byte(htmlDoc)) - - log.Printf("Successfully served Lumex player for imdb_id: %s", imdbID) + + // Примерная ссылка встраивания. При необходимости скорректируйте под фактический эндпоинт Lumex + iframeSrc := fmt.Sprintf("%s/embed/%s", h.cfg.LumexURL, imdbID) + renderIframePage(w, "Lumex Player", iframeSrc) +} + +func (h *PlayersHandler) GetVibixPlayer(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + imdbID := vars["imdb_id"] + if imdbID == "" { + http.Error(w, "imdb_id is required", http.StatusBadRequest) + return + } + + if h.cfg.VibixToken == "" { + http.Error(w, "VIBIX_TOKEN is not configured", http.StatusServiceUnavailable) + return + } + vibixHost := h.cfg.VibixHost + if vibixHost == "" { + vibixHost = "https://vibix.org" + } + + req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/publisher/videos/imdb/%s", vibixHost, imdbID), nil) + if err != nil { + http.Error(w, "failed to create request", http.StatusInternalServerError) + return + } + req.Header.Set("Accept", "application/json") + req.Header.Set("Authorization", "Bearer "+h.cfg.VibixToken) + req.Header.Set("X-CSRF-TOKEN", "") + + client := &http.Client{Timeout: 8 * time.Second} + resp, err := client.Do(req) + if err != nil { + http.Error(w, "failed to fetch player", http.StatusBadGateway) + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + http.Error(w, "movie not found on Vibix", http.StatusNotFound) + return + } + + var data struct { + ID interface{} `json:"id"` + IframeURL string `json:"iframe_url"` + } + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + http.Error(w, "failed to parse Vibix response", http.StatusBadGateway) + return + } + if data.ID == nil || data.IframeURL == "" { + http.Error(w, "movie not found on Vibix", http.StatusNotFound) + return + } + + renderIframePage(w, "Vibix Player", data.IframeURL) +} + +func renderIframePage(w http.ResponseWriter, title, src string) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + fmt.Fprintf(w, ` + + + +%s + + + + + +`, title, src) } \ No newline at end of file diff --git a/pkg/services/movie.go b/pkg/services/movie.go index 036be02..e41624b 100644 --- a/pkg/services/movie.go +++ b/pkg/services/movie.go @@ -5,7 +5,6 @@ import ( "strconv" "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "neomovies-api/pkg/models" @@ -58,32 +57,24 @@ func (s *MovieService) GetSimilar(id, page int, language string) (*models.TMDBRe func (s *MovieService) AddToFavorites(userID string, movieID string) error { collection := s.db.Collection("users") - objID, err := primitive.ObjectIDFromHex(userID) - if err != nil { - return err - } - filter := bson.M{"_id": objID} + filter := bson.M{"_id": userID} update := bson.M{ "$addToSet": bson.M{"favorites": movieID}, } - _, err = collection.UpdateOne(context.Background(), filter, update) + _, err := collection.UpdateOne(context.Background(), filter, update) return err } func (s *MovieService) RemoveFromFavorites(userID string, movieID string) error { collection := s.db.Collection("users") - objID, err := primitive.ObjectIDFromHex(userID) - if err != nil { - return err - } - filter := bson.M{"_id": objID} + filter := bson.M{"_id": userID} update := bson.M{ "$pull": bson.M{"favorites": movieID}, } - _, err = collection.UpdateOne(context.Background(), filter, update) + _, err := collection.UpdateOne(context.Background(), filter, update) return err } @@ -91,11 +82,7 @@ func (s *MovieService) GetFavorites(userID string, language string) ([]models.Mo collection := s.db.Collection("users") var user models.User - objID, err := primitive.ObjectIDFromHex(userID) - if err != nil { - return nil, err - } - err = collection.FindOne(context.Background(), bson.M{"_id": objID}).Decode(&user) + err := collection.FindOne(context.Background(), bson.M{"_id": userID}).Decode(&user) if err != nil { return nil, err }