Add player: Vibix

This commit is contained in:
2025-08-11 18:36:02 +00:00
parent 3cfa75cc00
commit eeb96a9efb
9 changed files with 137 additions and 129 deletions

View File

@@ -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

View File

@@ -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} # Поиск торрентов

View File

@@ -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")

View File

@@ -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")

View File

@@ -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, ""),
}
}

View File

@@ -18,6 +18,8 @@ const (
EnvGoogleClientSecret= "GOOGLE_CLIENT_SECRET"
EnvGoogleRedirectURL = "GOOGLE_REDIRECT_URL"
EnvFrontendURL = "FRONTEND_URL"
EnvVibixHost = "VIBIX_HOST"
EnvVibixToken = "VIBIX_TOKEN"
// Default values
DefaultJWTSecret = "your-secret-key"
@@ -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"

View File

@@ -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{}{

View File

@@ -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
}
if h.cfg.AllohaToken == "" {
http.Error(w, "ALLOHA_TOKEN is not configured", http.StatusServiceUnavailable)
return
}
log.Printf("Processing 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)
}
if h.config.AllohaToken == "" {
log.Printf("Error: ALLOHA_TOKEN is missing")
http.Error(w, "Server misconfiguration: ALLOHA_TOKEN missing", http.StatusInternalServerError)
func (h *PlayersHandler) GetLumexPlayer(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.LumexURL == "" {
http.Error(w, "LUMEX_URL 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)
// Примерная ссылка встраивания. При необходимости скорректируйте под фактический эндпоинт Lumex
iframeSrc := fmt.Sprintf("%s/embed/%s", h.cfg.LumexURL, imdbID)
renderIframePage(w, "Lumex Player", iframeSrc)
}
resp, err := http.Get(apiURL)
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 {
log.Printf("Error calling Alloha API: %v", err)
http.Error(w, "Failed to fetch from Alloha API", http.StatusInternalServerError)
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()
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)
http.Error(w, "movie not found on Vibix", http.StatusNotFound)
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)
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
}
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(`<iframe src="%s" allowfullscreen style="border:none;width:100%%;height:100%%"></iframe>`, iframeCode)
}
htmlDoc := fmt.Sprintf(`<!DOCTYPE html><html><head><meta charset='utf-8'/><title>Alloha Player</title><style>html,body{margin:0;height:100%%;}</style></head><body>%s</body></html>`, 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)
renderIframePage(w, "Vibix Player", data.IframeURL)
}
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)
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)
return
}
url := fmt.Sprintf("%s?imdb_id=%s", h.config.LumexURL, url.QueryEscape(imdbID))
log.Printf("Generated Lumex URL: %s", url)
iframe := fmt.Sprintf(`<iframe src="%s" allowfullscreen loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe>`, url)
htmlDoc := fmt.Sprintf(`<!DOCTYPE html><html><head><meta charset='utf-8'/><title>Lumex Player</title><style>html,body{margin:0;height:100%%;}</style></head><body>%s</body></html>`, iframe)
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(htmlDoc))
log.Printf("Successfully served Lumex player for imdb_id: %s", imdbID)
func renderIframePage(w http.ResponseWriter, title, src string) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>%s</title>
<style>
html,body{height:100%%;margin:0;background:#000;}
iframe{width:100%%;height:100vh;border:0;}
</style>
</head>
<body>
<iframe src="%s" allowfullscreen></iframe>
</body>
</html>`, title, src)
}

View File

@@ -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
}