mirror of
https://gitlab.com/foxixus/neomovies-api.git
synced 2025-10-28 01:48:51 +05:00
feat: add client-side parsing players with Video.js
- Added client-side parsing for Vidsrc and Vidlink - Custom Video.js player with HLS support - Auto-detection of m3u8/mp4 streams from iframe - New routes: /players/vidsrc-parse, /players/vidlink-parse - Performance API monitoring for stream detection - Fallback to original iframe if parsing fails - Updated API documentation
This commit is contained in:
@@ -100,6 +100,11 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
api.HandleFunc("/players/vidsrc/{media_type}/{imdb_id}", playersHandler.GetVidsrcPlayer).Methods("GET")
|
||||
api.HandleFunc("/players/vidlink/movie/{imdb_id}", playersHandler.GetVidlinkMoviePlayer).Methods("GET")
|
||||
api.HandleFunc("/players/vidlink/tv/{tmdb_id}", playersHandler.GetVidlinkTVPlayer).Methods("GET")
|
||||
|
||||
// Client-side parsing players (custom player with HLS support)
|
||||
api.HandleFunc("/players/vidsrc-parse/{media_type}/{imdb_id}", playersHandler.GetVidsrcParserPlayer).Methods("GET")
|
||||
api.HandleFunc("/players/vidlink-parse/movie/{imdb_id}", playersHandler.GetVidlinkParserMoviePlayer).Methods("GET")
|
||||
api.HandleFunc("/players/vidlink-parse/tv/{tmdb_id}", playersHandler.GetVidlinkParserTVPlayer).Methods("GET")
|
||||
api.HandleFunc("/players/rgshows/{tmdb_id}", playersHandler.GetRgShowsPlayer).Methods("GET")
|
||||
api.HandleFunc("/players/rgshows/{tmdb_id}/{season}/{episode}", playersHandler.GetRgShowsTVPlayer).Methods("GET")
|
||||
api.HandleFunc("/players/iframevideo/{kinopoisk_id}/{imdb_id}", playersHandler.GetIframeVideoPlayer).Methods("GET")
|
||||
|
||||
5
main.go
5
main.go
@@ -81,6 +81,11 @@ func main() {
|
||||
api.HandleFunc("/players/vidsrc/{media_type}/{imdb_id}", playersHandler.GetVidsrcPlayer).Methods("GET")
|
||||
api.HandleFunc("/players/vidlink/movie/{imdb_id}", playersHandler.GetVidlinkMoviePlayer).Methods("GET")
|
||||
api.HandleFunc("/players/vidlink/tv/{tmdb_id}", playersHandler.GetVidlinkTVPlayer).Methods("GET")
|
||||
|
||||
// Client-side parsing players (custom player with HLS support)
|
||||
api.HandleFunc("/players/vidsrc-parse/{media_type}/{imdb_id}", playersHandler.GetVidsrcParserPlayer).Methods("GET")
|
||||
api.HandleFunc("/players/vidlink-parse/movie/{imdb_id}", playersHandler.GetVidlinkParserMoviePlayer).Methods("GET")
|
||||
api.HandleFunc("/players/vidlink-parse/tv/{tmdb_id}", playersHandler.GetVidlinkParserTVPlayer).Methods("GET")
|
||||
|
||||
api.HandleFunc("/torrents/search/{imdbId}", torrentsHandler.SearchTorrents).Methods("GET")
|
||||
api.HandleFunc("/torrents/movies", torrentsHandler.SearchMovies).Methods("GET")
|
||||
|
||||
419
pkg/handlers/client_parser.go
Normal file
419
pkg/handlers/client_parser.go
Normal file
@@ -0,0 +1,419 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// GetVidsrcParserPlayer handles Vidsrc.to player with client-side parsing
|
||||
func (h *PlayersHandler) GetVidsrcParserPlayer(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("GetVidsrcParserPlayer called: %s %s", r.Method, r.URL.Path)
|
||||
|
||||
vars := mux.Vars(r)
|
||||
imdbId := vars["imdb_id"]
|
||||
mediaType := vars["media_type"] // "movie" or "tv"
|
||||
|
||||
if imdbId == "" || mediaType == "" {
|
||||
http.Error(w, "imdb_id and media_type are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var embedURL string
|
||||
if mediaType == "movie" {
|
||||
embedURL = fmt.Sprintf("https://vidsrc.to/embed/movie/%s", imdbId)
|
||||
} else if mediaType == "tv" {
|
||||
season := r.URL.Query().Get("season")
|
||||
episode := r.URL.Query().Get("episode")
|
||||
if season == "" || episode == "" {
|
||||
http.Error(w, "season and episode are required for TV shows", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
embedURL = fmt.Sprintf("https://vidsrc.to/embed/tv/%s/%s/%s", imdbId, season, episode)
|
||||
} else {
|
||||
http.Error(w, "Invalid media_type. Use 'movie' or 'tv'", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Generated Vidsrc embed URL: %s", embedURL)
|
||||
|
||||
htmlDoc := generateClientParserHTML(embedURL, "Vidsrc Player")
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write([]byte(htmlDoc))
|
||||
|
||||
log.Printf("Successfully served Vidsrc parser player for %s: %s", mediaType, imdbId)
|
||||
}
|
||||
|
||||
// GetVidlinkParserMoviePlayer handles Vidlink.pro parser for movies
|
||||
func (h *PlayersHandler) GetVidlinkParserMoviePlayer(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("GetVidlinkParserMoviePlayer called: %s %s", r.Method, r.URL.Path)
|
||||
|
||||
vars := mux.Vars(r)
|
||||
imdbId := vars["imdb_id"]
|
||||
|
||||
if imdbId == "" {
|
||||
http.Error(w, "imdb_id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
embedURL := fmt.Sprintf("https://vidlink.pro/movie/%s", imdbId)
|
||||
|
||||
log.Printf("Generated Vidlink movie embed URL: %s", embedURL)
|
||||
|
||||
htmlDoc := generateClientParserHTML(embedURL, "Vidlink Player")
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write([]byte(htmlDoc))
|
||||
|
||||
log.Printf("Successfully served Vidlink parser movie player: %s", imdbId)
|
||||
}
|
||||
|
||||
// GetVidlinkParserTVPlayer handles Vidlink.pro parser for TV shows
|
||||
func (h *PlayersHandler) GetVidlinkParserTVPlayer(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("GetVidlinkParserTVPlayer called: %s %s", r.Method, r.URL.Path)
|
||||
|
||||
vars := mux.Vars(r)
|
||||
tmdbId := vars["tmdb_id"]
|
||||
|
||||
if tmdbId == "" {
|
||||
http.Error(w, "tmdb_id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
season := r.URL.Query().Get("season")
|
||||
episode := r.URL.Query().Get("episode")
|
||||
if season == "" || episode == "" {
|
||||
http.Error(w, "season and episode are required for TV shows", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
embedURL := fmt.Sprintf("https://vidlink.pro/tv/%s/%s/%s", tmdbId, season, episode)
|
||||
|
||||
log.Printf("Generated Vidlink TV embed URL: %s", embedURL)
|
||||
|
||||
htmlDoc := generateClientParserHTML(embedURL, "Vidlink Player")
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write([]byte(htmlDoc))
|
||||
|
||||
log.Printf("Successfully served Vidlink parser TV player: %s S%sE%s", tmdbId, season, episode)
|
||||
}
|
||||
|
||||
func generateClientParserHTML(embedURL, title string) string {
|
||||
return fmt.Sprintf(`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>%s</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/video.js@8.6.1/dist/video-js.min.css">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
|
||||
#status {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #fff;
|
||||
padding: 10px 15px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
z-index: 10000;
|
||||
max-width: 90%%;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
#status.success {
|
||||
background: rgba(34, 197, 94, 0.9);
|
||||
}
|
||||
|
||||
#status.error {
|
||||
background: rgba(239, 68, 68, 0.9);
|
||||
}
|
||||
|
||||
#loader {
|
||||
position: fixed;
|
||||
top: 50%%;
|
||||
left: 50%%;
|
||||
transform: translate(-50%%, -50%%);
|
||||
text-align: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid rgba(255, 255, 255, 0.1);
|
||||
border-top: 4px solid #fff;
|
||||
border-radius: 50%%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0%% { transform: rotate(0deg); }
|
||||
100%% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
#loader-text {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#hidden-iframe {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#player-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#player-container.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-js {
|
||||
width: 100%%;
|
||||
height: 100%%;
|
||||
}
|
||||
|
||||
.vjs-big-play-button {
|
||||
left: 50%%;
|
||||
top: 50%%;
|
||||
transform: translate(-50%%, -50%%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="status">Инициализация плеера...</div>
|
||||
|
||||
<div id="loader">
|
||||
<div class="spinner"></div>
|
||||
<div id="loader-text">Загрузка видео...</div>
|
||||
</div>
|
||||
|
||||
<iframe id="hidden-iframe" src="%s" sandbox="allow-scripts allow-same-origin"></iframe>
|
||||
|
||||
<div id="player-container">
|
||||
<video id="video-player" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto">
|
||||
<p class="vjs-no-js">
|
||||
Для воспроизведения видео включите JavaScript или используйте браузер с поддержкой HTML5.
|
||||
</p>
|
||||
</video>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/video.js@8.6.1/dist/video.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@videojs/http-streaming@3.8.0/dist/videojs-http-streaming.min.js"></script>
|
||||
|
||||
<script>
|
||||
const status = document.getElementById('status');
|
||||
const loader = document.getElementById('loader');
|
||||
const playerContainer = document.getElementById('player-container');
|
||||
const iframe = document.getElementById('hidden-iframe');
|
||||
|
||||
let foundUrls = new Set();
|
||||
let player = null;
|
||||
let checkTimeout = null;
|
||||
|
||||
function updateStatus(message, type = 'info') {
|
||||
status.textContent = message;
|
||||
status.className = type;
|
||||
console.log('[Parser]', message);
|
||||
}
|
||||
|
||||
function hideLoader() {
|
||||
loader.style.display = 'none';
|
||||
}
|
||||
|
||||
function showPlayer(url) {
|
||||
if (foundUrls.has(url)) return;
|
||||
foundUrls.add(url);
|
||||
|
||||
console.log('[Parser] Found stream URL:', url);
|
||||
updateStatus('Видео найдено! Запуск...', 'success');
|
||||
|
||||
setTimeout(() => {
|
||||
hideLoader();
|
||||
playerContainer.classList.add('active');
|
||||
iframe.style.display = 'none';
|
||||
|
||||
if (player) {
|
||||
player.dispose();
|
||||
}
|
||||
|
||||
player = videojs('video-player', {
|
||||
controls: true,
|
||||
autoplay: true,
|
||||
preload: 'auto',
|
||||
fluid: false,
|
||||
fill: true,
|
||||
responsive: true,
|
||||
html5: {
|
||||
vhs: {
|
||||
overrideNative: true,
|
||||
enableLowInitialPlaylist: true,
|
||||
smoothQualityChange: true,
|
||||
fastQualityChange: true
|
||||
},
|
||||
nativeAudioTracks: false,
|
||||
nativeVideoTracks: false
|
||||
}
|
||||
});
|
||||
|
||||
player.src({
|
||||
src: url,
|
||||
type: url.includes('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'
|
||||
});
|
||||
|
||||
player.ready(function() {
|
||||
updateStatus('Воспроизведение...', 'success');
|
||||
setTimeout(() => {
|
||||
status.style.opacity = '0';
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
player.on('error', function(e) {
|
||||
const error = player.error();
|
||||
console.error('[Parser] Player error:', error);
|
||||
updateStatus('Ошибка воспроизведения: ' + (error ? error.message : 'Unknown'), 'error');
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Intercept fetch requests from iframe
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = function(...args) {
|
||||
const url = args[0];
|
||||
if (typeof url === 'string') {
|
||||
if (url.includes('.m3u8') || url.includes('.mp4')) {
|
||||
console.log('[Parser] Intercepted fetch:', url);
|
||||
showPlayer(url);
|
||||
}
|
||||
}
|
||||
return originalFetch.apply(this, args);
|
||||
};
|
||||
|
||||
// Monitor iframe network activity using Performance API
|
||||
let lastCheck = Date.now();
|
||||
function checkPerformance() {
|
||||
try {
|
||||
const entries = performance.getEntriesByType('resource');
|
||||
const newEntries = entries.filter(e => e.startTime > lastCheck);
|
||||
|
||||
newEntries.forEach(entry => {
|
||||
const url = entry.name;
|
||||
if (url.includes('.m3u8') || (url.includes('.mp4') && !url.includes('poster'))) {
|
||||
console.log('[Parser] Performance API detected:', url);
|
||||
showPlayer(url);
|
||||
}
|
||||
});
|
||||
|
||||
lastCheck = Date.now();
|
||||
} catch (e) {
|
||||
console.error('[Parser] Performance check error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Check performance entries every 500ms
|
||||
setInterval(checkPerformance, 500);
|
||||
|
||||
// Listen for messages from iframe (if we can inject script there)
|
||||
window.addEventListener('message', function(event) {
|
||||
try {
|
||||
if (event.data && typeof event.data === 'object') {
|
||||
if (event.data.type === 'stream-url' && event.data.url) {
|
||||
console.log('[Parser] Message from iframe:', event.data.url);
|
||||
showPlayer(event.data.url);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Parser] Message handler error:', e);
|
||||
}
|
||||
});
|
||||
|
||||
// Inject monitoring script into iframe (may not work due to CORS)
|
||||
iframe.addEventListener('load', function() {
|
||||
try {
|
||||
console.log('[Parser] Iframe loaded, attempting to inject monitor...');
|
||||
updateStatus('Поиск видео...', 'info');
|
||||
|
||||
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
const script = iframeDoc.createElement('script');
|
||||
script.textContent = ` + "`" + `
|
||||
(function() {
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = function(...args) {
|
||||
const url = args[0];
|
||||
if (typeof url === 'string' && (url.includes('.m3u8') || url.includes('.mp4'))) {
|
||||
window.parent.postMessage({ type: 'stream-url', url: url }, '*');
|
||||
}
|
||||
return originalFetch.apply(this, args);
|
||||
};
|
||||
|
||||
const originalOpen = XMLHttpRequest.prototype.open;
|
||||
XMLHttpRequest.prototype.open = function(method, url) {
|
||||
if (typeof url === 'string' && (url.includes('.m3u8') || url.includes('.mp4'))) {
|
||||
window.parent.postMessage({ type: 'stream-url', url: url }, '*');
|
||||
}
|
||||
return originalOpen.apply(this, arguments);
|
||||
};
|
||||
})();
|
||||
` + "`" + `;
|
||||
iframeDoc.head.appendChild(script);
|
||||
console.log('[Parser] Monitor script injected successfully');
|
||||
} catch (e) {
|
||||
console.warn('[Parser] Could not inject into iframe (CORS):', e.message);
|
||||
updateStatus('Мониторинг через Performance API...', 'info');
|
||||
}
|
||||
});
|
||||
|
||||
// Timeout if nothing found in 30 seconds
|
||||
checkTimeout = setTimeout(() => {
|
||||
if (foundUrls.size === 0) {
|
||||
updateStatus('Не удалось найти видео. Попробуйте обновить страницу.', 'error');
|
||||
hideLoader();
|
||||
// Fallback: show iframe directly
|
||||
iframe.style.width = '100%%';
|
||||
iframe.style.height = '100%%';
|
||||
iframe.style.opacity = '1';
|
||||
iframe.style.pointerEvents = 'auto';
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
// Cleanup
|
||||
window.addEventListener('beforeunload', function() {
|
||||
if (player) {
|
||||
player.dispose();
|
||||
}
|
||||
if (checkTimeout) {
|
||||
clearTimeout(checkTimeout);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[Parser] Client-side parser initialized');
|
||||
console.log('[Parser] Target URL: %s');
|
||||
</script>
|
||||
</body>
|
||||
</html>`, title, embedURL, embedURL)
|
||||
}
|
||||
@@ -484,6 +484,116 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec {
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/v1/players/vidsrc-parse/{media_type}/{imdb_id}": map[string]interface{}{
|
||||
"get": map[string]interface{}{
|
||||
"summary": "Vidsrc плеер с парсингом (кастомный плеер)",
|
||||
"description": "Возвращает HTML-страницу с кастомным Video.js плеером. Автоматически извлекает m3u8 ссылку из Vidsrc.to через клиентский парсинг в iframe. Использует IMDb ID для фильмов и сериалов.",
|
||||
"tags": []string{"Players"},
|
||||
"parameters": []map[string]interface{}{
|
||||
{
|
||||
"name": "media_type",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": map[string]interface{}{"type": "string", "enum": []string{"movie", "tv"}},
|
||||
"description": "Тип контента: movie (фильм) или tv (сериал)",
|
||||
},
|
||||
{
|
||||
"name": "imdb_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": map[string]string{"type": "string"},
|
||||
"description": "IMDb ID, например tt6385540 (с префиксом tt)",
|
||||
},
|
||||
{
|
||||
"name": "season",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": map[string]string{"type": "integer"},
|
||||
"description": "Номер сезона (обязательно для TV)",
|
||||
},
|
||||
{
|
||||
"name": "episode",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": map[string]string{"type": "integer"},
|
||||
"description": "Номер серии (обязательно для TV)",
|
||||
},
|
||||
},
|
||||
"responses": map[string]interface{}{
|
||||
"200": map[string]interface{}{
|
||||
"description": "HTML с кастомным Video.js плеером и системой парсинга",
|
||||
"content": map[string]interface{}{
|
||||
"text/html": map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
"400": map[string]interface{}{"description": "Отсутствуют обязательные параметры"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/v1/players/vidlink-parse/movie/{imdb_id}": map[string]interface{}{
|
||||
"get": map[string]interface{}{
|
||||
"summary": "Vidlink плеер с парсингом для фильмов (кастомный)",
|
||||
"description": "Возвращает HTML-страницу с кастомным Video.js плеером. Автоматически извлекает m3u8/mp4 ссылку из Vidlink.pro через клиентский парсинг. Использует IMDb ID для фильмов.",
|
||||
"tags": []string{"Players"},
|
||||
"parameters": []map[string]interface{}{
|
||||
{
|
||||
"name": "imdb_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": map[string]string{"type": "string"},
|
||||
"description": "IMDb ID фильма, например tt1234567 (с префиксом tt)",
|
||||
},
|
||||
},
|
||||
"responses": map[string]interface{}{
|
||||
"200": map[string]interface{}{
|
||||
"description": "HTML с кастомным Video.js плеером и системой парсинга",
|
||||
"content": map[string]interface{}{
|
||||
"text/html": map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
"400": map[string]interface{}{"description": "IMDb ID не указан"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/v1/players/vidlink-parse/tv/{tmdb_id}": map[string]interface{}{
|
||||
"get": map[string]interface{}{
|
||||
"summary": "Vidlink плеер с парсингом для сериалов (кастомный)",
|
||||
"description": "Возвращает HTML-страницу с кастомным Video.js плеером. Автоматически извлекает m3u8/mp4 ссылку из Vidlink.pro через клиентский парсинг. Использует TMDB ID для сериалов.",
|
||||
"tags": []string{"Players"},
|
||||
"parameters": []map[string]interface{}{
|
||||
{
|
||||
"name": "tmdb_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": map[string]string{"type": "integer"},
|
||||
"description": "TMDB ID сериала, например 94997 (числовой идентификатор без префикса)",
|
||||
},
|
||||
{
|
||||
"name": "season",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": map[string]string{"type": "integer"},
|
||||
"description": "Номер сезона (обязательно)",
|
||||
},
|
||||
{
|
||||
"name": "episode",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": map[string]string{"type": "integer"},
|
||||
"description": "Номер серии (обязательно)",
|
||||
},
|
||||
},
|
||||
"responses": map[string]interface{}{
|
||||
"200": map[string]interface{}{
|
||||
"description": "HTML с кастомным Video.js плеером и системой парсинга",
|
||||
"content": map[string]interface{}{
|
||||
"text/html": map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
"400": map[string]interface{}{"description": "Отсутствуют обязательные параметры (tmdb_id, season, episode)"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
"/api/v1/torrents/search/{imdbId}": map[string]interface{}{
|
||||
|
||||
Reference in New Issue
Block a user