@@ -4,17 +4,18 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"neomovies-api/pkg/config"
"neomovies-api/pkg/players"
"github.com/gorilla/mux"
)
type PlayersHandler struct {
@@ -28,48 +29,39 @@ func NewPlayersHandler(cfg *config.Config) *PlayersHandler {
}
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 )
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 == "kinopoisk_id" { idType = "kp" }
if idType == "kinopoisk_id" {
idType = "kp"
}
if idType != "kp" && idType != "imdb" {
log . Printf ( "Error: invalid id_type: %s" , idType )
http . Error ( w , "id_type must be 'kp' (kinopoisk_id) or 'imdb'" , http . StatusBadRequest )
return
}
log . Printf ( "Processing %s ID: %s" , idType , id )
if h . config . AllohaToken == "" {
log . Printf ( "Error: ALLOHA_TOKEN is missing" )
http . Error ( w , "Server misconfiguration: ALLOHA_TOKEN missing" , http . StatusInternalServerError )
return
}
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 )
client := & http . Client { Timeout : 8 * time . Second }
resp , err := client . 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
@@ -77,13 +69,10 @@ func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request)
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 {
@@ -92,13 +81,11 @@ func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request)
}
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
}
@@ -126,7 +113,7 @@ func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request)
}
playerURL = fmt . Sprintf ( "%s%sseason=%s&episode=%s&translation=%s" , playerURL , separator , season , episode , translation )
}
iframeCode = fmt . Sprintf ( ` <iframe src="%s" allowfullscreen style="border:none;width:100%%;height:100%%"></iframe> ` , playerURL )
iframeCode = fmt . Sprintf ( ` <iframe src="%s" allowfullscreen controlsList="nodownload" style="border:none;width:100%%;height:100%%"></iframe> ` , playerURL )
}
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 )
@@ -137,8 +124,6 @@ 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 %s: %s" , idType , id )
}
// GetAllohaMetaByKP returns seasons/episodes meta for Alloha by kinopoisk_id
@@ -256,30 +241,25 @@ func (h *PlayersHandler) GetAllohaMetaByKP(w http.ResponseWriter, r *http.Reques
}
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 )
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 == "kinopoisk_id" { idType = "kp" }
if idType == "kinopoisk_id" {
idType = "kp"
}
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" )
http . Error ( w , "Server misconfiguration: LUMEX_URL missing" , http . StatusInternalServerError )
return
}
@@ -298,40 +278,30 @@ func (h *PlayersHandler) GetLumexPlayer(w http.ResponseWriter, r *http.Request)
separator = "&"
}
playerURL := fmt . Sprintf ( "%s%s%s=%s" , h . config . LumexURL , separator , paramName , url . QueryEscape ( 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 )
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen controlsList="nodownload" 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 )
w . Header ( ) . Set ( "Content-Type" , "text/html" )
w . Write ( [ ] byte ( htmlDoc ) )
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 )
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 . VibixToken == "" {
log . Printf ( "Error: VIBIX_TOKEN is missing" )
http . Error ( w , "Server misconfiguration: VIBIX_TOKEN missing" , http . StatusInternalServerError )
return
}
@@ -349,11 +319,9 @@ func (h *PlayersHandler) GetVibixPlayer(w http.ResponseWriter, r *http.Request)
}
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 )
if err != nil {
log . Printf ( "Error creating Vibix request: %v" , err )
http . Error ( w , "Failed to create request" , http . StatusInternalServerError )
return
}
@@ -365,202 +333,158 @@ func (h *PlayersHandler) GetVibixPlayer(w http.ResponseWriter, r *http.Request)
client := & http . Client { Timeout : 8 * time . Second }
resp , err := client . Do ( req )
if err != nil {
log . Printf ( "Error calling Vibix API: %v" , err )
http . Error ( w , "Failed to fetch from Vibix API" , http . StatusInternalServerError )
return
}
defer resp . Body . Close ( )
log . Printf ( "Vibix API response status: %d" , resp . StatusCode )
if resp . StatusCode != http . StatusOK {
log . Printf ( "Vibix API error: %d" , resp . StatusCode )
http . Error ( w , fmt . Sprintf ( "Vibix API error: %d" , resp . StatusCode ) , http . StatusBadGateway )
return
}
body , err := io . ReadAll ( resp . Body )
if err != nil {
log . Printf ( "Error reading Vibix response: %v" , err )
http . Error ( w , "Failed to read Vibix response" , http . StatusInternalServerError )
return
}
log . Printf ( "Vibix API response body: %s" , string ( body ) )
var vibixResponse struct {
ID interface { } ` json:"id" `
IframeURL string ` json:"iframe_url" `
}
if err := json . Unmarshal ( body , & vibixResponse ) ; err != nil {
log . Printf ( "Error unmarshaling Vibix JSON: %v" , err )
http . Error ( w , "Invalid JSON from Vibix" , http . StatusBadGateway )
return
}
if vibixResponse . ID == nil || vibixResponse . IframeURL == "" {
log . Printf ( "Video not found or empty iframe_url" )
http . Error ( w , "Video not found" , http . StatusNotFound )
return
}
// Vibix использует только iframe_url без season/episode
playerURL := vibixResponse . IframeURL
log . Printf ( "🔗 Vibix iframe URL: %s" , playerURL )
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe> ` , playerURL )
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen controlsList="nodownload" loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe> ` , playerURL )
htmlDoc := fmt . Sprintf ( ` <!DOCTYPE html><html><head><meta charset='utf-8'/><title>Vibix 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 Vibix player for %s: %s" , idType , id )
}
// GetRgShowsPlayer handles RgShows streaming requests
func ( h * PlayersHandler ) GetRgShowsPlayer ( w http . ResponseWriter , r * http . Request ) {
log . Printf ( "GetRgShowsPlayer called: %s %s" , r . Method , r . URL . Path )
vars := mux . Vars ( r )
tmdbID := vars [ "tmdb_id" ]
if tmdbID == "" {
log . Printf ( "Error: tmdb_id is empty" )
http . Error ( w , "tmdb_id path param is required" , http . StatusBadRequest )
return
}
log . Printf ( "Processing tmdb_id: %s" , tmdbID )
pm := players . NewPlayersManager ( )
result , err := pm . GetMovieStreamByProvider ( "rgshows" , tmdbID )
if err != nil {
log . Printf ( "Error getting RgShows stream: %v" , err )
http . Error ( w , "Failed to get stream" , http . StatusInternalServerError )
return
}
if ! result . Success {
log . Printf ( "RgShows stream not found: %s" , result . Error )
http . Error ( w , "Stream not found" , http . StatusNotFound )
return
}
// Create iframe with the stream URL
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe> ` , result . StreamURL )
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen controlsList="nodownload" loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe> ` , result . StreamURL )
htmlDoc := fmt . Sprintf ( ` <!DOCTYPE html><html><head><meta charset='utf-8'/><title>RgShows 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 RgShows player for tmdb_id: %s" , tmdbID )
}
// GetRgShowsTVPlayer handles RgShows TV show streaming requests
func ( h * PlayersHandler ) GetRgShowsTVPlayer ( w http . ResponseWriter , r * http . Request ) {
log . Printf ( "GetRgShowsTVPlayer called: %s %s" , r . Method , r . URL . Path )
vars := mux . Vars ( r )
tmdbID := vars [ "tmdb_id" ]
seasonStr := vars [ "season" ]
episodeStr := vars [ "episode" ]
if tmdbID == "" || seasonStr == "" || episodeStr == "" {
log . Printf ( "Error: missing required parameters" )
http . Error ( w , "tmdb_id, season, and episode path params are required" , http . StatusBadRequest )
return
}
season , err := strconv . Atoi ( seasonStr )
if err != nil {
log . Printf ( "Error parsing season: %v" , err )
http . Error ( w , "Invalid season number" , http . StatusBadRequest )
return
}
episode , err := strconv . Atoi ( episodeStr )
if err != nil {
log . Printf ( "Error parsing episode: %v" , err )
http . Error ( w , "Invalid episode number" , http . StatusBadRequest )
return
}
log . Printf ( "Processing tmdb_id: %s, season: %d, episode: %d" , tmdbID , season , episode )
pm := players . NewPlayersManager ( )
result , err := pm . GetTVStreamByProvider ( "rgshows" , tmdbID , season , episode )
if err != nil {
log . Printf ( "Error getting RgShows TV stream: %v" , err )
http . Error ( w , "Failed to get stream" , http . StatusInternalServerError )
return
}
if ! result . Success {
log . Printf ( "RgShows TV stream not found: %s" , result . Error )
http . Error ( w , "Stream not found" , http . StatusNotFound )
return
}
// Create iframe with the stream URL
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe> ` , result . StreamURL )
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen controlsList="nodownload" loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe> ` , result . StreamURL )
htmlDoc := fmt . Sprintf ( ` <!DOCTYPE html><html><head><meta charset='utf-8'/><title>RgShows TV 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 RgShows TV player for tmdb_id: %s, S%dE%d" , tmdbID , season , episode )
}
// GetIframeVideoPlayer handles IframeVideo streaming requests
func ( h * PlayersHandler ) GetIframeVideoPlayer ( w http . ResponseWriter , r * http . Request ) {
log . Printf ( "GetIframeVideoPlayer called: %s %s" , r . Method , r . URL . Path )
vars := mux . Vars ( r )
kinopoiskID := vars [ "kinopoisk_id" ]
imdbID := vars [ "imdb_id" ]
if kinopoiskID == "" && imdbID == "" {
log . Printf ( "Error: both kinopoisk_id and imdb_id are empty" )
http . Error ( w , "Either kinopoisk_id or imdb_id path param is required" , http . StatusBadRequest )
return
}
log . Printf ( "Processing kinopoisk_id: %s, imdb_id: %s" , kinopoiskID , imdbID )
pm := players . NewPlayersManager ( )
result , err := pm . GetStreamWithKinopoisk ( kinopoiskID , imdbID )
if err != nil {
log . Printf ( "Error getting IframeVideo stream: %v" , err )
http . Error ( w , "Failed to get stream" , http . StatusInternalServerError )
return
}
if ! result . Success {
log . Printf ( "IframeVideo stream not found: %s" , result . Error )
http . Error ( w , "Stream not found" , http . StatusNotFound )
return
}
// Create iframe with the stream URL
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe> ` , result . StreamURL )
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen controlsList="nodownload" loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe> ` , result . StreamURL )
htmlDoc := fmt . Sprintf ( ` <!DOCTYPE html><html><head><meta charset='utf-8'/><title>IframeVideo 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 IframeVideo player for kinopoisk_id: %s, imdb_id: %s" , kinopoiskID , imdbID )
}
// GetStreamAPI returns stream information as JSON API
func ( h * PlayersHandler ) GetStreamAPI ( w http . ResponseWriter , r * http . Request ) {
log . Printf ( "GetStreamAPI called: %s %s" , r . Method , r . URL . Path )
vars := mux . Vars ( r )
provider := vars [ "provider" ]
tmdbID := vars [ "tmdb_id" ]
if provider == "" || tmdbID == "" {
log . Printf ( "Error: missing required parameters" )
http . Error ( w , "provider and tmdb_id path params are required" , http . StatusBadRequest )
return
}
@@ -571,8 +495,6 @@ func (h *PlayersHandler) GetStreamAPI(w http.ResponseWriter, r *http.Request) {
kinopoiskID := r . URL . Query ( ) . Get ( "kinopoisk_id" )
imdbID := r . URL . Query ( ) . Get ( "imdb_id" )
log . Printf ( "Processing provider: %s, tmdb_id: %s" , provider , tmdbID )
pm := players . NewPlayersManager ( )
var result * players . StreamResult
var err error
@@ -602,7 +524,6 @@ func (h *PlayersHandler) GetStreamAPI(w http.ResponseWriter, r *http.Request) {
}
if err != nil {
log . Printf ( "Error getting stream from %s: %v" , provider , err )
result = & players . StreamResult {
Success : false ,
Provider : provider ,
@@ -612,14 +533,10 @@ func (h *PlayersHandler) GetStreamAPI(w http.ResponseWriter, r *http.Request) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
json . NewEncoder ( w ) . Encode ( result )
log . Printf ( "Successfully served stream API for provider: %s, tmdb_id: %s" , provider , tmdbID )
}
// 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"
@@ -645,21 +562,15 @@ func (h *PlayersHandler) GetVidsrcPlayer(w http.ResponseWriter, r *http.Request)
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" ]
@@ -670,103 +581,15 @@ func (h *PlayersHandler) GetVidlinkMoviePlayer(w http.ResponseWriter, r *http.Re
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 apiURL string
if idType == "kp" {
apiURL = fmt . Sprintf ( "https://apivb.com/api/videos.json?id_kp=%s&token=%s" , id , h . config . HDVBToken )
} else {
apiURL = fmt . Sprintf ( "https://apivb.com/api/videos.json?imdb_id=%s&token=%s" , id , h . config . HDVBToken )
}
log . Printf ( "HDVB API URL: %s" , apiURL )
client := & http . Client { Timeout : 8 * time . Second }
resp , err := client . Get ( apiURL )
if err != nil {
log . Printf ( "Error fetching HDVB data: %v" , err )
http . Error ( w , "Failed to fetch player data" , http . StatusInternalServerError )
return
}
defer resp . Body . Close ( )
body , err := io . ReadAll ( resp . Body )
if err != nil {
log . Printf ( "Error reading HDVB response: %v" , err )
http . Error ( w , "Failed to read player data" , http . StatusInternalServerError )
return
}
var hdvbData [ ] map [ string ] interface { }
if err := json . Unmarshal ( body , & hdvbData ) ; err != nil {
log . Printf ( "Error parsing HDVB JSON: %v, body: %s" , err , string ( body ) )
http . Error ( w , "Failed to parse player data" , http . StatusInternalServerError )
return
}
if len ( hdvbData ) == 0 {
log . Printf ( "No HDVB data found for ID: %s" , id )
http . Error ( w , "No player data found" , http . StatusNotFound )
return
}
iframeURL , ok := hdvbData [ 0 ] [ "iframe_url" ] . ( string )
if ! ok || iframeURL == "" {
log . Printf ( "No iframe_url in HDVB response for ID: %s" , id )
http . Error ( w , "No player URL found" , http . StatusNotFound )
return
}
log . Printf ( "HDVB iframe URL: %s" , iframeURL )
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe> ` , iframeURL )
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" ]
@@ -784,15 +607,76 @@ func (h *PlayersHandler) GetVidlinkTVPlayer(w http.ResponseWriter, r *http.Reque
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 )
// GetHDVBPlayer handles HDVB streaming requests
func ( h * PlayersHandler ) GetHDVBPlayer ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
idType := vars [ "id_type" ]
id := vars [ "id" ]
if idType == "" || id == "" {
http . Error ( w , "id_type and id are required" , http . StatusBadRequest )
return
}
if idType != "kp" && idType != "imdb" {
http . Error ( w , "id_type must be 'kp' or 'imdb'" , http . StatusBadRequest )
return
}
if h . config . HDVBToken == "" {
http . Error ( w , "Server misconfiguration: HDVB_TOKEN missing" , http . StatusInternalServerError )
return
}
var apiURL string
if idType == "kp" {
apiURL = fmt . Sprintf ( "https://apivb.com/api/videos.json?id_kp=%s&token=%s" , id , h . config . HDVBToken )
} else {
apiURL = fmt . Sprintf ( "https://apivb.com/api/videos.json?imdb_id=%s&token=%s" , id , h . config . HDVBToken )
}
client := & http . Client { Timeout : 8 * time . Second }
resp , err := client . Get ( apiURL )
if err != nil {
http . Error ( w , "Failed to fetch player data" , http . StatusInternalServerError )
return
}
defer resp . Body . Close ( )
body , err := io . ReadAll ( resp . Body )
if err != nil {
http . Error ( w , "Failed to read player data" , http . StatusInternalServerError )
return
}
var hdvbData [ ] map [ string ] interface { }
if err := json . Unmarshal ( body , & hdvbData ) ; err != nil {
http . Error ( w , "Failed to parse player data" , http . StatusInternalServerError )
return
}
if len ( hdvbData ) == 0 {
http . Error ( w , "No player data found" , http . StatusNotFound )
return
}
iframeURL , ok := hdvbData [ 0 ] [ "iframe_url" ] . ( string )
if ! ok || iframeURL == "" {
http . Error ( w , "No player URL found" , http . StatusNotFound )
return
}
htmlDoc := getPlayerWithControlsHTML ( iframeURL , "HDVB Player" )
w . Header ( ) . Set ( "Content-Type" , "text/html" )
w . Write ( [ ] byte ( htmlDoc ) )
}
// getPlayerWithControlsHTML возвращает HTML с плеером и overlay для блокировки кликов
@@ -823,13 +707,6 @@ html,body{margin:0;height:100%%;overflow:hidden;background:#000;font-family:Aria
</div>
</div>
<script>
const overlay=document.getElementById('overlay');
// Блокируем клики на iframe (защита от рекламы)
overlay.addEventListener('click',(e)=> { e.preventDefault();e.stopPropagation();});
overlay.addEventListener('mousedown',(e)=> { e.preventDefault();e.stopPropagation();});
// Fullscreen
document.getElementById('btn-fullscreen').addEventListener('click',()=> {
if(!document.fullscreenElement) {
document.getElementById('container').requestFullscreen();
@@ -841,3 +718,190 @@ document.getElementById('btn-fullscreen').addEventListener('click',()=>{
</body>
</html> ` , title , playerURL )
}
// GetCollapsPlayer handles Collaps streaming requests
func ( h * PlayersHandler ) GetCollapsPlayer ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
idType := vars [ "id_type" ]
id := vars [ "id" ]
season := r . URL . Query ( ) . Get ( "season" )
episode := r . URL . Query ( ) . Get ( "episode" )
if idType == "" || id == "" {
http . Error ( w , "id_type and id are required" , http . StatusBadRequest )
return
}
if idType == "kinopoisk_id" {
idType = "kp"
}
if idType != "kp" && idType != "imdb" && idType != "orid" {
http . Error ( w , "id_type must be 'kp', 'imdb', or 'orid'" , http . StatusBadRequest )
return
}
if h . config . CollapsAPIHost == "" || h . config . CollapsToken == "" {
http . Error ( w , "Server misconfiguration: COLLAPS_API_HOST or COLLAPS_TOKEN missing" , http . StatusInternalServerError )
return
}
client := & http . Client { Timeout : 8 * time . Second }
// Используем /list endpoint для получения iframe_url
var listURL string
if idType == "kp" {
listURL = fmt . Sprintf ( "%s/list?token=%s&kinopoisk_id=%s" , h . config . CollapsAPIHost , h . config . CollapsToken , id )
} else if idType == "imdb" {
listURL = fmt . Sprintf ( "%s/list?token=%s&imdb_id=%s" , h . config . CollapsAPIHost , h . config . CollapsToken , id )
} else {
// Для orid используем прямой embed
listURL = fmt . Sprintf ( "%s/embed/movie/%s?token=%s" , h . config . CollapsAPIHost , id , h . config . CollapsToken )
}
resp , err := client . Get ( listURL )
if err != nil {
http . Error ( w , "Failed to fetch from Collaps API" , http . StatusInternalServerError )
return
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
http . Error ( w , fmt . Sprintf ( "Collaps API error: %d" , resp . StatusCode ) , http . StatusBadGateway )
return
}
body , err := io . ReadAll ( resp . Body )
if err != nil {
http . Error ( w , "Failed to read Collaps response" , http . StatusInternalServerError )
return
}
content := string ( body )
// Если это прямой embed (для orid), проверяем на сезоны
if idType == "orid" {
if strings . Contains ( content , "seasons:" ) {
w . Header ( ) . Set ( "Content-Type" , "text/html" )
w . Write ( body )
return
}
// Парсим HLS для фильмов
hlsMatch := regexp . MustCompile ( ` hls:\s*"(https?://[^"]+\.m3u[^"]*) ` ) . FindStringSubmatch ( content )
if len ( hlsMatch ) == 0 {
http . Error ( w , "Video not found" , http . StatusNotFound )
return
}
playerURL := hlsMatch [ 1 ]
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen controlsList="nodownload" loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe> ` , playerURL )
htmlDoc := fmt . Sprintf ( ` <!DOCTYPE html><html><head><meta charset='utf-8'/><title>Collaps 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 ) )
return
}
// Парсим JSON ответ от /list
var listResponse struct {
Results [ ] struct {
ID int ` json:"id" `
Name string ` json:"name" `
Type string ` json:"type" `
IframeURL string ` json:"iframe_url" `
Seasons [ ] struct {
Season int ` json:"season" `
Episodes [ ] struct {
Episode interface { } ` json:"episode" ` // может быть string или int
IframeURL string ` json:"iframe_url" `
} ` json:"episodes" `
} ` json:"seasons" `
} ` json:"results" `
}
if err := json . Unmarshal ( body , & listResponse ) ; err != nil {
http . Error ( w , "Invalid JSON from Collaps" , http . StatusBadGateway )
return
}
if len ( listResponse . Results ) == 0 {
http . Error ( w , "Video not found on Collaps" , http . StatusNotFound )
return
}
result := listResponse . Results [ 0 ]
var iframeURL string
// Helper функция для конвертации episode в число
episodeToInt := func ( ep interface { } ) int {
switch v := ep . ( type ) {
case float64 :
return int ( v )
case string :
num := 0
fmt . Sscanf ( v , "%d" , & num )
return num
default :
return 0
}
}
// Если это сериал и запрошены сезон/эпизод
if result . Type == "series" && season != "" && episode != "" {
seasonNum := 0
episodeNum := 0
fmt . Sscanf ( season , "%d" , & seasonNum )
fmt . Sscanf ( episode , "%d" , & episodeNum )
for _ , s := range result . Seasons {
if s . Season == seasonNum {
for _ , e := range s . Episodes {
if episodeToInt ( e . Episode ) == episodeNum {
iframeURL = e . IframeURL
break
}
}
break
}
}
if iframeURL == "" {
http . Error ( w , "Episode not found" , http . StatusNotFound )
return
}
} else if result . Type == "series" && season != "" {
// Только сезон
seasonNum := 0
fmt . Sscanf ( season , "%d" , & seasonNum )
for _ , s := range result . Seasons {
if s . Season == seasonNum {
iframeURL = s . Episodes [ 0 ] . IframeURL
break
}
}
if iframeURL == "" {
http . Error ( w , "Season not found" , http . StatusNotFound )
return
}
} else {
// Фильм или первый эпизод сериала
iframeURL = result . IframeURL
}
// Возвращаем HTML с iframe напрямую
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen controlsList="nodownload" loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe> ` , iframeURL )
htmlDoc := fmt . Sprintf ( ` <!DOCTYPE html><html><head><meta charset='utf-8'/><title>Collaps 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 ) )
}
func min ( a , b int ) int {
if a < b {
return a
}
return b
}