2025-08-07 13:47:42 +00:00
package handlers
import (
"encoding/json"
"fmt"
2025-08-13 18:02:03 +00:00
"io"
"log"
2025-08-07 13:47:42 +00:00
"net/http"
2025-08-13 18:02:03 +00:00
"net/url"
2025-09-28 16:11:09 +00:00
"strconv"
2025-08-13 18:02:03 +00:00
"strings"
2025-08-11 18:36:02 +00:00
"time"
2025-08-07 13:47:42 +00:00
2025-08-13 18:02:03 +00:00
"github.com/gorilla/mux"
2025-09-28 11:46:20 +00:00
"neomovies-api/pkg/config"
2025-09-28 16:11:09 +00:00
"neomovies-api/pkg/players"
2025-08-07 13:47:42 +00:00
)
type PlayersHandler struct {
2025-08-13 18:02:03 +00:00
config * config . Config
2025-08-07 13:47:42 +00:00
}
func NewPlayersHandler ( cfg * config . Config ) * PlayersHandler {
2025-08-13 18:02:03 +00:00
return & PlayersHandler {
config : cfg ,
}
2025-08-07 13:47:42 +00:00
}
func ( h * PlayersHandler ) GetAllohaPlayer ( w http . ResponseWriter , r * http . Request ) {
2025-08-13 18:02:03 +00:00
log . Printf ( "GetAllohaPlayer called: %s %s" , r . Method , r . URL . Path )
2025-09-28 11:46:20 +00:00
2025-08-07 13:47:42 +00:00
vars := mux . Vars ( r )
2025-08-13 18:02:03 +00:00
log . Printf ( "Route vars: %+v" , vars )
2025-09-28 11:46:20 +00:00
2025-08-07 13:47:42 +00:00
imdbID := vars [ "imdb_id" ]
if imdbID == "" {
2025-08-13 18:02:03 +00:00
log . Printf ( "Error: imdb_id is empty" )
http . Error ( w , "imdb_id path param is required" , http . StatusBadRequest )
2025-08-07 13:47:42 +00:00
return
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
log . Printf ( "Processing imdb_id: %s" , imdbID )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
if h . config . AllohaToken == "" {
log . Printf ( "Error: ALLOHA_TOKEN is missing" )
http . Error ( w , "Server misconfiguration: ALLOHA_TOKEN missing" , http . StatusInternalServerError )
2025-08-07 13:47:42 +00:00
return
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
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 )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
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 ( )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
log . Printf ( "Alloha API response status: %d" , resp . StatusCode )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
if resp . StatusCode != http . StatusOK {
http . Error ( w , fmt . Sprintf ( "Alloha API error: %d" , resp . StatusCode ) , http . StatusBadGateway )
return
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
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
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
log . Printf ( "Alloha API response body: %s" , string ( body ) )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
var allohaResponse struct {
Status string ` json:"status" `
2025-09-28 11:46:20 +00:00
Data struct {
2025-08-13 18:02:03 +00:00
Iframe string ` json:"iframe" `
} ` json:"data" `
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
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
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
if allohaResponse . Status != "success" || allohaResponse . Data . Iframe == "" {
log . Printf ( "Video not found or empty iframe" )
http . Error ( w , "Video not found" , http . StatusNotFound )
return
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
iframeCode := allohaResponse . Data . Iframe
if ! strings . Contains ( iframeCode , "<" ) {
iframeCode = fmt . Sprintf ( ` <iframe src="%s" allowfullscreen style="border:none;width:100%%;height:100%%"></iframe> ` , iframeCode )
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
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 )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
// Авто-исправление экранированных кавычек
htmlDoc = strings . ReplaceAll ( htmlDoc , ` \" ` , ` " ` )
htmlDoc = strings . ReplaceAll ( htmlDoc , ` \' ` , ` ' ` )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
w . Header ( ) . Set ( "Content-Type" , "text/html" )
w . Write ( [ ] byte ( htmlDoc ) )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
log . Printf ( "Successfully served Alloha player for imdb_id: %s" , imdbID )
2025-08-11 18:36:02 +00:00
}
func ( h * PlayersHandler ) GetLumexPlayer ( w http . ResponseWriter , r * http . Request ) {
2025-08-13 18:02:03 +00:00
log . Printf ( "GetLumexPlayer called: %s %s" , r . Method , r . URL . Path )
2025-09-28 11:46:20 +00:00
2025-08-11 18:36:02 +00:00
vars := mux . Vars ( r )
2025-08-13 18:02:03 +00:00
log . Printf ( "Route vars: %+v" , vars )
2025-09-28 11:46:20 +00:00
2025-08-11 18:36:02 +00:00
imdbID := vars [ "imdb_id" ]
if imdbID == "" {
2025-08-13 18:02:03 +00:00
log . Printf ( "Error: imdb_id is empty" )
http . Error ( w , "imdb_id path param is required" , http . StatusBadRequest )
2025-08-07 13:47:42 +00:00
return
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
log . Printf ( "Processing imdb_id: %s" , imdbID )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
if h . config . LumexURL == "" {
log . Printf ( "Error: LUMEX_URL is missing" )
http . Error ( w , "Server misconfiguration: LUMEX_URL missing" , http . StatusInternalServerError )
2025-08-07 13:47:42 +00:00
return
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
url := fmt . Sprintf ( "%s?imdb_id=%s" , h . config . LumexURL , url . QueryEscape ( imdbID ) )
log . Printf ( "Generated Lumex URL: %s" , url )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
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 )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
w . Header ( ) . Set ( "Content-Type" , "text/html" )
w . Write ( [ ] byte ( htmlDoc ) )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
log . Printf ( "Successfully served Lumex player for imdb_id: %s" , imdbID )
2025-08-11 18:36:02 +00:00
}
func ( h * PlayersHandler ) GetVibixPlayer ( w http . ResponseWriter , r * http . Request ) {
2025-08-13 18:02:03 +00:00
log . Printf ( "GetVibixPlayer called: %s %s" , r . Method , r . URL . Path )
2025-09-28 11:46:20 +00:00
2025-08-11 18:36:02 +00:00
vars := mux . Vars ( r )
2025-08-13 18:02:03 +00:00
log . Printf ( "Route vars: %+v" , vars )
2025-09-28 11:46:20 +00:00
2025-08-11 18:36:02 +00:00
imdbID := vars [ "imdb_id" ]
if imdbID == "" {
2025-08-13 18:02:03 +00:00
log . Printf ( "Error: imdb_id is empty" )
http . Error ( w , "imdb_id path param is required" , http . StatusBadRequest )
2025-08-07 13:47:42 +00:00
return
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
log . Printf ( "Processing imdb_id: %s" , imdbID )
2025-08-11 18:36:02 +00:00
2025-08-13 18:02:03 +00:00
if h . config . VibixToken == "" {
log . Printf ( "Error: VIBIX_TOKEN is missing" )
http . Error ( w , "Server misconfiguration: VIBIX_TOKEN missing" , http . StatusInternalServerError )
2025-08-11 18:36:02 +00:00
return
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
vibixHost := h . config . VibixHost
2025-08-11 18:36:02 +00:00
if vibixHost == "" {
vibixHost = "https://vibix.org"
2025-08-07 13:47:42 +00:00
}
2025-08-11 18:36:02 +00:00
2025-08-13 18:02:03 +00:00
apiURL := fmt . Sprintf ( "%s/api/v1/publisher/videos/imdb/%s" , vibixHost , imdbID )
log . Printf ( "Calling Vibix API: %s" , apiURL )
req , err := http . NewRequest ( "GET" , apiURL , nil )
2025-08-11 18:36:02 +00:00
if err != nil {
2025-08-13 18:02:03 +00:00
log . Printf ( "Error creating Vibix request: %v" , err )
http . Error ( w , "Failed to create request" , http . StatusInternalServerError )
2025-08-07 13:47:42 +00:00
return
}
2025-09-28 11:46:20 +00:00
2025-08-11 18:36:02 +00:00
req . Header . Set ( "Accept" , "application/json" )
2025-08-13 18:02:03 +00:00
req . Header . Set ( "Authorization" , "Bearer " + h . config . VibixToken )
2025-08-11 18:36:02 +00:00
req . Header . Set ( "X-CSRF-TOKEN" , "" )
client := & http . Client { Timeout : 8 * time . Second }
resp , err := client . Do ( req )
if err != nil {
2025-08-13 18:02:03 +00:00
log . Printf ( "Error calling Vibix API: %v" , err )
http . Error ( w , "Failed to fetch from Vibix API" , http . StatusInternalServerError )
2025-08-07 13:47:42 +00:00
return
}
2025-08-11 18:36:02 +00:00
defer resp . Body . Close ( )
2025-08-13 18:02:03 +00:00
log . Printf ( "Vibix API response status: %d" , resp . StatusCode )
2025-08-11 18:36:02 +00:00
if resp . StatusCode != http . StatusOK {
2025-08-13 18:02:03 +00:00
log . Printf ( "Vibix API error: %d" , resp . StatusCode )
http . Error ( w , fmt . Sprintf ( "Vibix API error: %d" , resp . StatusCode ) , http . StatusBadGateway )
2025-08-11 18:36:02 +00:00
return
2025-08-07 13:47:42 +00:00
}
2025-08-13 18:02:03 +00:00
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
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
log . Printf ( "Vibix API response body: %s" , string ( body ) )
var vibixResponse struct {
2025-08-11 18:36:02 +00:00
ID interface { } ` json:"id" `
IframeURL string ` json:"iframe_url" `
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
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 )
2025-08-07 13:47:42 +00:00
return
}
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
if vibixResponse . ID == nil || vibixResponse . IframeURL == "" {
log . Printf ( "Video not found or empty iframe_url" )
http . Error ( w , "Video not found" , http . StatusNotFound )
2025-08-07 13:47:42 +00:00
return
}
2025-08-11 18:36:02 +00:00
2025-08-13 18:02:03 +00:00
log . Printf ( "Generated Vibix iframe URL: %s" , vibixResponse . IframeURL )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
iframe := fmt . Sprintf ( ` <iframe src="%s" allowfullscreen loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe> ` , vibixResponse . IframeURL )
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 )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
w . Header ( ) . Set ( "Content-Type" , "text/html" )
w . Write ( [ ] byte ( htmlDoc ) )
2025-09-28 11:46:20 +00:00
2025-08-13 18:02:03 +00:00
log . Printf ( "Successfully served Vibix player for imdb_id: %s" , imdbID )
2025-09-28 11:46:20 +00:00
}
2025-09-28 16:11:09 +00:00
// 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 )
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 )
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 )
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
}
// Check for TV show parameters
seasonStr := r . URL . Query ( ) . Get ( "season" )
episodeStr := r . URL . Query ( ) . Get ( "episode" )
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
switch provider {
case "iframevideo" :
if kinopoiskID == "" && imdbID == "" {
http . Error ( w , "kinopoisk_id or imdb_id query param is required for IframeVideo" , http . StatusBadRequest )
return
}
result , err = pm . GetStreamWithKinopoisk ( kinopoiskID , imdbID )
case "rgshows" :
if seasonStr != "" && episodeStr != "" {
season , err1 := strconv . Atoi ( seasonStr )
episode , err2 := strconv . Atoi ( episodeStr )
if err1 != nil || err2 != nil {
http . Error ( w , "Invalid season or episode number" , http . StatusBadRequest )
return
}
result , err = pm . GetTVStreamByProvider ( "rgshows" , tmdbID , season , episode )
} else {
result , err = pm . GetMovieStreamByProvider ( "rgshows" , tmdbID )
}
default :
http . Error ( w , "Unsupported provider" , http . StatusBadRequest )
return
}
if err != nil {
log . Printf ( "Error getting stream from %s: %v" , provider , err )
result = & players . StreamResult {
Success : false ,
Provider : provider ,
Error : err . Error ( ) ,
}
}
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 )
}