mirror of
https://gitlab.com/foxixus/neomovies-api.git
synced 2025-12-16 20:46:10 +05:00
v3.0.1
This commit is contained in:
@@ -18,6 +18,12 @@ ALLOHA_TOKEN=your_alloha_token
|
|||||||
REDAPI_BASE_URL=http://redapi.cfhttp.top
|
REDAPI_BASE_URL=http://redapi.cfhttp.top
|
||||||
REDAPI_KEY=your_redapi_key
|
REDAPI_KEY=your_redapi_key
|
||||||
|
|
||||||
|
COLLAPS_API_HOST=https://api.bhcesh.me
|
||||||
|
COLLAPS_TOKEN=your_collaps_token
|
||||||
|
|
||||||
|
VEOVEO_HOST=https://api.veoveo.app
|
||||||
|
VEOVEO_TOKEN=
|
||||||
|
|
||||||
JWT_SECRET=your_jwt_secret_key
|
JWT_SECRET=your_jwt_secret_key
|
||||||
|
|
||||||
GMAIL_USER=your_gmail@gmail.com
|
GMAIL_USER=your_gmail@gmail.com
|
||||||
@@ -28,7 +34,7 @@ GOOGLE_CLIENT_SECRET=your_google_client_secret
|
|||||||
GOOGLE_REDIRECT_URL=http://localhost:3000/api/v1/auth/google/callback
|
GOOGLE_REDIRECT_URL=http://localhost:3000/api/v1/auth/google/callback
|
||||||
|
|
||||||
BASE_URL=http://localhost:3000
|
BASE_URL=http://localhost:3000
|
||||||
FRONTEND_URL=http://localhost:3001
|
FRONTEND_URL=http://localhost:5173
|
||||||
|
|
||||||
PORT=3000
|
PORT=3000
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -34,19 +33,15 @@ func initializeApp() {
|
|||||||
var err error
|
var err error
|
||||||
globalDB, err = database.Connect(globalCfg.MongoURI, globalCfg.MongoDBName)
|
globalDB, err = database.Connect(globalCfg.MongoURI, globalCfg.MongoDBName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to connect to database: %v", err)
|
|
||||||
initError = err
|
initError = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Successfully connected to database")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
initOnce.Do(initializeApp)
|
initOnce.Do(initializeApp)
|
||||||
|
|
||||||
if initError != nil {
|
if initError != nil {
|
||||||
log.Printf("Initialization error: %v", initError)
|
|
||||||
http.Error(w, "Application initialization failed: "+initError.Error(), http.StatusInternalServerError)
|
http.Error(w, "Application initialization failed: "+initError.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -114,6 +109,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
|||||||
api.HandleFunc("/torrents/series", torrentsHandler.SearchSeries).Methods("GET")
|
api.HandleFunc("/torrents/series", torrentsHandler.SearchSeries).Methods("GET")
|
||||||
api.HandleFunc("/torrents/anime", torrentsHandler.SearchAnime).Methods("GET")
|
api.HandleFunc("/torrents/anime", torrentsHandler.SearchAnime).Methods("GET")
|
||||||
api.HandleFunc("/torrents/seasons", torrentsHandler.GetAvailableSeasons).Methods("GET")
|
api.HandleFunc("/torrents/seasons", torrentsHandler.GetAvailableSeasons).Methods("GET")
|
||||||
|
api.HandleFunc("/torrents/by-title", torrentsHandler.SearchByTitle).Methods("GET")
|
||||||
api.HandleFunc("/torrents/search", torrentsHandler.SearchByQuery).Methods("GET")
|
api.HandleFunc("/torrents/search", torrentsHandler.SearchByQuery).Methods("GET")
|
||||||
|
|
||||||
api.HandleFunc("/reactions/{mediaType}/{mediaId}/counts", reactionsHandler.GetReactionCounts).Methods("GET")
|
api.HandleFunc("/reactions/{mediaType}/{mediaId}/counts", reactionsHandler.GetReactionCounts).Methods("GET")
|
||||||
|
|||||||
18
main.go
18
main.go
@@ -2,6 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -18,6 +20,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Инициализация логирования в файл
|
||||||
|
logFile, err := os.OpenFile("neomovies-api.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to open log file: %v\n", err)
|
||||||
|
} else {
|
||||||
|
// Пишем логи одновременно в файл и в консоль
|
||||||
|
multiWriter := io.MultiWriter(os.Stdout, logFile)
|
||||||
|
log.SetOutput(multiWriter)
|
||||||
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
defer logFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
if err := godotenv.Load(); err != nil {
|
if err := godotenv.Load(); err != nil {
|
||||||
_ = err
|
_ = err
|
||||||
}
|
}
|
||||||
@@ -84,13 +98,15 @@ func main() {
|
|||||||
api.HandleFunc("/players/vidlink/movie/{imdb_id}", playersHandler.GetVidlinkMoviePlayer).Methods("GET")
|
api.HandleFunc("/players/vidlink/movie/{imdb_id}", playersHandler.GetVidlinkMoviePlayer).Methods("GET")
|
||||||
api.HandleFunc("/players/vidlink/tv/{tmdb_id}", playersHandler.GetVidlinkTVPlayer).Methods("GET")
|
api.HandleFunc("/players/vidlink/tv/{tmdb_id}", playersHandler.GetVidlinkTVPlayer).Methods("GET")
|
||||||
api.HandleFunc("/players/hdvb/{id_type}/{id}", playersHandler.GetHDVBPlayer).Methods("GET")
|
api.HandleFunc("/players/hdvb/{id_type}/{id}", playersHandler.GetHDVBPlayer).Methods("GET")
|
||||||
|
api.HandleFunc("/players/collaps/{id_type}/{id}", playersHandler.GetCollapsPlayer).Methods("GET")
|
||||||
|
|
||||||
|
api.HandleFunc("/torrents/search/by-title", torrentsHandler.SearchByTitle).Methods("GET")
|
||||||
|
api.HandleFunc("/torrents/search", torrentsHandler.SearchByQuery).Methods("GET")
|
||||||
api.HandleFunc("/torrents/search/{imdbId}", torrentsHandler.SearchTorrents).Methods("GET")
|
api.HandleFunc("/torrents/search/{imdbId}", torrentsHandler.SearchTorrents).Methods("GET")
|
||||||
api.HandleFunc("/torrents/movies", torrentsHandler.SearchMovies).Methods("GET")
|
api.HandleFunc("/torrents/movies", torrentsHandler.SearchMovies).Methods("GET")
|
||||||
api.HandleFunc("/torrents/series", torrentsHandler.SearchSeries).Methods("GET")
|
api.HandleFunc("/torrents/series", torrentsHandler.SearchSeries).Methods("GET")
|
||||||
api.HandleFunc("/torrents/anime", torrentsHandler.SearchAnime).Methods("GET")
|
api.HandleFunc("/torrents/anime", torrentsHandler.SearchAnime).Methods("GET")
|
||||||
api.HandleFunc("/torrents/seasons", torrentsHandler.GetAvailableSeasons).Methods("GET")
|
api.HandleFunc("/torrents/seasons", torrentsHandler.GetAvailableSeasons).Methods("GET")
|
||||||
api.HandleFunc("/torrents/search", torrentsHandler.SearchByQuery).Methods("GET")
|
|
||||||
|
|
||||||
api.HandleFunc("/reactions/{mediaType}/{mediaId}/counts", reactionsHandler.GetReactionCounts).Methods("GET")
|
api.HandleFunc("/reactions/{mediaType}/{mediaId}/counts", reactionsHandler.GetReactionCounts).Methods("GET")
|
||||||
|
|
||||||
|
|||||||
1988
neomovies-api.log
Normal file
1988
neomovies-api.log
Normal file
File diff suppressed because one or more lines are too long
@@ -1,7 +1,6 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,6 +27,10 @@ type Config struct {
|
|||||||
KPAPIKey string
|
KPAPIKey string
|
||||||
HDVBToken string
|
HDVBToken string
|
||||||
KPAPIBaseURL string
|
KPAPIBaseURL string
|
||||||
|
CollapsAPIHost string
|
||||||
|
CollapsToken string
|
||||||
|
VeoVeoHost string
|
||||||
|
VeoVeoToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Config {
|
func New() *Config {
|
||||||
@@ -52,21 +55,22 @@ func New() *Config {
|
|||||||
GoogleRedirectURL: getEnv(EnvGoogleRedirectURL, ""),
|
GoogleRedirectURL: getEnv(EnvGoogleRedirectURL, ""),
|
||||||
FrontendURL: getEnv(EnvFrontendURL, ""),
|
FrontendURL: getEnv(EnvFrontendURL, ""),
|
||||||
VibixHost: getEnv(EnvVibixHost, DefaultVibixHost),
|
VibixHost: getEnv(EnvVibixHost, DefaultVibixHost),
|
||||||
VibixToken: getEnv(EnvVibixToken, ""),
|
|
||||||
KPAPIKey: getEnv(EnvKPAPIKey, ""),
|
KPAPIKey: getEnv(EnvKPAPIKey, ""),
|
||||||
HDVBToken: getEnv(EnvHDVBToken, ""),
|
HDVBToken: getEnv(EnvHDVBToken, ""),
|
||||||
KPAPIBaseURL: getEnv("KPAPI_BASE_URL", DefaultKPAPIBase),
|
KPAPIBaseURL: getEnv("KPAPI_BASE_URL", DefaultKPAPIBase),
|
||||||
|
CollapsAPIHost: getEnv("COLLAPS_API_HOST", ""),
|
||||||
|
CollapsToken: getEnv("COLLAPS_TOKEN", ""),
|
||||||
|
VeoVeoHost: getEnv("VEOVEO_HOST", ""),
|
||||||
|
VeoVeoToken: getEnv("VEOVEO_TOKEN", ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMongoURI() string {
|
func getMongoURI() string {
|
||||||
for _, envVar := range []string{"MONGO_URI", "MONGODB_URI", "DATABASE_URL", "MONGO_URL"} {
|
for _, envVar := range []string{"MONGO_URI", "MONGODB_URI", "DATABASE_URL", "MONGO_URL"} {
|
||||||
if value := os.Getenv(envVar); value != "" {
|
if value := os.Getenv(envVar); value != "" {
|
||||||
log.Printf("DEBUG: Using %s for MongoDB connection", envVar)
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("DEBUG: No MongoDB URI environment variable found")
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,18 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"neomovies-api/pkg/config"
|
"neomovies-api/pkg/config"
|
||||||
"neomovies-api/pkg/players"
|
"neomovies-api/pkg/players"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PlayersHandler struct {
|
type PlayersHandler struct {
|
||||||
@@ -28,48 +29,39 @@ func NewPlayersHandler(cfg *config.Config) *PlayersHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request) {
|
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)
|
vars := mux.Vars(r)
|
||||||
idType := vars["id_type"]
|
idType := vars["id_type"]
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
if idType == "" || id == "" {
|
if idType == "" || id == "" {
|
||||||
log.Printf("Error: id_type or id is empty")
|
|
||||||
http.Error(w, "id_type and id are required", http.StatusBadRequest)
|
http.Error(w, "id_type and id are required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if idType == "kinopoisk_id" { idType = "kp" }
|
if idType == "kinopoisk_id" {
|
||||||
|
idType = "kp"
|
||||||
|
}
|
||||||
if idType != "kp" && idType != "imdb" {
|
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)
|
http.Error(w, "id_type must be 'kp' (kinopoisk_id) or 'imdb'", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Processing %s ID: %s", idType, id)
|
|
||||||
|
|
||||||
if h.config.AllohaToken == "" {
|
if h.config.AllohaToken == "" {
|
||||||
log.Printf("Error: ALLOHA_TOKEN is missing")
|
|
||||||
http.Error(w, "Server misconfiguration: ALLOHA_TOKEN missing", http.StatusInternalServerError)
|
http.Error(w, "Server misconfiguration: ALLOHA_TOKEN missing", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
idParam := fmt.Sprintf("%s=%s", idType, url.QueryEscape(id))
|
idParam := fmt.Sprintf("%s=%s", idType, url.QueryEscape(id))
|
||||||
apiURL := fmt.Sprintf("https://api.alloha.tv/?token=%s&%s", h.config.AllohaToken, idParam)
|
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}
|
client := &http.Client{Timeout: 8 * time.Second}
|
||||||
resp, err := client.Get(apiURL)
|
resp, err := client.Get(apiURL)
|
||||||
if err != 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 fetch from Alloha API", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
log.Printf("Alloha API response status: %d", resp.StatusCode)
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
http.Error(w, fmt.Sprintf("Alloha API error: %d", resp.StatusCode), http.StatusBadGateway)
|
http.Error(w, fmt.Sprintf("Alloha API error: %d", resp.StatusCode), http.StatusBadGateway)
|
||||||
return
|
return
|
||||||
@@ -77,13 +69,10 @@ func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error reading Alloha response: %v", err)
|
|
||||||
http.Error(w, "Failed to read Alloha response", http.StatusInternalServerError)
|
http.Error(w, "Failed to read Alloha response", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Alloha API response body: %s", string(body))
|
|
||||||
|
|
||||||
var allohaResponse struct {
|
var allohaResponse struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Data struct {
|
Data struct {
|
||||||
@@ -92,13 +81,11 @@ func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(body, &allohaResponse); err != nil {
|
if err := json.Unmarshal(body, &allohaResponse); err != nil {
|
||||||
log.Printf("Error unmarshaling JSON: %v", err)
|
|
||||||
http.Error(w, "Invalid JSON from Alloha", http.StatusBadGateway)
|
http.Error(w, "Invalid JSON from Alloha", http.StatusBadGateway)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if allohaResponse.Status != "success" || allohaResponse.Data.Iframe == "" {
|
if allohaResponse.Status != "success" || allohaResponse.Data.Iframe == "" {
|
||||||
log.Printf("Video not found or empty iframe")
|
|
||||||
http.Error(w, "Video not found", http.StatusNotFound)
|
http.Error(w, "Video not found", http.StatusNotFound)
|
||||||
return
|
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)
|
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)
|
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.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte(htmlDoc))
|
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
|
// 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) {
|
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)
|
vars := mux.Vars(r)
|
||||||
idType := vars["id_type"]
|
idType := vars["id_type"]
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
if idType == "" || id == "" {
|
if idType == "" || id == "" {
|
||||||
log.Printf("Error: id_type or id is empty")
|
|
||||||
http.Error(w, "id_type and id are required", http.StatusBadRequest)
|
http.Error(w, "id_type and id are required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Поддержка алиаса
|
// Поддержка алиаса
|
||||||
if idType == "kinopoisk_id" { idType = "kp" }
|
if idType == "kinopoisk_id" {
|
||||||
|
idType = "kp"
|
||||||
|
}
|
||||||
if idType != "kp" && idType != "imdb" {
|
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)
|
http.Error(w, "id_type must be 'kp' or 'imdb'", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Processing %s ID: %s", idType, id)
|
|
||||||
|
|
||||||
if h.config.LumexURL == "" {
|
if h.config.LumexURL == "" {
|
||||||
log.Printf("Error: LUMEX_URL is missing")
|
|
||||||
http.Error(w, "Server misconfiguration: LUMEX_URL missing", http.StatusInternalServerError)
|
http.Error(w, "Server misconfiguration: LUMEX_URL missing", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -298,40 +278,30 @@ func (h *PlayersHandler) GetLumexPlayer(w http.ResponseWriter, r *http.Request)
|
|||||||
separator = "&"
|
separator = "&"
|
||||||
}
|
}
|
||||||
playerURL := fmt.Sprintf("%s%s%s=%s", h.config.LumexURL, separator, paramName, url.QueryEscape(id))
|
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)
|
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.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte(htmlDoc))
|
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) {
|
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)
|
vars := mux.Vars(r)
|
||||||
idType := vars["id_type"]
|
idType := vars["id_type"]
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
if idType == "" || id == "" {
|
if idType == "" || id == "" {
|
||||||
log.Printf("Error: id_type or id is empty")
|
|
||||||
http.Error(w, "id_type and id are required", http.StatusBadRequest)
|
http.Error(w, "id_type and id are required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if idType != "kp" && idType != "imdb" {
|
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)
|
http.Error(w, "id_type must be 'kp' or 'imdb'", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Processing %s ID: %s", idType, id)
|
|
||||||
|
|
||||||
if h.config.VibixToken == "" {
|
if h.config.VibixToken == "" {
|
||||||
log.Printf("Error: VIBIX_TOKEN is missing")
|
|
||||||
http.Error(w, "Server misconfiguration: VIBIX_TOKEN missing", http.StatusInternalServerError)
|
http.Error(w, "Server misconfiguration: VIBIX_TOKEN missing", http.StatusInternalServerError)
|
||||||
return
|
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)
|
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)
|
req, err := http.NewRequest("GET", apiURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error creating Vibix request: %v", err)
|
|
||||||
http.Error(w, "Failed to create request", http.StatusInternalServerError)
|
http.Error(w, "Failed to create request", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -365,202 +333,158 @@ func (h *PlayersHandler) GetVibixPlayer(w http.ResponseWriter, r *http.Request)
|
|||||||
client := &http.Client{Timeout: 8 * time.Second}
|
client := &http.Client{Timeout: 8 * time.Second}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error calling Vibix API: %v", err)
|
|
||||||
http.Error(w, "Failed to fetch from Vibix API", http.StatusInternalServerError)
|
http.Error(w, "Failed to fetch from Vibix API", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
log.Printf("Vibix API response status: %d", resp.StatusCode)
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
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)
|
http.Error(w, fmt.Sprintf("Vibix API error: %d", resp.StatusCode), http.StatusBadGateway)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error reading Vibix response: %v", err)
|
|
||||||
http.Error(w, "Failed to read Vibix response", http.StatusInternalServerError)
|
http.Error(w, "Failed to read Vibix response", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Vibix API response body: %s", string(body))
|
|
||||||
|
|
||||||
var vibixResponse struct {
|
var vibixResponse struct {
|
||||||
ID interface{} `json:"id"`
|
ID interface{} `json:"id"`
|
||||||
IframeURL string `json:"iframe_url"`
|
IframeURL string `json:"iframe_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(body, &vibixResponse); err != nil {
|
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)
|
http.Error(w, "Invalid JSON from Vibix", http.StatusBadGateway)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if vibixResponse.ID == nil || vibixResponse.IframeURL == "" {
|
if vibixResponse.ID == nil || vibixResponse.IframeURL == "" {
|
||||||
log.Printf("Video not found or empty iframe_url")
|
|
||||||
http.Error(w, "Video not found", http.StatusNotFound)
|
http.Error(w, "Video not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vibix использует только iframe_url без season/episode
|
// Vibix использует только iframe_url без season/episode
|
||||||
playerURL := vibixResponse.IframeURL
|
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)
|
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.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte(htmlDoc))
|
w.Write([]byte(htmlDoc))
|
||||||
|
|
||||||
log.Printf("Successfully served Vibix player for %s: %s", idType, id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRgShowsPlayer handles RgShows streaming requests
|
// GetRgShowsPlayer handles RgShows streaming requests
|
||||||
func (h *PlayersHandler) GetRgShowsPlayer(w http.ResponseWriter, r *http.Request) {
|
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)
|
vars := mux.Vars(r)
|
||||||
tmdbID := vars["tmdb_id"]
|
tmdbID := vars["tmdb_id"]
|
||||||
if tmdbID == "" {
|
if tmdbID == "" {
|
||||||
log.Printf("Error: tmdb_id is empty")
|
|
||||||
http.Error(w, "tmdb_id path param is required", http.StatusBadRequest)
|
http.Error(w, "tmdb_id path param is required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Processing tmdb_id: %s", tmdbID)
|
|
||||||
|
|
||||||
pm := players.NewPlayersManager()
|
pm := players.NewPlayersManager()
|
||||||
result, err := pm.GetMovieStreamByProvider("rgshows", tmdbID)
|
result, err := pm.GetMovieStreamByProvider("rgshows", tmdbID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error getting RgShows stream: %v", err)
|
|
||||||
http.Error(w, "Failed to get stream", http.StatusInternalServerError)
|
http.Error(w, "Failed to get stream", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !result.Success {
|
if !result.Success {
|
||||||
log.Printf("RgShows stream not found: %s", result.Error)
|
|
||||||
http.Error(w, "Stream not found", http.StatusNotFound)
|
http.Error(w, "Stream not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create iframe with the stream URL
|
// 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)
|
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.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte(htmlDoc))
|
w.Write([]byte(htmlDoc))
|
||||||
|
|
||||||
log.Printf("Successfully served RgShows player for tmdb_id: %s", tmdbID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRgShowsTVPlayer handles RgShows TV show streaming requests
|
// GetRgShowsTVPlayer handles RgShows TV show streaming requests
|
||||||
func (h *PlayersHandler) GetRgShowsTVPlayer(w http.ResponseWriter, r *http.Request) {
|
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)
|
vars := mux.Vars(r)
|
||||||
tmdbID := vars["tmdb_id"]
|
tmdbID := vars["tmdb_id"]
|
||||||
seasonStr := vars["season"]
|
seasonStr := vars["season"]
|
||||||
episodeStr := vars["episode"]
|
episodeStr := vars["episode"]
|
||||||
|
|
||||||
if tmdbID == "" || seasonStr == "" || episodeStr == "" {
|
if tmdbID == "" || seasonStr == "" || episodeStr == "" {
|
||||||
log.Printf("Error: missing required parameters")
|
|
||||||
http.Error(w, "tmdb_id, season, and episode path params are required", http.StatusBadRequest)
|
http.Error(w, "tmdb_id, season, and episode path params are required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
season, err := strconv.Atoi(seasonStr)
|
season, err := strconv.Atoi(seasonStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing season: %v", err)
|
|
||||||
http.Error(w, "Invalid season number", http.StatusBadRequest)
|
http.Error(w, "Invalid season number", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
episode, err := strconv.Atoi(episodeStr)
|
episode, err := strconv.Atoi(episodeStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing episode: %v", err)
|
|
||||||
http.Error(w, "Invalid episode number", http.StatusBadRequest)
|
http.Error(w, "Invalid episode number", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Processing tmdb_id: %s, season: %d, episode: %d", tmdbID, season, episode)
|
|
||||||
|
|
||||||
pm := players.NewPlayersManager()
|
pm := players.NewPlayersManager()
|
||||||
result, err := pm.GetTVStreamByProvider("rgshows", tmdbID, season, episode)
|
result, err := pm.GetTVStreamByProvider("rgshows", tmdbID, season, episode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error getting RgShows TV stream: %v", err)
|
|
||||||
http.Error(w, "Failed to get stream", http.StatusInternalServerError)
|
http.Error(w, "Failed to get stream", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !result.Success {
|
if !result.Success {
|
||||||
log.Printf("RgShows TV stream not found: %s", result.Error)
|
|
||||||
http.Error(w, "Stream not found", http.StatusNotFound)
|
http.Error(w, "Stream not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create iframe with the stream URL
|
// 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)
|
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.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte(htmlDoc))
|
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
|
// GetIframeVideoPlayer handles IframeVideo streaming requests
|
||||||
func (h *PlayersHandler) GetIframeVideoPlayer(w http.ResponseWriter, r *http.Request) {
|
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)
|
vars := mux.Vars(r)
|
||||||
kinopoiskID := vars["kinopoisk_id"]
|
kinopoiskID := vars["kinopoisk_id"]
|
||||||
imdbID := vars["imdb_id"]
|
imdbID := vars["imdb_id"]
|
||||||
|
|
||||||
if kinopoiskID == "" && imdbID == "" {
|
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)
|
http.Error(w, "Either kinopoisk_id or imdb_id path param is required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Processing kinopoisk_id: %s, imdb_id: %s", kinopoiskID, imdbID)
|
|
||||||
|
|
||||||
pm := players.NewPlayersManager()
|
pm := players.NewPlayersManager()
|
||||||
result, err := pm.GetStreamWithKinopoisk(kinopoiskID, imdbID)
|
result, err := pm.GetStreamWithKinopoisk(kinopoiskID, imdbID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error getting IframeVideo stream: %v", err)
|
|
||||||
http.Error(w, "Failed to get stream", http.StatusInternalServerError)
|
http.Error(w, "Failed to get stream", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !result.Success {
|
if !result.Success {
|
||||||
log.Printf("IframeVideo stream not found: %s", result.Error)
|
|
||||||
http.Error(w, "Stream not found", http.StatusNotFound)
|
http.Error(w, "Stream not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create iframe with the stream URL
|
// 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)
|
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.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte(htmlDoc))
|
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
|
// GetStreamAPI returns stream information as JSON API
|
||||||
func (h *PlayersHandler) GetStreamAPI(w http.ResponseWriter, r *http.Request) {
|
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)
|
vars := mux.Vars(r)
|
||||||
provider := vars["provider"]
|
provider := vars["provider"]
|
||||||
tmdbID := vars["tmdb_id"]
|
tmdbID := vars["tmdb_id"]
|
||||||
|
|
||||||
if provider == "" || tmdbID == "" {
|
if provider == "" || tmdbID == "" {
|
||||||
log.Printf("Error: missing required parameters")
|
|
||||||
http.Error(w, "provider and tmdb_id path params are required", http.StatusBadRequest)
|
http.Error(w, "provider and tmdb_id path params are required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -571,8 +495,6 @@ func (h *PlayersHandler) GetStreamAPI(w http.ResponseWriter, r *http.Request) {
|
|||||||
kinopoiskID := r.URL.Query().Get("kinopoisk_id")
|
kinopoiskID := r.URL.Query().Get("kinopoisk_id")
|
||||||
imdbID := r.URL.Query().Get("imdb_id")
|
imdbID := r.URL.Query().Get("imdb_id")
|
||||||
|
|
||||||
log.Printf("Processing provider: %s, tmdb_id: %s", provider, tmdbID)
|
|
||||||
|
|
||||||
pm := players.NewPlayersManager()
|
pm := players.NewPlayersManager()
|
||||||
var result *players.StreamResult
|
var result *players.StreamResult
|
||||||
var err error
|
var err error
|
||||||
@@ -602,7 +524,6 @@ func (h *PlayersHandler) GetStreamAPI(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error getting stream from %s: %v", provider, err)
|
|
||||||
result = &players.StreamResult{
|
result = &players.StreamResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
Provider: provider,
|
Provider: provider,
|
||||||
@@ -612,14 +533,10 @@ func (h *PlayersHandler) GetStreamAPI(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(result)
|
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)
|
// GetVidsrcPlayer handles Vidsrc.to player (uses IMDb ID for both movies and TV shows)
|
||||||
func (h *PlayersHandler) GetVidsrcPlayer(w http.ResponseWriter, r *http.Request) {
|
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)
|
vars := mux.Vars(r)
|
||||||
imdbId := vars["imdb_id"]
|
imdbId := vars["imdb_id"]
|
||||||
mediaType := vars["media_type"] // "movie" or "tv"
|
mediaType := vars["media_type"] // "movie" or "tv"
|
||||||
@@ -645,21 +562,15 @@ func (h *PlayersHandler) GetVidsrcPlayer(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Generated Vidsrc URL: %s", playerURL)
|
|
||||||
|
|
||||||
// Используем общий шаблон с кастомными контролами
|
// Используем общий шаблон с кастомными контролами
|
||||||
htmlDoc := getPlayerWithControlsHTML(playerURL, "Vidsrc Player")
|
htmlDoc := getPlayerWithControlsHTML(playerURL, "Vidsrc Player")
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte(htmlDoc))
|
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)
|
// GetVidlinkMoviePlayer handles vidlink.pro player for movies (uses IMDb ID)
|
||||||
func (h *PlayersHandler) GetVidlinkMoviePlayer(w http.ResponseWriter, r *http.Request) {
|
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)
|
vars := mux.Vars(r)
|
||||||
imdbId := vars["imdb_id"]
|
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)
|
playerURL := fmt.Sprintf("https://vidlink.pro/movie/%s", imdbId)
|
||||||
|
|
||||||
log.Printf("Generated Vidlink Movie URL: %s", playerURL)
|
|
||||||
|
|
||||||
// Используем общий шаблон с кастомными контролами
|
// Используем общий шаблон с кастомными контролами
|
||||||
htmlDoc := getPlayerWithControlsHTML(playerURL, "Vidlink Player")
|
htmlDoc := getPlayerWithControlsHTML(playerURL, "Vidlink Player")
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte(htmlDoc))
|
w.Write([]byte(htmlDoc))
|
||||||
|
|
||||||
log.Printf("Successfully served Vidlink movie player: %s", imdbId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVidlinkTVPlayer handles vidlink.pro player for TV shows (uses TMDB ID)
|
// 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) {
|
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)
|
vars := mux.Vars(r)
|
||||||
tmdbId := vars["tmdb_id"]
|
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)
|
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")
|
htmlDoc := getPlayerWithControlsHTML(playerURL, "Vidlink Player")
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte(htmlDoc))
|
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 для блокировки кликов
|
// getPlayerWithControlsHTML возвращает HTML с плеером и overlay для блокировки кликов
|
||||||
@@ -823,13 +707,6 @@ html,body{margin:0;height:100%%;overflow:hidden;background:#000;font-family:Aria
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<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',()=>{
|
document.getElementById('btn-fullscreen').addEventListener('click',()=>{
|
||||||
if(!document.fullscreenElement){
|
if(!document.fullscreenElement){
|
||||||
document.getElementById('container').requestFullscreen();
|
document.getElementById('container').requestFullscreen();
|
||||||
@@ -841,3 +718,190 @@ document.getElementById('btn-fullscreen').addEventListener('click',()=>{
|
|||||||
</body>
|
</body>
|
||||||
</html>`, title, playerURL)
|
</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
|
||||||
|
}
|
||||||
|
|||||||
@@ -365,3 +365,57 @@ func (h *TorrentsHandler) SearchByQuery(w http.ResponseWriter, r *http.Request)
|
|||||||
Data: response,
|
Data: response,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchByTitle - поиск торрентов по названию фильма/сериала (новый метод)
|
||||||
|
func (h *TorrentsHandler) SearchByTitle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
title := r.URL.Query().Get("title")
|
||||||
|
originalTitle := r.URL.Query().Get("originalTitle")
|
||||||
|
yearStr := r.URL.Query().Get("year")
|
||||||
|
contentType := r.URL.Query().Get("type")
|
||||||
|
|
||||||
|
if title == "" && originalTitle == "" {
|
||||||
|
http.Error(w, "Title or originalTitle is required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = "movie"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Парсим год
|
||||||
|
year := 0
|
||||||
|
if yearStr != "" {
|
||||||
|
if y, err := strconv.Atoi(yearStr); err == nil {
|
||||||
|
year = y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Определяем является ли это сериалом
|
||||||
|
isSerial := contentType == "series" || contentType == "tv"
|
||||||
|
|
||||||
|
// Поиск торрентов
|
||||||
|
results, err := h.torrentService.SearchTorrentsByTitle(title, originalTitle, year, isSerial)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяем фильтрацию по типу контента
|
||||||
|
results.Results = h.torrentService.FilterByContentType(results.Results, contentType)
|
||||||
|
results.Total = len(results.Results)
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"title": title,
|
||||||
|
"originalTitle": originalTitle,
|
||||||
|
"year": year,
|
||||||
|
"type": contentType,
|
||||||
|
"total": results.Total,
|
||||||
|
"results": results.Results,
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(models.APIResponse{
|
||||||
|
Success: true,
|
||||||
|
Data: response,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ type TorrentSearchResponse struct {
|
|||||||
// RedAPI специфичные структуры
|
// RedAPI специфичные структуры
|
||||||
type RedAPIResponse struct {
|
type RedAPIResponse struct {
|
||||||
Results []RedAPITorrent `json:"Results"`
|
Results []RedAPITorrent `json:"Results"`
|
||||||
|
Jacred bool `json:"jacred,omitempty"` // Флаг для определения источника
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedAPITorrent struct {
|
type RedAPITorrent struct {
|
||||||
@@ -277,15 +278,22 @@ type RedAPITorrent struct {
|
|||||||
MagnetUri string `json:"MagnetUri"`
|
MagnetUri string `json:"MagnetUri"`
|
||||||
PublishDate string `json:"PublishDate"`
|
PublishDate string `json:"PublishDate"`
|
||||||
CategoryDesc string `json:"CategoryDesc"`
|
CategoryDesc string `json:"CategoryDesc"`
|
||||||
|
Category []int `json:"Category,omitempty"` // Для jacred
|
||||||
Details string `json:"Details"`
|
Details string `json:"Details"`
|
||||||
Info *RedAPITorrentInfo `json:"Info,omitempty"`
|
Info *RedAPITorrentInfo `json:"Info,omitempty"`
|
||||||
|
Languages []string `json:"languages,omitempty"` // Для jacred
|
||||||
|
FFprobe []interface{} `json:"ffprobe,omitempty"` // Для jacred
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedAPITorrentInfo struct {
|
type RedAPITorrentInfo struct {
|
||||||
Quality interface{} `json:"quality,omitempty"` // Может быть string или number
|
Quality interface{} `json:"quality,omitempty"` // Может быть string или number
|
||||||
|
VideoType string `json:"videotype,omitempty"`
|
||||||
Voices []string `json:"voices,omitempty"`
|
Voices []string `json:"voices,omitempty"`
|
||||||
Types []string `json:"types,omitempty"`
|
Types []string `json:"types,omitempty"`
|
||||||
Seasons []int `json:"seasons,omitempty"`
|
Seasons []int `json:"seasons,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
OriginalName string `json:"originalname,omitempty"`
|
||||||
|
Released int `json:"relased,omitempty"` // Примечание: опечатка в API
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alloha API структуры для получения информации о фильмах
|
// Alloha API структуры для получения информации о фильмах
|
||||||
|
|||||||
@@ -82,11 +82,70 @@ func (s *TorrentService) SearchTorrents(params map[string]string) (*models.Torre
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchTorrentsByTitle - поиск торрентов по названию фильма/сериала
|
||||||
|
func (s *TorrentService) SearchTorrentsByTitle(title, originalTitle string, year int, isSerial bool) (*models.TorrentSearchResponse, error) {
|
||||||
|
searchParams := url.Values{}
|
||||||
|
|
||||||
|
// Добавляем основное название
|
||||||
|
if title != "" {
|
||||||
|
searchParams.Add("title", title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем оригинальное название
|
||||||
|
if originalTitle != "" {
|
||||||
|
searchParams.Add("title_original", originalTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем год
|
||||||
|
if year > 0 {
|
||||||
|
searchParams.Add("year", strconv.Itoa(year))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Указываем тип контента
|
||||||
|
if isSerial {
|
||||||
|
searchParams.Add("is_serial", "2")
|
||||||
|
} else {
|
||||||
|
searchParams.Add("is_serial", "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.apiKey != "" {
|
||||||
|
searchParams.Add("apikey", s.apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
searchURL := fmt.Sprintf("%s/api/v2.0/indexers/all/results?%s", s.baseURL, searchParams.Encode())
|
||||||
|
|
||||||
|
resp, err := s.client.Get(searchURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to search torrents by title: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var redAPIResponse models.RedAPIResponse
|
||||||
|
if err := json.Unmarshal(body, &redAPIResponse); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
results := s.parseRedAPIResults(redAPIResponse)
|
||||||
|
|
||||||
|
return &models.TorrentSearchResponse{
|
||||||
|
Query: title,
|
||||||
|
Results: results,
|
||||||
|
Total: len(results),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseRedAPIResults преобразует результаты RedAPI в наш формат
|
// parseRedAPIResults преобразует результаты RedAPI в наш формат
|
||||||
func (s *TorrentService) parseRedAPIResults(data models.RedAPIResponse) []models.TorrentResult {
|
func (s *TorrentService) parseRedAPIResults(data models.RedAPIResponse) []models.TorrentResult {
|
||||||
var results []models.TorrentResult
|
var results []models.TorrentResult
|
||||||
|
|
||||||
for _, torrent := range data.Results {
|
for i, torrent := range data.Results {
|
||||||
|
_ = i // для избежания неиспользуемой переменной
|
||||||
|
|
||||||
var sizeStr string
|
var sizeStr string
|
||||||
switch v := torrent.Size.(type) {
|
switch v := torrent.Size.(type) {
|
||||||
case string:
|
case string:
|
||||||
@@ -112,25 +171,38 @@ func (s *TorrentService) parseRedAPIResults(data models.RedAPIResponse) []models
|
|||||||
Source: "RedAPI",
|
Source: "RedAPI",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обработка Info для обоих форматов (RedAPI и jacred)
|
||||||
if torrent.Info != nil {
|
if torrent.Info != nil {
|
||||||
|
// Обработка качества
|
||||||
|
if torrent.Info.Quality != nil {
|
||||||
switch v := torrent.Info.Quality.(type) {
|
switch v := torrent.Info.Quality.(type) {
|
||||||
case string:
|
case string:
|
||||||
result.Quality = v
|
result.Quality = v
|
||||||
case float64:
|
case float64:
|
||||||
|
// Для jacred качество приходит как число (1080, 720 и т.д.)
|
||||||
|
if v >= 2160 {
|
||||||
|
result.Quality = "4K"
|
||||||
|
} else {
|
||||||
result.Quality = fmt.Sprintf("%.0fp", v)
|
result.Quality = fmt.Sprintf("%.0fp", v)
|
||||||
|
}
|
||||||
case int:
|
case int:
|
||||||
|
if v >= 2160 {
|
||||||
|
result.Quality = "4K"
|
||||||
|
} else {
|
||||||
result.Quality = fmt.Sprintf("%dp", v)
|
result.Quality = fmt.Sprintf("%dp", v)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.Voice = torrent.Info.Voices
|
result.Voice = torrent.Info.Voices
|
||||||
result.Types = torrent.Info.Types
|
result.Types = torrent.Info.Types
|
||||||
result.Seasons = torrent.Info.Seasons
|
result.Seasons = torrent.Info.Seasons
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Если качество все еще не определено, извлекаем из названия
|
||||||
if result.Quality == "" {
|
if result.Quality == "" {
|
||||||
result.Quality = s.ExtractQuality(result.Title)
|
result.Quality = s.ExtractQuality(result.Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, result)
|
results = append(results, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user