mirror of
https://gitlab.com/foxixus/neomovies-api.git
synced 2025-10-29 02:18:50 +05:00
Compare commits
113 Commits
1f51746740
...
c7aa844f49
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7aa844f49 | ||
|
|
0fbf0f0f42 | ||
|
|
f2f06485fd | ||
| f5a754ddf7 | |||
| be849fd103 | |||
| af625c7950 | |||
| 7631e34f2d | |||
| 18421848c2 | |||
| fd8e2cccfe | |||
| 30c48fbc50 | |||
| 36389f674f | |||
| 35ceb00217 | |||
| 7b8d92af14 | |||
| e5e70a635b | |||
| 28555a83e1 | |||
|
|
567b287322 | ||
|
|
03091b0fc3 | ||
|
|
42d38ba0d1 | ||
|
|
859a7fd380 | ||
|
|
303079740f | ||
|
|
39c8366ae1 | ||
|
|
d47b4fd0a8 | ||
|
|
0d54aacc7d | ||
|
|
4e88529e0a | ||
|
|
0bd3a8860f | ||
|
|
5e761dbbc6 | ||
|
|
5d422231ca | ||
|
|
b467b7ed1c | ||
|
|
b76e8f685d | ||
|
|
3be73ad264 | ||
|
|
c170b2c7fa | ||
|
|
52d7e48bdb | ||
|
|
d4e29a8093 | ||
|
|
6ee4b8cc58 | ||
|
|
b20edae256 | ||
|
|
d29dce0afc | ||
|
|
39eea67323 | ||
|
|
bd853e7f89 | ||
|
|
4e6e447e79 | ||
| e734e462c4 | |||
| c183861491 | |||
|
|
63b11eb2ad | ||
|
|
321694df9c | ||
| a31cdf0f75 | |||
| dfcd9db295 | |||
| 59334da140 | |||
| 04583418a1 | |||
| 42073ea7b4 | |||
| a2e015aa53 | |||
| 552e60440c | |||
| fcb6caf1b9 | |||
| bb64b2dde4 | |||
| 86034c8e12 | |||
| f3c1cab796 | |||
| d790eb7903 | |||
| d347c6003a | |||
| c8cf79d764 | |||
| 206aa770b6 | |||
| 9db1ee3f50 | |||
| 171a2bf3ed | |||
| 486bbf5475 | |||
| 12ed40f3d4 | |||
| 53d70c9262 | |||
| 7f6ff5f660 | |||
| 4a9a7febec | |||
| 66cd0d3b21 | |||
| 92b936f057 | |||
| 9cd3d45327 | |||
| efcc5cd2b9 | |||
| a575b5c5bf | |||
| 51af31a6d5 | |||
| 0b2dc6b2f4 | |||
| cc463c4d7c | |||
| 94968f3cd1 | |||
| dff5e963ab | |||
| cf5dfc7e54 | |||
| 1005f30285 | |||
| ea3c208292 | |||
| 5ce5da39bb | |||
| 58a32d8838 | |||
| 770ecef6d5 | |||
| 37040dd7ec | |||
| 7aa0307e25 | |||
| d961393562 | |||
| e1e2b4f92b | |||
| 6b063f4c70 | |||
| 95910e0710 | |||
| 02dedbb8f7 | |||
| 6bf00451fa | |||
| 600de04561 | |||
| 7a83bf2e27 | |||
| 1fd522872d | |||
| 0f751cced0 | |||
| 4cb06cbde5 | |||
| 4f23e979d5 | |||
| c25d4e5d87 | |||
| 5361894af1 | |||
| a5eb03aea8 | |||
| a04b4f7c12 | |||
| 3a86c14129 | |||
| 498bc41c1b | |||
| 4d73fc9d8c | |||
| 2d25162b1c | |||
| af5957b4f9 | |||
| a724bf0484 | |||
| 8b11f89347 | |||
| 0c1cfb1ac5 | |||
| 868e71991c | |||
| 5f859eebb8 | |||
| 3a6ac8db4b | |||
| 60c574849b | |||
| 037ab7a458 | |||
| 9d60080116 |
10
.env.example
10
.env.example
@@ -1,15 +1,15 @@
|
|||||||
MONGO_URI=mongodb://localhost:27017/neomovies
|
MONGO_URI=mongodb://localhost:27017/neomovies
|
||||||
MONGO_DB_NAME=neomovies
|
MONGO_DB_NAME=neomovies
|
||||||
|
|
||||||
TMDB_ACCESS_TOKEN=your_tmdb_access_token
|
TMDB_ACCESS_TOKEN=your_tmdb_access_token_here
|
||||||
|
|
||||||
KPAPI_KEY=your_kp_api_key
|
KPAPI_KEY=920aaf6a-9f64-46f7-bda7-209fb1069440
|
||||||
KPAPI_BASE_URL=https://kinopoiskapiunofficial.tech/api
|
KPAPI_BASE_URL=https://kinopoiskapiunofficial.tech/api
|
||||||
|
|
||||||
HDVB_TOKEN=your_hdvb_token
|
HDVB_TOKEN=b9ae5f8c4832244060916af4aa9d1939
|
||||||
|
|
||||||
VIBIX_HOST=https://vibix.org
|
VIBIX_HOST=https://vibix.org
|
||||||
VIBIX_TOKEN=your_vibix_token
|
VIBIX_TOKEN=18745|NzecUXT4gikPUtFkSEFlDLPmr9kWnQACTo1N0Ixq9240bcf1
|
||||||
|
|
||||||
LUMEX_URL=https://p.lumex.space
|
LUMEX_URL=https://p.lumex.space
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ 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
|
||||||
|
|
||||||
JWT_SECRET=your_jwt_secret_key
|
JWT_SECRET=your_jwt_secret_key_here
|
||||||
|
|
||||||
GMAIL_USER=your_gmail@gmail.com
|
GMAIL_USER=your_gmail@gmail.com
|
||||||
GMAIL_APP_PASSWORD=your_gmail_app_password
|
GMAIL_APP_PASSWORD=your_gmail_app_password
|
||||||
|
|||||||
87
README.md
87
README.md
@@ -1,4 +1,4 @@
|
|||||||
# Neo Movies API (Unified)
|
# Neo Movies API
|
||||||
|
|
||||||
REST API для поиска и получения информации о фильмах, использующий TMDB API.
|
REST API для поиска и получения информации о фильмах, использующий TMDB API.
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ GOOGLE_REDIRECT_URL=http://localhost:3000/api/v1/auth/google/callback
|
|||||||
|
|
||||||
## 📋 API Endpoints
|
## 📋 API Endpoints
|
||||||
|
|
||||||
### 🔓 Публичные маршруты (старые)
|
### 🔓 Публичные маршруты
|
||||||
|
|
||||||
```http
|
```http
|
||||||
# Система
|
# Система
|
||||||
@@ -114,7 +114,7 @@ GET /api/v1/movies/popular # Популярные
|
|||||||
GET /api/v1/movies/top-rated # Топ-рейтинговые
|
GET /api/v1/movies/top-rated # Топ-рейтинговые
|
||||||
GET /api/v1/movies/upcoming # Предстоящие
|
GET /api/v1/movies/upcoming # Предстоящие
|
||||||
GET /api/v1/movies/now-playing # В прокате
|
GET /api/v1/movies/now-playing # В прокате
|
||||||
GET /api/v1/movies/{id} # Детали фильма (устар.)
|
GET /api/v1/movies/{id} # Детали фильма
|
||||||
GET /api/v1/movies/{id}/recommendations # Рекомендации
|
GET /api/v1/movies/{id}/recommendations # Рекомендации
|
||||||
GET /api/v1/movies/{id}/similar # Похожие
|
GET /api/v1/movies/{id}/similar # Похожие
|
||||||
|
|
||||||
@@ -124,86 +124,7 @@ GET /api/v1/tv/popular # Популярные
|
|||||||
GET /api/v1/tv/top-rated # Топ-рейтинговые
|
GET /api/v1/tv/top-rated # Топ-рейтинговые
|
||||||
GET /api/v1/tv/on-the-air # В эфире
|
GET /api/v1/tv/on-the-air # В эфире
|
||||||
GET /api/v1/tv/airing-today # Сегодня в эфире
|
GET /api/v1/tv/airing-today # Сегодня в эфире
|
||||||
GET /api/v1/tv/{id} # Детали сериала (устар.)
|
GET /api/v1/tv/{id} # Детали сериала
|
||||||
### 🔓 Публичные маршруты (унифицированные)
|
|
||||||
|
|
||||||
```http
|
|
||||||
# Единый формат ID: SOURCE_ID = kp_123 | tmdb_456
|
|
||||||
GET /api/v1/movie/{SOURCE_ID} # Детали фильма (унифицированный ответ)
|
|
||||||
GET /api/v1/tv/{SOURCE_ID} # Детали сериала (унифицированный ответ, с seasons[])
|
|
||||||
GET /api/v1/search?query=...&source=kp|tmdb # Мультипоиск (унифицированные элементы)
|
|
||||||
```
|
|
||||||
|
|
||||||
Примеры:
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/v1/movie/tmdb_550
|
|
||||||
GET /api/v1/movie/kp_666
|
|
||||||
GET /api/v1/tv/tmdb_1399
|
|
||||||
GET /api/v1/search?query=matrix&source=tmdb
|
|
||||||
```
|
|
||||||
|
|
||||||
Схема ответа см. раздел «Unified responses» ниже.
|
|
||||||
|
|
||||||
## Unified responses
|
|
||||||
|
|
||||||
Пример карточки:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"id": "550",
|
|
||||||
"sourceId": "tmdb_550",
|
|
||||||
"title": "Fight Club",
|
|
||||||
"originalTitle": "Fight Club",
|
|
||||||
"description": "…",
|
|
||||||
"releaseDate": "1999-10-15",
|
|
||||||
"endDate": null,
|
|
||||||
"type": "movie",
|
|
||||||
"genres": [{ "id": "drama", "name": "Drama" }],
|
|
||||||
"rating": 8.8,
|
|
||||||
"posterUrl": "https://image.tmdb.org/t/p/w500/...jpg",
|
|
||||||
"backdropUrl": "https://image.tmdb.org/t/p/w1280/...jpg",
|
|
||||||
"director": "",
|
|
||||||
"cast": [],
|
|
||||||
"duration": 139,
|
|
||||||
"country": "US",
|
|
||||||
"language": "en",
|
|
||||||
"budget": 63000000,
|
|
||||||
"revenue": 100853753,
|
|
||||||
"imdbId": "0137523",
|
|
||||||
"externalIds": { "kp": null, "tmdb": 550, "imdb": "0137523" },
|
|
||||||
"seasons": []
|
|
||||||
},
|
|
||||||
"source": "tmdb",
|
|
||||||
"metadata": { "fetchedAt": "...", "apiVersion": "3.0", "responseTime": 12 }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Пример мультипоиска:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "550",
|
|
||||||
"sourceId": "tmdb_550",
|
|
||||||
"title": "Fight Club",
|
|
||||||
"type": "movie",
|
|
||||||
"releaseDate": "1999-10-15",
|
|
||||||
"posterUrl": "https://image.tmdb.org/t/p/w500/...jpg",
|
|
||||||
"rating": 8.8,
|
|
||||||
"description": "…",
|
|
||||||
"externalIds": { "kp": null, "tmdb": 550, "imdb": "" }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": "tmdb",
|
|
||||||
"pagination": { "page": 1, "totalPages": 5, "totalResults": 42, "pageSize": 20 },
|
|
||||||
"metadata": { "fetchedAt": "...", "apiVersion": "3.0", "responseTime": 20, "query": "fight" }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
GET /api/v1/tv/{id}/recommendations # Рекомендации
|
GET /api/v1/tv/{id}/recommendations # Рекомендации
|
||||||
GET /api/v1/tv/{id}/similar # Похожие
|
GET /api/v1/tv/{id}/similar # Похожие
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
|||||||
favoritesHandler := handlersPkg.NewFavoritesHandler(favoritesService, globalCfg)
|
favoritesHandler := handlersPkg.NewFavoritesHandler(favoritesService, globalCfg)
|
||||||
docsHandler := handlersPkg.NewDocsHandler()
|
docsHandler := handlersPkg.NewDocsHandler()
|
||||||
searchHandler := handlersPkg.NewSearchHandler(tmdbService, kpService)
|
searchHandler := handlersPkg.NewSearchHandler(tmdbService, kpService)
|
||||||
unifiedHandler := handlersPkg.NewUnifiedHandler(tmdbService, kpService)
|
|
||||||
categoriesHandler := handlersPkg.NewCategoriesHandler(tmdbService)
|
categoriesHandler := handlersPkg.NewCategoriesHandler(tmdbService)
|
||||||
playersHandler := handlersPkg.NewPlayersHandler(globalCfg)
|
playersHandler := handlersPkg.NewPlayersHandler(globalCfg)
|
||||||
torrentsHandler := handlersPkg.NewTorrentsHandler(torrentService, tmdbService)
|
torrentsHandler := handlersPkg.NewTorrentsHandler(torrentService, tmdbService)
|
||||||
@@ -125,10 +124,6 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
|||||||
api.HandleFunc("/movies/upcoming", movieHandler.Upcoming).Methods("GET")
|
api.HandleFunc("/movies/upcoming", movieHandler.Upcoming).Methods("GET")
|
||||||
api.HandleFunc("/movies/now-playing", movieHandler.NowPlaying).Methods("GET")
|
api.HandleFunc("/movies/now-playing", movieHandler.NowPlaying).Methods("GET")
|
||||||
api.HandleFunc("/movies/{id}", movieHandler.GetByID).Methods("GET")
|
api.HandleFunc("/movies/{id}", movieHandler.GetByID).Methods("GET")
|
||||||
// Unified prefixed routes
|
|
||||||
api.HandleFunc("/movie/{id}", unifiedHandler.GetMovie).Methods("GET")
|
|
||||||
api.HandleFunc("/tv/{id}", unifiedHandler.GetTV).Methods("GET")
|
|
||||||
api.HandleFunc("/search", unifiedHandler.Search).Methods("GET")
|
|
||||||
api.HandleFunc("/movies/{id}/recommendations", movieHandler.GetRecommendations).Methods("GET")
|
api.HandleFunc("/movies/{id}/recommendations", movieHandler.GetRecommendations).Methods("GET")
|
||||||
api.HandleFunc("/movies/{id}/similar", movieHandler.GetSimilar).Methods("GET")
|
api.HandleFunc("/movies/{id}/similar", movieHandler.GetSimilar).Methods("GET")
|
||||||
api.HandleFunc("/movies/{id}/external-ids", movieHandler.GetExternalIDs).Methods("GET")
|
api.HandleFunc("/movies/{id}/external-ids", movieHandler.GetExternalIDs).Methods("GET")
|
||||||
|
|||||||
5
main.go
5
main.go
@@ -48,7 +48,6 @@ func main() {
|
|||||||
favoritesHandler := appHandlers.NewFavoritesHandler(favoritesService, cfg)
|
favoritesHandler := appHandlers.NewFavoritesHandler(favoritesService, cfg)
|
||||||
docsHandler := appHandlers.NewDocsHandler()
|
docsHandler := appHandlers.NewDocsHandler()
|
||||||
searchHandler := appHandlers.NewSearchHandler(tmdbService, kpService)
|
searchHandler := appHandlers.NewSearchHandler(tmdbService, kpService)
|
||||||
unifiedHandler := appHandlers.NewUnifiedHandler(tmdbService, kpService)
|
|
||||||
categoriesHandler := appHandlers.NewCategoriesHandler(tmdbService)
|
categoriesHandler := appHandlers.NewCategoriesHandler(tmdbService)
|
||||||
playersHandler := appHandlers.NewPlayersHandler(cfg)
|
playersHandler := appHandlers.NewPlayersHandler(cfg)
|
||||||
torrentsHandler := appHandlers.NewTorrentsHandler(torrentService, tmdbService)
|
torrentsHandler := appHandlers.NewTorrentsHandler(torrentService, tmdbService)
|
||||||
@@ -102,10 +101,6 @@ func main() {
|
|||||||
api.HandleFunc("/movies/upcoming", movieHandler.Upcoming).Methods("GET")
|
api.HandleFunc("/movies/upcoming", movieHandler.Upcoming).Methods("GET")
|
||||||
api.HandleFunc("/movies/now-playing", movieHandler.NowPlaying).Methods("GET")
|
api.HandleFunc("/movies/now-playing", movieHandler.NowPlaying).Methods("GET")
|
||||||
api.HandleFunc("/movies/{id}", movieHandler.GetByID).Methods("GET")
|
api.HandleFunc("/movies/{id}", movieHandler.GetByID).Methods("GET")
|
||||||
// Unified prefixed routes
|
|
||||||
api.HandleFunc("/movie/{id}", unifiedHandler.GetMovie).Methods("GET")
|
|
||||||
api.HandleFunc("/tv/{id}", unifiedHandler.GetTV).Methods("GET")
|
|
||||||
api.HandleFunc("/search", unifiedHandler.Search).Methods("GET")
|
|
||||||
api.HandleFunc("/movies/{id}/recommendations", movieHandler.GetRecommendations).Methods("GET")
|
api.HandleFunc("/movies/{id}/recommendations", movieHandler.GetRecommendations).Methods("GET")
|
||||||
api.HandleFunc("/movies/{id}/similar", movieHandler.GetSimilar).Methods("GET")
|
api.HandleFunc("/movies/{id}/similar", movieHandler.GetSimilar).Methods("GET")
|
||||||
api.HandleFunc("/movies/{id}/external-ids", movieHandler.GetExternalIDs).Methods("GET")
|
api.HandleFunc("/movies/{id}/external-ids", movieHandler.GetExternalIDs).Methods("GET")
|
||||||
|
|||||||
@@ -161,8 +161,8 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec {
|
|||||||
OpenAPI: "3.0.0",
|
OpenAPI: "3.0.0",
|
||||||
Info: Info{
|
Info: Info{
|
||||||
Title: "Neo Movies API",
|
Title: "Neo Movies API",
|
||||||
Description: "Унифицированный API (TMDB/Kinopoisk) с префиксными ID (kp_*, tmdb_*) и единым форматом данных",
|
Description: "Современный API для поиска фильмов и сериалов с интеграцией TMDB и поддержкой авторизации",
|
||||||
Version: "3.0.0",
|
Version: "2.0.0",
|
||||||
Contact: Contact{
|
Contact: Contact{
|
||||||
Name: "API Support",
|
Name: "API Support",
|
||||||
URL: "https://github.com/your-username/neomovies-api-go",
|
URL: "https://github.com/your-username/neomovies-api-go",
|
||||||
@@ -194,10 +194,10 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"/api/v1/search": map[string]interface{}{
|
"/api/v1/search/multi": map[string]interface{}{
|
||||||
"get": map[string]interface{}{
|
"get": map[string]interface{}{
|
||||||
"summary": "Унифицированный поиск",
|
"summary": "Мультипоиск",
|
||||||
"description": "Поиск фильмов и сериалов в источниках TMDB или Kinopoisk",
|
"description": "Поиск фильмов, сериалов и актеров",
|
||||||
"tags": []string{"Search"},
|
"tags": []string{"Search"},
|
||||||
"parameters": []map[string]interface{}{
|
"parameters": []map[string]interface{}{
|
||||||
{
|
{
|
||||||
@@ -207,13 +207,6 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec {
|
|||||||
"schema": map[string]string{"type": "string"},
|
"schema": map[string]string{"type": "string"},
|
||||||
"description": "Поисковый запрос",
|
"description": "Поисковый запрос",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "source",
|
|
||||||
"in": "query",
|
|
||||||
"required": true,
|
|
||||||
"schema": map[string]interface{}{"type": "string", "enum": []string{"kp", "tmdb"}},
|
|
||||||
"description": "Источник: kp или tmdb",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "page",
|
"name": "page",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
@@ -223,7 +216,7 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec {
|
|||||||
},
|
},
|
||||||
"responses": map[string]interface{}{
|
"responses": map[string]interface{}{
|
||||||
"200": map[string]interface{}{
|
"200": map[string]interface{}{
|
||||||
"description": "Результаты поиска (унифицированные)",
|
"description": "Результаты поиска",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,14 +36,7 @@ func (h *ImagesHandler) GetImage(w http.ResponseWriter, r *http.Request) {
|
|||||||
size = "original"
|
size = "original"
|
||||||
}
|
}
|
||||||
|
|
||||||
var imageURL string
|
imageURL := fmt.Sprintf("%s/%s/%s", config.TMDBImageBaseURL, size, imagePath)
|
||||||
if strings.HasPrefix(imagePath, "http://") || strings.HasPrefix(imagePath, "https://") {
|
|
||||||
// Проксируем внешний абсолютный URL (например, Kinopoisk)
|
|
||||||
imageURL = imagePath
|
|
||||||
} else {
|
|
||||||
// TMDB относительный путь
|
|
||||||
imageURL = fmt.Sprintf("%s/%s/%s", config.TMDBImageBaseURL, size, imagePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.Get(imageURL)
|
resp, err := http.Get(imageURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -39,10 +39,9 @@ func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if idType == "kinopoisk_id" { idType = "kp" }
|
|
||||||
if idType != "kp" && idType != "imdb" {
|
if idType != "kp" && idType != "imdb" {
|
||||||
log.Printf("Error: invalid id_type: %s", idType)
|
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' or 'imdb'", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,9 +165,6 @@ func (h *PlayersHandler) GetLumexPlayer(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Формируем запрос вида: https://portal.lumex.host/api/short?api_token=...&kinopoisk_id=...
|
|
||||||
// Ожидается, что LUMEX_URL уже содержит базовый URL и api_token, например:
|
|
||||||
// LUMEX_URL=https://portal.lumex.host/api/short?api_token=XXXX
|
|
||||||
var paramName string
|
var paramName string
|
||||||
if idType == "kp" {
|
if idType == "kp" {
|
||||||
paramName = "kinopoisk_id"
|
paramName = "kinopoisk_id"
|
||||||
@@ -176,11 +172,7 @@ func (h *PlayersHandler) GetLumexPlayer(w http.ResponseWriter, r *http.Request)
|
|||||||
paramName = "imdb_id"
|
paramName = "imdb_id"
|
||||||
}
|
}
|
||||||
|
|
||||||
separator := "&"
|
playerURL := fmt.Sprintf("%s?%s=%s", h.config.LumexURL, paramName, id)
|
||||||
if !strings.Contains(h.config.LumexURL, "?") {
|
|
||||||
separator = "?"
|
|
||||||
}
|
|
||||||
playerURL := fmt.Sprintf("%s% s%s=%s", h.config.LumexURL, separator, paramName, id)
|
|
||||||
log.Printf("Lumex URL: %s", playerURL)
|
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 loading="lazy" style="border:none;width:100%%;height:100%%;"></iframe>`, playerURL)
|
||||||
|
|||||||
@@ -1,228 +0,0 @@
|
|||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"neomovies-api/pkg/models"
|
|
||||||
"neomovies-api/pkg/services"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UnifiedHandler struct {
|
|
||||||
tmdb *services.TMDBService
|
|
||||||
kp *services.KinopoiskService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUnifiedHandler(tmdb *services.TMDBService, kp *services.KinopoiskService) *UnifiedHandler {
|
|
||||||
return &UnifiedHandler{tmdb: tmdb, kp: kp}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse source ID of form "kp_123" or "tmdb_456"
|
|
||||||
func parseSourceID(raw string) (source string, id int, err error) {
|
|
||||||
parts := strings.SplitN(raw, "_", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return "", 0, strconv.ErrSyntax
|
|
||||||
}
|
|
||||||
src := strings.ToLower(parts[0])
|
|
||||||
if src != "kp" && src != "tmdb" {
|
|
||||||
return "", 0, strconv.ErrSyntax
|
|
||||||
}
|
|
||||||
num, err := strconv.Atoi(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, err
|
|
||||||
}
|
|
||||||
return src, num, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *UnifiedHandler) GetMovie(w http.ResponseWriter, r *http.Request) {
|
|
||||||
start := time.Now()
|
|
||||||
vars := muxVars(r)
|
|
||||||
rawID := vars["id"]
|
|
||||||
|
|
||||||
source, id, err := parseSourceID(rawID)
|
|
||||||
if err != nil {
|
|
||||||
writeUnifiedError(w, http.StatusBadRequest, "invalid SOURCE_ID format", start, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
language := GetLanguage(r)
|
|
||||||
var data *models.UnifiedContent
|
|
||||||
if source == "kp" {
|
|
||||||
if h.kp == nil {
|
|
||||||
writeUnifiedError(w, http.StatusBadGateway, "Kinopoisk service not configured", start, source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
kpFilm, err := h.kp.GetFilmByKinopoiskId(id)
|
|
||||||
if err != nil {
|
|
||||||
writeUnifiedError(w, http.StatusBadGateway, err.Error(), start, source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data = services.MapKPToUnified(kpFilm)
|
|
||||||
// Обогащаем только externalIds.tmdb через /find (берем только поле id)
|
|
||||||
if kpFilm.ImdbId != "" {
|
|
||||||
if tmdbID, fErr := h.tmdb.FindTMDBIdByIMDB(kpFilm.ImdbId, "movie", GetLanguage(r)); fErr == nil {
|
|
||||||
data.ExternalIDs.TMDB = &tmdbID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// tmdb
|
|
||||||
movie, err := h.tmdb.GetMovie(id, language)
|
|
||||||
if err != nil {
|
|
||||||
writeUnifiedError(w, http.StatusBadGateway, err.Error(), start, source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ext, _ := h.tmdb.GetMovieExternalIDs(id)
|
|
||||||
data = services.MapTMDBToUnifiedMovie(movie, ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
writeUnifiedOK(w, data, start, source, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *UnifiedHandler) GetTV(w http.ResponseWriter, r *http.Request) {
|
|
||||||
start := time.Now()
|
|
||||||
vars := muxVars(r)
|
|
||||||
rawID := vars["id"]
|
|
||||||
|
|
||||||
source, id, err := parseSourceID(rawID)
|
|
||||||
if err != nil {
|
|
||||||
writeUnifiedError(w, http.StatusBadRequest, "invalid SOURCE_ID format", start, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
language := GetLanguage(r)
|
|
||||||
var data *models.UnifiedContent
|
|
||||||
if source == "kp" {
|
|
||||||
if h.kp == nil {
|
|
||||||
writeUnifiedError(w, http.StatusBadGateway, "Kinopoisk service not configured", start, source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
kpFilm, err := h.kp.GetFilmByKinopoiskId(id)
|
|
||||||
if err != nil {
|
|
||||||
writeUnifiedError(w, http.StatusBadGateway, err.Error(), start, source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data = services.MapKPToUnified(kpFilm)
|
|
||||||
if kpFilm.ImdbId != "" {
|
|
||||||
if tmdbID, fErr := h.tmdb.FindTMDBIdByIMDB(kpFilm.ImdbId, "tv", GetLanguage(r)); fErr == nil {
|
|
||||||
data.ExternalIDs.TMDB = &tmdbID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tv, err := h.tmdb.GetTVShow(id, language)
|
|
||||||
if err != nil {
|
|
||||||
writeUnifiedError(w, http.StatusBadGateway, err.Error(), start, source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ext, _ := h.tmdb.GetTVExternalIDs(id)
|
|
||||||
data = services.MapTMDBTVToUnified(tv, ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
writeUnifiedOK(w, data, start, source, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *UnifiedHandler) Search(w http.ResponseWriter, r *http.Request) {
|
|
||||||
start := time.Now()
|
|
||||||
query := r.URL.Query().Get("query")
|
|
||||||
if strings.TrimSpace(query) == "" {
|
|
||||||
writeUnifiedError(w, http.StatusBadRequest, "query is required", start, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
source := strings.ToLower(r.URL.Query().Get("source")) // kp|tmdb
|
|
||||||
page := getIntQuery(r, "page", 1)
|
|
||||||
language := GetLanguage(r)
|
|
||||||
|
|
||||||
if source != "kp" && source != "tmdb" {
|
|
||||||
writeUnifiedError(w, http.StatusBadRequest, "source must be 'kp' or 'tmdb'", start, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if source == "kp" {
|
|
||||||
if h.kp == nil {
|
|
||||||
writeUnifiedError(w, http.StatusBadGateway, "Kinopoisk service not configured", start, source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
kpSearch, err := h.kp.SearchFilms(query, page)
|
|
||||||
if err != nil {
|
|
||||||
writeUnifiedError(w, http.StatusBadGateway, err.Error(), start, source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
items := services.MapKPSearchToUnifiedItems(kpSearch)
|
|
||||||
// Обогащаем результаты поиска TMDB ID через получение полной информации о фильмах
|
|
||||||
if h.tmdb != nil {
|
|
||||||
for i := range items {
|
|
||||||
if kpID, err := strconv.Atoi(items[i].ID); err == nil {
|
|
||||||
if kpFilm, err := h.kp.GetFilmByKinopoiskId(kpID); err == nil && kpFilm.ImdbId != "" {
|
|
||||||
items[i].ExternalIDs.IMDb = kpFilm.ImdbId
|
|
||||||
mediaType := "movie"
|
|
||||||
if items[i].Type == "tv" {
|
|
||||||
mediaType = "tv"
|
|
||||||
}
|
|
||||||
if tmdbID, err := h.tmdb.FindTMDBIdByIMDB(kpFilm.ImdbId, mediaType, "ru-RU"); err == nil {
|
|
||||||
items[i].ExternalIDs.TMDB = &tmdbID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp := models.UnifiedSearchResponse{
|
|
||||||
Success: true,
|
|
||||||
Data: items,
|
|
||||||
Source: source,
|
|
||||||
Pagination: models.UnifiedPagination{Page: page, TotalPages: kpSearch.PagesCount, TotalResults: kpSearch.SearchFilmsCountResult, PageSize: len(items)},
|
|
||||||
Metadata: models.UnifiedMetadata{FetchedAt: time.Now(), APIVersion: "3.0", ResponseTime: time.Since(start).Milliseconds(), Query: query},
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TMDB multi search
|
|
||||||
multi, err := h.tmdb.SearchMulti(query, page, language)
|
|
||||||
if err != nil {
|
|
||||||
writeUnifiedError(w, http.StatusBadGateway, err.Error(), start, source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
items := services.MapTMDBMultiToUnifiedItems(multi)
|
|
||||||
resp := models.UnifiedSearchResponse{
|
|
||||||
Success: true,
|
|
||||||
Data: items,
|
|
||||||
Source: source,
|
|
||||||
Pagination: models.UnifiedPagination{Page: multi.Page, TotalPages: multi.TotalPages, TotalResults: multi.TotalResults, PageSize: len(items)},
|
|
||||||
Metadata: models.UnifiedMetadata{FetchedAt: time.Now(), APIVersion: "3.0", ResponseTime: time.Since(start).Milliseconds(), Query: query},
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeUnifiedOK(w http.ResponseWriter, data *models.UnifiedContent, start time.Time, source string, query string) {
|
|
||||||
resp := models.UnifiedAPIResponse{
|
|
||||||
Success: true,
|
|
||||||
Data: data,
|
|
||||||
Source: source,
|
|
||||||
Metadata: models.UnifiedMetadata{
|
|
||||||
FetchedAt: time.Now(),
|
|
||||||
APIVersion: "3.0",
|
|
||||||
ResponseTime: time.Since(start).Milliseconds(),
|
|
||||||
Query: query,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
json.NewEncoder(w).Encode(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeUnifiedError(w http.ResponseWriter, code int, message string, start time.Time, source string) {
|
|
||||||
resp := models.UnifiedAPIResponse{
|
|
||||||
Success: false,
|
|
||||||
Error: message,
|
|
||||||
Source: source,
|
|
||||||
Metadata: models.UnifiedMetadata{
|
|
||||||
FetchedAt: time.Now(),
|
|
||||||
APIVersion: "3.0",
|
|
||||||
ResponseTime: time.Since(start).Milliseconds(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(code)
|
|
||||||
json.NewEncoder(w).Encode(resp)
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func muxVars(r *http.Request) map[string]string { return mux.Vars(r) }
|
|
||||||
|
|
||||||
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(status)
|
|
||||||
_ = json.NewEncoder(w).Encode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
type metaEnvelope struct {
|
|
||||||
FetchedAt time.Time `json:"fetchedAt"`
|
|
||||||
APIVersion string `json:"apiVersion"`
|
|
||||||
ResponseTime int64 `json:"responseTime"`
|
|
||||||
}
|
|
||||||
@@ -123,7 +123,6 @@ type ExternalIDs struct {
|
|||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
IMDbID string `json:"imdb_id"`
|
IMDbID string `json:"imdb_id"`
|
||||||
KinopoiskID int `json:"kinopoisk_id,omitempty"`
|
KinopoiskID int `json:"kinopoisk_id,omitempty"`
|
||||||
TMDBID int `json:"tmdb_id,omitempty"`
|
|
||||||
TVDBID int `json:"tvdb_id,omitempty"`
|
TVDBID int `json:"tvdb_id,omitempty"`
|
||||||
WikidataID string `json:"wikidata_id"`
|
WikidataID string `json:"wikidata_id"`
|
||||||
FacebookID string `json:"facebook_id"`
|
FacebookID string `json:"facebook_id"`
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// Unified entities and response envelopes for prefixed-source API
|
|
||||||
|
|
||||||
type UnifiedGenre struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnifiedCastMember struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Character string `json:"character,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnifiedExternalIDs struct {
|
|
||||||
KP *int `json:"kp"`
|
|
||||||
TMDB *int `json:"tmdb"`
|
|
||||||
IMDb string `json:"imdb"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnifiedContent struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
SourceID string `json:"sourceId"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
OriginalTitle string `json:"originalTitle"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
ReleaseDate string `json:"releaseDate"`
|
|
||||||
EndDate *string `json:"endDate"`
|
|
||||||
Type string `json:"type"` // movie | tv
|
|
||||||
Genres []UnifiedGenre `json:"genres"`
|
|
||||||
Rating float64 `json:"rating"`
|
|
||||||
PosterURL string `json:"posterUrl"`
|
|
||||||
BackdropURL string `json:"backdropUrl"`
|
|
||||||
Director string `json:"director"`
|
|
||||||
Cast []UnifiedCastMember `json:"cast"`
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
Country string `json:"country"`
|
|
||||||
Language string `json:"language"`
|
|
||||||
Budget *int64 `json:"budget"`
|
|
||||||
Revenue *int64 `json:"revenue"`
|
|
||||||
IMDbID string `json:"imdbId"`
|
|
||||||
ExternalIDs UnifiedExternalIDs `json:"externalIds"`
|
|
||||||
// For TV shows
|
|
||||||
Seasons []UnifiedSeason `json:"seasons,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnifiedSeason struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
SourceID string `json:"sourceId"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
SeasonNumber int `json:"seasonNumber"`
|
|
||||||
EpisodeCount int `json:"episodeCount"`
|
|
||||||
ReleaseDate string `json:"releaseDate"`
|
|
||||||
PosterURL string `json:"posterUrl"`
|
|
||||||
Episodes []UnifiedEpisode `json:"episodes,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnifiedEpisode struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
SourceID string `json:"sourceId"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
EpisodeNumber int `json:"episodeNumber"`
|
|
||||||
SeasonNumber int `json:"seasonNumber"`
|
|
||||||
AirDate string `json:"airDate"`
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
StillURL string `json:"stillUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnifiedSearchItem struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
SourceID string `json:"sourceId"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
ReleaseDate string `json:"releaseDate"`
|
|
||||||
PosterURL string `json:"posterUrl"`
|
|
||||||
Rating float64 `json:"rating"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
ExternalIDs UnifiedExternalIDs `json:"externalIds"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnifiedPagination struct {
|
|
||||||
Page int `json:"page"`
|
|
||||||
TotalPages int `json:"totalPages"`
|
|
||||||
TotalResults int `json:"totalResults"`
|
|
||||||
PageSize int `json:"pageSize"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnifiedMetadata struct {
|
|
||||||
FetchedAt time.Time `json:"fetchedAt"`
|
|
||||||
APIVersion string `json:"apiVersion"`
|
|
||||||
ResponseTime int64 `json:"responseTime"`
|
|
||||||
Query string `json:"query,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnifiedAPIResponse struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Data interface{} `json:"data,omitempty"`
|
|
||||||
Error string `json:"error,omitempty"`
|
|
||||||
Source string `json:"source,omitempty"`
|
|
||||||
Metadata UnifiedMetadata `json:"metadata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UnifiedSearchResponse struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Data []UnifiedSearchItem `json:"data"`
|
|
||||||
Source string `json:"source"`
|
|
||||||
Pagination UnifiedPagination `json:"pagination"`
|
|
||||||
Metadata UnifiedMetadata `json:"metadata"`
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -140,7 +139,7 @@ func (s *KinopoiskService) GetFilmByKinopoiskId(id int) (*KPFilm, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *KinopoiskService) GetFilmByImdbId(imdbId string) (*KPFilm, error) {
|
func (s *KinopoiskService) GetFilmByImdbId(imdbId string) (*KPFilm, error) {
|
||||||
endpoint := fmt.Sprintf("%s/v2.2/films?imdbId=%s", s.baseURL, url.QueryEscape(imdbId))
|
endpoint := fmt.Sprintf("%s/v2.2/films?imdbId=%s", s.baseURL, imdbId)
|
||||||
|
|
||||||
var response struct {
|
var response struct {
|
||||||
Films []KPFilm `json:"items"`
|
Films []KPFilm `json:"items"`
|
||||||
@@ -159,7 +158,7 @@ func (s *KinopoiskService) GetFilmByImdbId(imdbId string) (*KPFilm, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *KinopoiskService) SearchFilms(keyword string, page int) (*KPSearchResponse, error) {
|
func (s *KinopoiskService) SearchFilms(keyword string, page int) (*KPSearchResponse, error) {
|
||||||
endpoint := fmt.Sprintf("%s/v2.1/films/search-by-keyword?keyword=%s&page=%d", s.baseURL, url.QueryEscape(keyword), page)
|
endpoint := fmt.Sprintf("%s/v2.1/films/search-by-keyword?keyword=%s&page=%d", s.baseURL, keyword, page)
|
||||||
var response KPSearchResponse
|
var response KPSearchResponse
|
||||||
err := s.makeRequest(endpoint, &response)
|
err := s.makeRequest(endpoint, &response)
|
||||||
return &response, err
|
return &response, err
|
||||||
|
|||||||
@@ -388,37 +388,3 @@ func FormatKPDate(year int) string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%d-01-01", year)
|
return fmt.Sprintf("%d-01-01", year)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnrichKPWithTMDBID обогащает KP контент TMDB ID через IMDB ID
|
|
||||||
func EnrichKPWithTMDBID(content *models.UnifiedContent, tmdbService *TMDBService) {
|
|
||||||
if content == nil || content.IMDbID == "" || content.ExternalIDs.TMDB != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaType := "movie"
|
|
||||||
if content.Type == "tv" {
|
|
||||||
mediaType = "tv"
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmdbID, err := tmdbService.FindTMDBIdByIMDB(content.IMDbID, mediaType, "ru-RU"); err == nil {
|
|
||||||
content.ExternalIDs.TMDB = &tmdbID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnrichKPSearchItemsWithTMDBID обогащает массив поисковых элементов TMDB ID
|
|
||||||
func EnrichKPSearchItemsWithTMDBID(items []models.UnifiedSearchItem, tmdbService *TMDBService) {
|
|
||||||
for i := range items {
|
|
||||||
if items[i].ExternalIDs.IMDb == "" || items[i].ExternalIDs.TMDB != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaType := "movie"
|
|
||||||
if items[i].Type == "tv" {
|
|
||||||
mediaType = "tv"
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmdbID, err := tmdbService.FindTMDBIdByIMDB(items[i].ExternalIDs.IMDb, mediaType, "ru-RU"); err == nil {
|
|
||||||
items[i].ExternalIDs.TMDB = &tmdbID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ func (s *MovieService) GetByID(id int, language string, idType string) (*models.
|
|||||||
|
|
||||||
// Сначала пробуем как Kinopoisk ID
|
// Сначала пробуем как Kinopoisk ID
|
||||||
if kpFilm, err := s.kpService.GetFilmByKinopoiskId(id); err == nil {
|
if kpFilm, err := s.kpService.GetFilmByKinopoiskId(id); err == nil {
|
||||||
// Возвращаем KP-модель в TMDB-формате без подмены на TMDB объект
|
|
||||||
return MapKPFilmToTMDBMovie(kpFilm), nil
|
return MapKPFilmToTMDBMovie(kpFilm), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,14 +107,6 @@ func (s *MovieService) GetExternalIDs(id int) (*models.ExternalIDs, error) {
|
|||||||
if err == nil && kpFilm != nil {
|
if err == nil && kpFilm != nil {
|
||||||
externalIDs := MapKPExternalIDsToTMDB(kpFilm)
|
externalIDs := MapKPExternalIDsToTMDB(kpFilm)
|
||||||
externalIDs.ID = id
|
externalIDs.ID = id
|
||||||
|
|
||||||
// Пытаемся получить TMDB ID через IMDB ID
|
|
||||||
if kpFilm.ImdbId != "" && s.tmdb != nil {
|
|
||||||
if tmdbID, tmdbErr := s.tmdb.FindTMDBIdByIMDB(kpFilm.ImdbId, "movie", "ru-RU"); tmdbErr == nil {
|
|
||||||
externalIDs.TMDBID = tmdbID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return externalIDs, nil
|
return externalIDs, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"neomovies-api/pkg/models"
|
"neomovies-api/pkg/models"
|
||||||
)
|
)
|
||||||
@@ -179,46 +180,78 @@ func (s *TMDBService) GetTVShow(id int, language string) (*models.TVShow, error)
|
|||||||
return &tvShow, err
|
return &tvShow, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindTMDBIdByIMDB finds TMDB IDs by external IMDb ID using /find/{external_id}
|
// Map TMDB movie to unified content with prefixed IDs. Requires optional external IDs for imdbId.
|
||||||
// media: "movie" | "tv" | "" (auto)
|
func MapTMDBToUnifiedMovie(movie *models.Movie, external *models.ExternalIDs) *models.UnifiedContent {
|
||||||
func (s *TMDBService) FindTMDBIdByIMDB(imdbID string, media string, language string) (int, error) {
|
if movie == nil {
|
||||||
if imdbID == "" {
|
return nil
|
||||||
return 0, fmt.Errorf("imdb id is empty")
|
|
||||||
}
|
|
||||||
if language == "" {
|
|
||||||
language = "ru-RU"
|
|
||||||
}
|
|
||||||
params := url.Values{}
|
|
||||||
params.Set("external_source", "imdb_id")
|
|
||||||
params.Set("language", language)
|
|
||||||
endpoint := fmt.Sprintf("%s/find/%s?%s", s.baseURL, url.PathEscape(imdbID), params.Encode())
|
|
||||||
|
|
||||||
var resp struct {
|
|
||||||
MovieResults []struct{ ID int `json:"id"` } `json:"movie_results"`
|
|
||||||
TVResults []struct{ ID int `json:"id"` } `json:"tv_results"`
|
|
||||||
}
|
|
||||||
if err := s.makeRequest(endpoint, &resp); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch media {
|
genres := make([]models.UnifiedGenre, 0, len(movie.Genres))
|
||||||
case "movie":
|
for _, g := range movie.Genres {
|
||||||
if len(resp.MovieResults) > 0 {
|
name := strings.TrimSpace(g.Name)
|
||||||
return resp.MovieResults[0].ID, nil
|
id := strings.ToLower(strings.ReplaceAll(name, " ", "-"))
|
||||||
|
if id == "" {
|
||||||
|
id = strconv.Itoa(g.ID)
|
||||||
}
|
}
|
||||||
case "tv":
|
genres = append(genres, models.UnifiedGenre{ID: id, Name: name})
|
||||||
if len(resp.TVResults) > 0 {
|
|
||||||
return resp.TVResults[0].ID, nil
|
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
if len(resp.MovieResults) > 0 {
|
var imdb string
|
||||||
return resp.MovieResults[0].ID, nil
|
if external != nil {
|
||||||
|
imdb = external.IMDbID
|
||||||
}
|
}
|
||||||
if len(resp.TVResults) > 0 {
|
|
||||||
return resp.TVResults[0].ID, nil
|
var budgetPtr *int64
|
||||||
|
if movie.Budget > 0 {
|
||||||
|
v := movie.Budget
|
||||||
|
budgetPtr = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
var revenuePtr *int64
|
||||||
|
if movie.Revenue > 0 {
|
||||||
|
v := movie.Revenue
|
||||||
|
revenuePtr = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := models.UnifiedExternalIDs{
|
||||||
|
KP: nil,
|
||||||
|
TMDB: &movie.ID,
|
||||||
|
IMDb: imdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.UnifiedContent{
|
||||||
|
ID: strconv.Itoa(movie.ID),
|
||||||
|
SourceID: "tmdb_" + strconv.Itoa(movie.ID),
|
||||||
|
Title: movie.Title,
|
||||||
|
OriginalTitle: movie.OriginalTitle,
|
||||||
|
Description: movie.Overview,
|
||||||
|
ReleaseDate: movie.ReleaseDate,
|
||||||
|
EndDate: nil,
|
||||||
|
Type: "movie",
|
||||||
|
Genres: genres,
|
||||||
|
Rating: movie.VoteAverage,
|
||||||
|
PosterURL: movie.PosterPath,
|
||||||
|
BackdropURL: movie.BackdropPath,
|
||||||
|
Director: "",
|
||||||
|
Cast: []models.UnifiedCastMember{},
|
||||||
|
Duration: movie.Runtime,
|
||||||
|
Country: firstCountry(movie.ProductionCountries),
|
||||||
|
Language: movie.OriginalLanguage,
|
||||||
|
Budget: budgetPtr,
|
||||||
|
Revenue: revenuePtr,
|
||||||
|
IMDbID: imdb,
|
||||||
|
ExternalIDs: ext,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0, fmt.Errorf("tmdb id not found for imdb %s", imdbID)
|
|
||||||
|
func firstCountry(countries []models.ProductionCountry) string {
|
||||||
|
if len(countries) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(countries[0].Name) != "" {
|
||||||
|
return countries[0].Name
|
||||||
|
}
|
||||||
|
return countries[0].ISO31661
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TMDBService) GetGenres(mediaType string, language string) (*models.GenresResponse, error) {
|
func (s *TMDBService) GetGenres(mediaType string, language string) (*models.GenresResponse, error) {
|
||||||
|
|||||||
@@ -35,27 +35,12 @@ func (s *TVService) GetByID(id int, language string, idType string) (*models.TVS
|
|||||||
|
|
||||||
// Сначала пробуем как Kinopoisk ID
|
// Сначала пробуем как Kinopoisk ID
|
||||||
if kpFilm, err := s.kpService.GetFilmByKinopoiskId(id); err == nil && kpFilm != nil {
|
if kpFilm, err := s.kpService.GetFilmByKinopoiskId(id); err == nil && kpFilm != nil {
|
||||||
// Попробуем обогатить TMDB сериал через IMDb -> TMDB find
|
|
||||||
if kpFilm.ImdbId != "" {
|
|
||||||
if tmdbID, fErr := s.tmdb.FindTMDBIdByIMDB(kpFilm.ImdbId, "tv", NormalizeLanguage(language)); fErr == nil {
|
|
||||||
if tmdbTV, mErr := s.tmdb.GetTVShow(tmdbID, NormalizeLanguage(language)); mErr == nil {
|
|
||||||
return tmdbTV, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MapKPFilmToTVShow(kpFilm), nil
|
return MapKPFilmToTVShow(kpFilm), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Возможно пришел TMDB ID — пробуем конвертировать TMDB -> KP
|
// Возможно пришел TMDB ID — пробуем конвертировать TMDB -> KP
|
||||||
if kpId, convErr := TmdbIdToKPId(s.tmdb, s.kpService, id); convErr == nil {
|
if kpId, convErr := TmdbIdToKPId(s.tmdb, s.kpService, id); convErr == nil {
|
||||||
if kpFilm, err := s.kpService.GetFilmByKinopoiskId(kpId); err == nil && kpFilm != nil {
|
if kpFilm, err := s.kpService.GetFilmByKinopoiskId(kpId); err == nil && kpFilm != nil {
|
||||||
if kpFilm.ImdbId != "" {
|
|
||||||
if tmdbID, fErr := s.tmdb.FindTMDBIdByIMDB(kpFilm.ImdbId, "tv", NormalizeLanguage(language)); fErr == nil {
|
|
||||||
if tmdbTV, mErr := s.tmdb.GetTVShow(tmdbID, NormalizeLanguage(language)); mErr == nil {
|
|
||||||
return tmdbTV, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MapKPFilmToTVShow(kpFilm), nil
|
return MapKPFilmToTVShow(kpFilm), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,34 +86,5 @@ func (s *TVService) GetSimilar(id, page int, language string) (*models.TMDBTVRes
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *TVService) GetExternalIDs(id int) (*models.ExternalIDs, error) {
|
func (s *TVService) GetExternalIDs(id int) (*models.ExternalIDs, error) {
|
||||||
if s.kpService != nil {
|
return s.tmdb.GetTVExternalIDs(id)
|
||||||
kpFilm, err := s.kpService.GetFilmByKinopoiskId(id)
|
|
||||||
if err == nil && kpFilm != nil {
|
|
||||||
externalIDs := MapKPExternalIDsToTMDB(kpFilm)
|
|
||||||
externalIDs.ID = id
|
|
||||||
|
|
||||||
// Пытаемся получить TMDB ID через IMDB ID
|
|
||||||
if kpFilm.ImdbId != "" && s.tmdb != nil {
|
|
||||||
if tmdbID, tmdbErr := s.tmdb.FindTMDBIdByIMDB(kpFilm.ImdbId, "tv", "ru-RU"); tmdbErr == nil {
|
|
||||||
externalIDs.TMDBID = tmdbID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return externalIDs, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tmdbIDs, err := s.tmdb.GetTVExternalIDs(id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.kpService != nil && tmdbIDs.IMDbID != "" {
|
|
||||||
kpFilm, err := s.kpService.GetFilmByImdbId(tmdbIDs.IMDbID)
|
|
||||||
if err == nil && kpFilm != nil {
|
|
||||||
tmdbIDs.KinopoiskID = kpFilm.KinopoiskId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tmdbIDs, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,266 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"neomovies-api/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
const tmdbImageBase = "https://image.tmdb.org/t/p"
|
|
||||||
|
|
||||||
func BuildTMDBImageURL(path string, size string) string {
|
|
||||||
if path == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
if size == "" {
|
|
||||||
size = "w500"
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(path, "/") {
|
|
||||||
path = "/" + path
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s/%s%s", tmdbImageBase, size, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MapTMDBToUnifiedMovie(movie *models.Movie, external *models.ExternalIDs) *models.UnifiedContent {
|
|
||||||
if movie == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
genres := make([]models.UnifiedGenre, 0, len(movie.Genres))
|
|
||||||
for _, g := range movie.Genres {
|
|
||||||
name := strings.TrimSpace(g.Name)
|
|
||||||
id := strings.ToLower(strings.ReplaceAll(name, " ", "-"))
|
|
||||||
if id == "" {
|
|
||||||
id = strconv.Itoa(g.ID)
|
|
||||||
}
|
|
||||||
genres = append(genres, models.UnifiedGenre{ID: id, Name: name})
|
|
||||||
}
|
|
||||||
|
|
||||||
var imdb string
|
|
||||||
if external != nil {
|
|
||||||
imdb = external.IMDbID
|
|
||||||
}
|
|
||||||
|
|
||||||
var budgetPtr *int64
|
|
||||||
if movie.Budget > 0 {
|
|
||||||
v := movie.Budget
|
|
||||||
budgetPtr = &v
|
|
||||||
}
|
|
||||||
var revenuePtr *int64
|
|
||||||
if movie.Revenue > 0 {
|
|
||||||
v := movie.Revenue
|
|
||||||
revenuePtr = &v
|
|
||||||
}
|
|
||||||
|
|
||||||
ext := models.UnifiedExternalIDs{
|
|
||||||
KP: nil,
|
|
||||||
TMDB: &movie.ID,
|
|
||||||
IMDb: imdb,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &models.UnifiedContent{
|
|
||||||
ID: strconv.Itoa(movie.ID),
|
|
||||||
SourceID: "tmdb_" + strconv.Itoa(movie.ID),
|
|
||||||
Title: movie.Title,
|
|
||||||
OriginalTitle: movie.OriginalTitle,
|
|
||||||
Description: movie.Overview,
|
|
||||||
ReleaseDate: movie.ReleaseDate,
|
|
||||||
EndDate: nil,
|
|
||||||
Type: "movie",
|
|
||||||
Genres: genres,
|
|
||||||
Rating: movie.VoteAverage,
|
|
||||||
PosterURL: BuildTMDBImageURL(movie.PosterPath, "w500"),
|
|
||||||
BackdropURL: BuildTMDBImageURL(movie.BackdropPath, "w1280"),
|
|
||||||
Director: "",
|
|
||||||
Cast: []models.UnifiedCastMember{},
|
|
||||||
Duration: movie.Runtime,
|
|
||||||
Country: firstCountry(movie.ProductionCountries),
|
|
||||||
Language: movie.OriginalLanguage,
|
|
||||||
Budget: budgetPtr,
|
|
||||||
Revenue: revenuePtr,
|
|
||||||
IMDbID: imdb,
|
|
||||||
ExternalIDs: ext,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func MapTMDBTVToUnified(tv *models.TVShow, external *models.ExternalIDs) *models.UnifiedContent {
|
|
||||||
if tv == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
genres := make([]models.UnifiedGenre, 0, len(tv.Genres))
|
|
||||||
for _, g := range tv.Genres {
|
|
||||||
name := strings.TrimSpace(g.Name)
|
|
||||||
id := strings.ToLower(strings.ReplaceAll(name, " ", "-"))
|
|
||||||
if id == "" {
|
|
||||||
id = strconv.Itoa(g.ID)
|
|
||||||
}
|
|
||||||
genres = append(genres, models.UnifiedGenre{ID: id, Name: name})
|
|
||||||
}
|
|
||||||
|
|
||||||
var imdb string
|
|
||||||
if external != nil {
|
|
||||||
imdb = external.IMDbID
|
|
||||||
}
|
|
||||||
|
|
||||||
endDate := (*string)(nil)
|
|
||||||
if strings.TrimSpace(tv.LastAirDate) != "" {
|
|
||||||
v := tv.LastAirDate
|
|
||||||
endDate = &v
|
|
||||||
}
|
|
||||||
|
|
||||||
ext := models.UnifiedExternalIDs{
|
|
||||||
KP: nil,
|
|
||||||
TMDB: &tv.ID,
|
|
||||||
IMDb: imdb,
|
|
||||||
}
|
|
||||||
|
|
||||||
duration := 0
|
|
||||||
if len(tv.EpisodeRunTime) > 0 {
|
|
||||||
duration = tv.EpisodeRunTime[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
unified := &models.UnifiedContent{
|
|
||||||
ID: strconv.Itoa(tv.ID),
|
|
||||||
SourceID: "tmdb_" + strconv.Itoa(tv.ID),
|
|
||||||
Title: tv.Name,
|
|
||||||
OriginalTitle: tv.OriginalName,
|
|
||||||
Description: tv.Overview,
|
|
||||||
ReleaseDate: tv.FirstAirDate,
|
|
||||||
EndDate: endDate,
|
|
||||||
Type: "tv",
|
|
||||||
Genres: genres,
|
|
||||||
Rating: tv.VoteAverage,
|
|
||||||
PosterURL: BuildTMDBImageURL(tv.PosterPath, "w500"),
|
|
||||||
BackdropURL: BuildTMDBImageURL(tv.BackdropPath, "w1280"),
|
|
||||||
Director: "",
|
|
||||||
Cast: []models.UnifiedCastMember{},
|
|
||||||
Duration: duration,
|
|
||||||
Country: firstCountry(tv.ProductionCountries),
|
|
||||||
Language: tv.OriginalLanguage,
|
|
||||||
Budget: nil,
|
|
||||||
Revenue: nil,
|
|
||||||
IMDbID: imdb,
|
|
||||||
ExternalIDs: ext,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map seasons basic info
|
|
||||||
if len(tv.Seasons) > 0 {
|
|
||||||
unified.Seasons = make([]models.UnifiedSeason, 0, len(tv.Seasons))
|
|
||||||
for _, s := range tv.Seasons {
|
|
||||||
unified.Seasons = append(unified.Seasons, models.UnifiedSeason{
|
|
||||||
ID: strconv.Itoa(s.ID),
|
|
||||||
SourceID: "tmdb_" + strconv.Itoa(s.ID),
|
|
||||||
Name: s.Name,
|
|
||||||
SeasonNumber: s.SeasonNumber,
|
|
||||||
EpisodeCount: s.EpisodeCount,
|
|
||||||
ReleaseDate: s.AirDate,
|
|
||||||
PosterURL: BuildTMDBImageURL(s.PosterPath, "w500"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return unified
|
|
||||||
}
|
|
||||||
|
|
||||||
func MapTMDBMultiToUnifiedItems(m *models.MultiSearchResponse) []models.UnifiedSearchItem {
|
|
||||||
if m == nil {
|
|
||||||
return []models.UnifiedSearchItem{}
|
|
||||||
}
|
|
||||||
items := make([]models.UnifiedSearchItem, 0, len(m.Results))
|
|
||||||
for _, r := range m.Results {
|
|
||||||
if r.MediaType != "movie" && r.MediaType != "tv" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
title := r.Title
|
|
||||||
if r.MediaType == "tv" {
|
|
||||||
title = r.Name
|
|
||||||
}
|
|
||||||
release := r.ReleaseDate
|
|
||||||
if r.MediaType == "tv" {
|
|
||||||
release = r.FirstAirDate
|
|
||||||
}
|
|
||||||
poster := BuildTMDBImageURL(r.PosterPath, "w500")
|
|
||||||
tmdbId := r.ID
|
|
||||||
items = append(items, models.UnifiedSearchItem{
|
|
||||||
ID: strconv.Itoa(tmdbId),
|
|
||||||
SourceID: "tmdb_" + strconv.Itoa(tmdbId),
|
|
||||||
Title: title,
|
|
||||||
Type: map[string]string{"movie":"movie","tv":"tv"}[r.MediaType],
|
|
||||||
ReleaseDate: release,
|
|
||||||
PosterURL: poster,
|
|
||||||
Rating: r.VoteAverage,
|
|
||||||
Description: r.Overview,
|
|
||||||
ExternalIDs: models.UnifiedExternalIDs{KP: nil, TMDB: &tmdbId, IMDb: ""},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func MapKPSearchToUnifiedItems(kps *KPSearchResponse) []models.UnifiedSearchItem {
|
|
||||||
if kps == nil {
|
|
||||||
return []models.UnifiedSearchItem{}
|
|
||||||
}
|
|
||||||
items := make([]models.UnifiedSearchItem, 0, len(kps.Films))
|
|
||||||
for _, f := range kps.Films {
|
|
||||||
title := f.NameRu
|
|
||||||
if strings.TrimSpace(title) == "" {
|
|
||||||
title = f.NameEn
|
|
||||||
}
|
|
||||||
poster := f.PosterUrlPreview
|
|
||||||
if poster == "" {
|
|
||||||
poster = f.PosterUrl
|
|
||||||
}
|
|
||||||
rating := 0.0
|
|
||||||
if strings.TrimSpace(f.Rating) != "" {
|
|
||||||
if v, err := strconv.ParseFloat(f.Rating, 64); err == nil {
|
|
||||||
rating = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
kpId := f.FilmId
|
|
||||||
items = append(items, models.UnifiedSearchItem{
|
|
||||||
ID: strconv.Itoa(kpId),
|
|
||||||
SourceID: "kp_" + strconv.Itoa(kpId),
|
|
||||||
Title: title,
|
|
||||||
Type: mapKPTypeToUnifiedShort(f.Type),
|
|
||||||
ReleaseDate: yearToDate(f.Year),
|
|
||||||
PosterURL: poster,
|
|
||||||
Rating: rating,
|
|
||||||
Description: f.Description,
|
|
||||||
ExternalIDs: models.UnifiedExternalIDs{KP: &kpId, TMDB: nil, IMDb: ""},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapKPTypeToUnifiedShort(t string) string {
|
|
||||||
switch strings.ToUpper(strings.TrimSpace(t)) {
|
|
||||||
case "TV_SERIES", "MINI_SERIES":
|
|
||||||
return "tv"
|
|
||||||
default:
|
|
||||||
return "movie"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func yearToDate(y string) string {
|
|
||||||
y = strings.TrimSpace(y)
|
|
||||||
if y == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return y + "-01-01"
|
|
||||||
}
|
|
||||||
|
|
||||||
func firstCountry(countries []models.ProductionCountry) string {
|
|
||||||
if len(countries) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(countries[0].Name) != "" {
|
|
||||||
return countries[0].Name
|
|
||||||
}
|
|
||||||
return countries[0].ISO31661
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user