mirror of
https://gitlab.com/foxixus/neomovies-api.git
synced 2025-10-28 01:48:51 +05:00
feat: Update player API to use id_type in path
All Russian players now use format: /players/{player}/{id_type}/{id}
- id_type can be kp (Kinopoisk) or imdb
- Alloha, Lumex, Vibix, HDVB support both ID types
- Added validation for id_type parameter
- Updated handlers to parse id_type from path
This commit is contained in:
@@ -39,7 +39,6 @@ func (h *DocsHandler) GetOpenAPISpec(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *DocsHandler) ServeDocs(w http.ResponseWriter, r *http.Request) {
|
||||
baseURL := determineBaseURL(r)
|
||||
|
||||
// Use absolute SpecURL so the library does not try to read a local file path
|
||||
htmlContent, err := scalar.ApiReferenceHTML(&scalar.Options{
|
||||
SpecURL: fmt.Sprintf("%s/openapi.json", baseURL),
|
||||
CustomOptions: scalar.CustomOptions{
|
||||
@@ -56,6 +55,7 @@ func (h *DocsHandler) ServeDocs(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
fmt.Fprintln(w, htmlContent)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,16 +30,22 @@ 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)
|
||||
idType := vars["id_type"]
|
||||
id := vars["id"]
|
||||
|
||||
imdbID := vars["imdb_id"]
|
||||
if imdbID == "" {
|
||||
log.Printf("Error: imdb_id is empty")
|
||||
http.Error(w, "imdb_id path param is required", http.StatusBadRequest)
|
||||
if idType == "" || id == "" {
|
||||
log.Printf("Error: id_type or id is empty")
|
||||
http.Error(w, "id_type and id are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Processing imdb_id: %s", imdbID)
|
||||
if idType != "kp" && idType != "imdb" {
|
||||
log.Printf("Error: invalid id_type: %s", idType)
|
||||
http.Error(w, "id_type must be 'kp' or 'imdb'", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Processing %s ID: %s", idType, id)
|
||||
|
||||
if h.config.AllohaToken == "" {
|
||||
log.Printf("Error: ALLOHA_TOKEN is missing")
|
||||
@@ -47,7 +53,7 @@ func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
idParam := fmt.Sprintf("imdb=%s", url.QueryEscape(imdbID))
|
||||
idParam := fmt.Sprintf("%s=%s", idType, url.QueryEscape(id))
|
||||
apiURL := fmt.Sprintf("https://api.alloha.tv/?token=%s&%s", h.config.AllohaToken, idParam)
|
||||
log.Printf("Calling Alloha API: %s", apiURL)
|
||||
|
||||
@@ -104,7 +110,7 @@ func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// Используем iframe URL из API
|
||||
iframeCode := allohaResponse.Data.Iframe
|
||||
|
||||
|
||||
// Если это не HTML код, а просто URL
|
||||
var playerURL string
|
||||
if !strings.Contains(iframeCode, "<") {
|
||||
@@ -129,23 +135,29 @@ func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request)
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Write([]byte(htmlDoc))
|
||||
|
||||
log.Printf("Successfully served Alloha player for imdb_id: %s", imdbID)
|
||||
log.Printf("Successfully served Alloha player for %s: %s", idType, id)
|
||||
}
|
||||
|
||||
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)
|
||||
idType := vars["id_type"]
|
||||
id := vars["id"]
|
||||
|
||||
imdbID := vars["imdb_id"]
|
||||
if imdbID == "" {
|
||||
log.Printf("Error: imdb_id is empty")
|
||||
http.Error(w, "imdb_id path param is required", http.StatusBadRequest)
|
||||
if idType == "" || id == "" {
|
||||
log.Printf("Error: id_type or id is empty")
|
||||
http.Error(w, "id_type and id are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Processing imdb_id: %s", imdbID)
|
||||
if idType != "kp" && idType != "imdb" {
|
||||
log.Printf("Error: invalid id_type: %s", idType)
|
||||
http.Error(w, "id_type must be 'kp' or 'imdb'", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Processing %s ID: %s", idType, id)
|
||||
|
||||
if h.config.LumexURL == "" {
|
||||
log.Printf("Error: LUMEX_URL is missing")
|
||||
@@ -153,9 +165,15 @@ func (h *PlayersHandler) GetLumexPlayer(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
// Lumex использует только IMDb ID без season/episode
|
||||
playerURL := fmt.Sprintf("%s?imdb_id=%s", h.config.LumexURL, imdbID)
|
||||
log.Printf("🔗 Lumex URL: %s", playerURL)
|
||||
var paramName string
|
||||
if idType == "kp" {
|
||||
paramName = "kinopoisk_id"
|
||||
} else {
|
||||
paramName = "imdb_id"
|
||||
}
|
||||
|
||||
playerURL := fmt.Sprintf("%s?%s=%s", h.config.LumexURL, paramName, id)
|
||||
log.Printf("Lumex URL: %s", playerURL)
|
||||
|
||||
iframe := fmt.Sprintf(`<iframe src="%s" allowfullscreen loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe>`, playerURL)
|
||||
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)
|
||||
@@ -163,23 +181,29 @@ func (h *PlayersHandler) GetLumexPlayer(w http.ResponseWriter, r *http.Request)
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Write([]byte(htmlDoc))
|
||||
|
||||
log.Printf("Successfully served Lumex player for imdb_id: %s", imdbID)
|
||||
log.Printf("Successfully served Lumex player for %s: %s", idType, id)
|
||||
}
|
||||
|
||||
func (h *PlayersHandler) GetVibixPlayer(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("GetVibixPlayer called: %s %s", r.Method, r.URL.Path)
|
||||
|
||||
vars := mux.Vars(r)
|
||||
log.Printf("Route vars: %+v", vars)
|
||||
idType := vars["id_type"]
|
||||
id := vars["id"]
|
||||
|
||||
imdbID := vars["imdb_id"]
|
||||
if imdbID == "" {
|
||||
log.Printf("Error: imdb_id is empty")
|
||||
http.Error(w, "imdb_id path param is required", http.StatusBadRequest)
|
||||
if idType == "" || id == "" {
|
||||
log.Printf("Error: id_type or id is empty")
|
||||
http.Error(w, "id_type and id are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Processing imdb_id: %s", imdbID)
|
||||
if idType != "kp" && idType != "imdb" {
|
||||
log.Printf("Error: invalid id_type: %s", idType)
|
||||
http.Error(w, "id_type must be 'kp' or 'imdb'", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Processing %s ID: %s", idType, id)
|
||||
|
||||
if h.config.VibixToken == "" {
|
||||
log.Printf("Error: VIBIX_TOKEN is missing")
|
||||
@@ -192,7 +216,14 @@ func (h *PlayersHandler) GetVibixPlayer(w http.ResponseWriter, r *http.Request)
|
||||
vibixHost = "https://vibix.org"
|
||||
}
|
||||
|
||||
apiURL := fmt.Sprintf("%s/api/v1/publisher/videos/imdb/%s", vibixHost, imdbID)
|
||||
var endpoint string
|
||||
if idType == "kp" {
|
||||
endpoint = "kinopoisk"
|
||||
} else {
|
||||
endpoint = "imdb"
|
||||
}
|
||||
|
||||
apiURL := fmt.Sprintf("%s/api/v1/publisher/videos/%s/%s", vibixHost, endpoint, id)
|
||||
log.Printf("Calling Vibix API: %s", apiURL)
|
||||
|
||||
req, err := http.NewRequest("GET", apiURL, nil)
|
||||
@@ -203,7 +234,7 @@ func (h *PlayersHandler) GetVibixPlayer(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+h.config.VibixToken)
|
||||
req.Header.Set("Authorization", h.config.VibixToken)
|
||||
req.Header.Set("X-CSRF-TOKEN", "")
|
||||
|
||||
client := &http.Client{Timeout: 8 * time.Second}
|
||||
@@ -259,7 +290,7 @@ func (h *PlayersHandler) GetVibixPlayer(w http.ResponseWriter, r *http.Request)
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Write([]byte(htmlDoc))
|
||||
|
||||
log.Printf("Successfully served Vibix player for imdb_id: %s", imdbID)
|
||||
log.Printf("Successfully served Vibix player for %s: %s", idType, id)
|
||||
}
|
||||
|
||||
// GetRgShowsPlayer handles RgShows streaming requests
|
||||
@@ -463,16 +494,16 @@ func (h *PlayersHandler) GetStreamAPI(w http.ResponseWriter, r *http.Request) {
|
||||
// GetVidsrcPlayer handles Vidsrc.to player (uses IMDb ID for both movies and TV shows)
|
||||
func (h *PlayersHandler) GetVidsrcPlayer(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("GetVidsrcPlayer called: %s %s", r.Method, r.URL.Path)
|
||||
|
||||
|
||||
vars := mux.Vars(r)
|
||||
imdbId := vars["imdb_id"]
|
||||
mediaType := vars["media_type"] // "movie" or "tv"
|
||||
|
||||
|
||||
if imdbId == "" || mediaType == "" {
|
||||
http.Error(w, "imdb_id and media_type are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
var playerURL string
|
||||
if mediaType == "movie" {
|
||||
playerURL = fmt.Sprintf("https://vidsrc.to/embed/movie/%s", imdbId)
|
||||
@@ -488,72 +519,116 @@ func (h *PlayersHandler) GetVidsrcPlayer(w http.ResponseWriter, r *http.Request)
|
||||
http.Error(w, "Invalid media_type. Use 'movie' or 'tv'", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
log.Printf("Generated Vidsrc URL: %s", playerURL)
|
||||
|
||||
|
||||
// Используем общий шаблон с кастомными контролами
|
||||
htmlDoc := getPlayerWithControlsHTML(playerURL, "Vidsrc Player")
|
||||
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Write([]byte(htmlDoc))
|
||||
|
||||
|
||||
log.Printf("Successfully served Vidsrc player for %s: %s", mediaType, imdbId)
|
||||
}
|
||||
|
||||
// GetVidlinkMoviePlayer handles vidlink.pro player for movies (uses IMDb ID)
|
||||
func (h *PlayersHandler) GetVidlinkMoviePlayer(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("GetVidlinkMoviePlayer called: %s %s", r.Method, r.URL.Path)
|
||||
|
||||
|
||||
vars := mux.Vars(r)
|
||||
imdbId := vars["imdb_id"]
|
||||
|
||||
|
||||
if imdbId == "" {
|
||||
http.Error(w, "imdb_id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
playerURL := fmt.Sprintf("https://vidlink.pro/movie/%s", imdbId)
|
||||
|
||||
|
||||
log.Printf("Generated Vidlink Movie URL: %s", playerURL)
|
||||
|
||||
|
||||
// Используем общий шаблон с кастомными контролами
|
||||
htmlDoc := getPlayerWithControlsHTML(playerURL, "Vidlink Player")
|
||||
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Write([]byte(htmlDoc))
|
||||
|
||||
|
||||
log.Printf("Successfully served Vidlink movie player: %s", imdbId)
|
||||
}
|
||||
|
||||
// GetVidlinkTVPlayer handles vidlink.pro player for TV shows (uses TMDB ID)
|
||||
func (h *PlayersHandler) GetHDVBPlayer(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("GetHDVBPlayer called: %s %s", r.Method, r.URL.Path)
|
||||
|
||||
vars := mux.Vars(r)
|
||||
idType := vars["id_type"]
|
||||
id := vars["id"]
|
||||
|
||||
if idType == "" || id == "" {
|
||||
log.Printf("Error: id_type or id is empty")
|
||||
http.Error(w, "id_type and id are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if idType != "kp" && idType != "imdb" {
|
||||
log.Printf("Error: invalid id_type: %s", idType)
|
||||
http.Error(w, "id_type must be 'kp' or 'imdb'", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Processing %s ID: %s", idType, id)
|
||||
|
||||
if h.config.HDVBToken == "" {
|
||||
log.Printf("Error: HDVB_TOKEN is missing")
|
||||
http.Error(w, "Server misconfiguration: HDVB_TOKEN missing", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var playerURL string
|
||||
if idType == "kp" {
|
||||
playerURL = fmt.Sprintf("https://apivb.com/api/videos.json?id_kp=%s&token=%s", id, h.config.HDVBToken)
|
||||
} else {
|
||||
playerURL = fmt.Sprintf("https://apivb.com/api/videos.json?imdb_id=%s&token=%s", id, h.config.HDVBToken)
|
||||
}
|
||||
log.Printf("Generated HDVB URL: %s", playerURL)
|
||||
|
||||
iframe := fmt.Sprintf(`<iframe src="%s" allowfullscreen loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe>`, playerURL)
|
||||
htmlDoc := fmt.Sprintf(`<!DOCTYPE html><html><head><meta charset='utf-8'/><title>HDVB 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 HDVB player for %s: %s", idType, id)
|
||||
}
|
||||
|
||||
func (h *PlayersHandler) GetVidlinkTVPlayer(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("GetVidlinkTVPlayer called: %s %s", r.Method, r.URL.Path)
|
||||
|
||||
|
||||
vars := mux.Vars(r)
|
||||
tmdbId := vars["tmdb_id"]
|
||||
|
||||
|
||||
if tmdbId == "" {
|
||||
http.Error(w, "tmdb_id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
season := r.URL.Query().Get("season")
|
||||
episode := r.URL.Query().Get("episode")
|
||||
if season == "" || episode == "" {
|
||||
http.Error(w, "season and episode are required for TV shows", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
playerURL := fmt.Sprintf("https://vidlink.pro/tv/%s/%s/%s", tmdbId, season, episode)
|
||||
|
||||
|
||||
log.Printf("Generated Vidlink TV URL: %s", playerURL)
|
||||
|
||||
|
||||
// Используем общий шаблон с кастомными контролами
|
||||
htmlDoc := getPlayerWithControlsHTML(playerURL, "Vidlink Player")
|
||||
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Write([]byte(htmlDoc))
|
||||
|
||||
|
||||
log.Printf("Successfully served Vidlink TV player: %s S%sE%s", tmdbId, season, episode)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,13 @@ import (
|
||||
|
||||
type SearchHandler struct {
|
||||
tmdbService *services.TMDBService
|
||||
kpService *services.KinopoiskService
|
||||
}
|
||||
|
||||
func NewSearchHandler(tmdbService *services.TMDBService) *SearchHandler {
|
||||
func NewSearchHandler(tmdbService *services.TMDBService, kpService *services.KinopoiskService) *SearchHandler {
|
||||
return &SearchHandler{
|
||||
tmdbService: tmdbService,
|
||||
kpService: kpService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +30,42 @@ func (h *SearchHandler) MultiSearch(w http.ResponseWriter, r *http.Request) {
|
||||
page := getIntQuery(r, "page", 1)
|
||||
language := GetLanguage(r)
|
||||
|
||||
if services.ShouldUseKinopoisk(language) && h.kpService != nil {
|
||||
kpSearch, err := h.kpService.SearchFilms(query, page)
|
||||
if err == nil {
|
||||
tmdbResp := services.MapKPSearchToTMDBResponse(kpSearch)
|
||||
multiResults := make([]models.MultiSearchResult, 0)
|
||||
for _, movie := range tmdbResp.Results {
|
||||
multiResults = append(multiResults, models.MultiSearchResult{
|
||||
ID: movie.ID,
|
||||
MediaType: "movie",
|
||||
Title: movie.Title,
|
||||
OriginalTitle: movie.OriginalTitle,
|
||||
Overview: movie.Overview,
|
||||
PosterPath: movie.PosterPath,
|
||||
BackdropPath: movie.BackdropPath,
|
||||
ReleaseDate: movie.ReleaseDate,
|
||||
VoteAverage: movie.VoteAverage,
|
||||
VoteCount: movie.VoteCount,
|
||||
Popularity: movie.Popularity,
|
||||
Adult: movie.Adult,
|
||||
OriginalLanguage: movie.OriginalLanguage,
|
||||
})
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(models.APIResponse{
|
||||
Success: true,
|
||||
Data: models.MultiSearchResponse{
|
||||
Page: page,
|
||||
Results: multiResults,
|
||||
TotalPages: tmdbResp.TotalPages,
|
||||
TotalResults: tmdbResp.TotalResults,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
results, err := h.tmdbService.SearchMulti(query, page, language)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
Reference in New Issue
Block a user