Compare commits
12 Commits
b6be9488c9
...
f2427ee6c7
Author | SHA1 | Date | |
---|---|---|---|
f2427ee6c7 | |||
f8de3be779 | |||
7b960d6616 | |||
24324455da | |||
9488795186 | |||
18c749f9e1 | |||
960ddcfa05 | |||
04e3aee65b | |||
d59c269caa | |||
36eeb6b983 | |||
7737e278c1 | |||
451fea7355 |
@ -3,12 +3,12 @@ name: default
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: prepare-workdir
|
- name: prepare-workdir
|
||||||
image: alpine:3.13.1
|
image: alpine:3.14.3
|
||||||
commands:
|
commands:
|
||||||
- mkdir canapeapp
|
- mkdir canapeapp
|
||||||
|
|
||||||
- name: frontend
|
- name: frontend
|
||||||
image: node:13.8.0
|
image: node:16.6.2
|
||||||
commands:
|
commands:
|
||||||
- cd frontend
|
- cd frontend
|
||||||
- npm install
|
- npm install
|
||||||
@ -16,7 +16,7 @@ steps:
|
|||||||
- npm run-script build
|
- npm run-script build
|
||||||
|
|
||||||
- name: backend
|
- name: backend
|
||||||
image: golang:1.15.7-alpine3.13
|
image: golang:1.16.7-alpine3.14
|
||||||
commands:
|
commands:
|
||||||
- apk --no-cache add git
|
- apk --no-cache add git
|
||||||
- GO111MODULE=off go get -tags 'postgres' -u github.com/golang-migrate/migrate/cmd/migrate
|
- GO111MODULE=off go get -tags 'postgres' -u github.com/golang-migrate/migrate/cmd/migrate
|
||||||
@ -24,7 +24,7 @@ steps:
|
|||||||
- CGO_ENABLED=0 go build -ldflags '-extldflags "-static"' -trimpath -v -o canapeapp/app backend/*.go
|
- CGO_ENABLED=0 go build -ldflags '-extldflags "-static"' -trimpath -v -o canapeapp/app backend/*.go
|
||||||
|
|
||||||
- name: prepare-docker
|
- name: prepare-docker
|
||||||
image: alpine:3.13.1
|
image: alpine:3.14.3
|
||||||
commands:
|
commands:
|
||||||
- cp docker/run.sh canapeapp/run.sh
|
- cp docker/run.sh canapeapp/run.sh
|
||||||
- cp migrate canapeapp/migrate
|
- cp migrate canapeapp/migrate
|
||||||
|
@ -141,7 +141,7 @@ func GetShows(env *web.Env, user *models.User, source string, category string, f
|
|||||||
for _, id := range media.IDs {
|
for _, id := range media.IDs {
|
||||||
pShow, _ := pShows.Has(id)
|
pShow, _ := pShows.Has(id)
|
||||||
wShow, _ := wShows.IsShowInWishlist(id)
|
wShow, _ := wShows.IsShowInWishlist(id)
|
||||||
show := shows.NewWithClient(&polochon.Show{ImdbID: id}, client, pShow, wShow)
|
show := shows.NewWithClient(id, client, pShow, wShow)
|
||||||
|
|
||||||
// First check in the DB
|
// First check in the DB
|
||||||
before := []polochon.Detailer{env.Backend.Detailer}
|
before := []polochon.Detailer{env.Backend.Detailer}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
root: .
|
|
||||||
valid_ext: .go
|
|
||||||
colors: 1
|
|
||||||
build_name: dev-build
|
|
||||||
build_log: dev-build.log
|
|
||||||
tmp_path: ../build
|
|
@ -84,7 +84,6 @@ func FillEpisodeFromDB(eDB *episodeDB, pEpisode *polochon.ShowEpisode) {
|
|||||||
pEpisode.Title = eDB.Title
|
pEpisode.Title = eDB.Title
|
||||||
pEpisode.Rating = eDB.Rating
|
pEpisode.Rating = eDB.Rating
|
||||||
pEpisode.Plot = eDB.Plot
|
pEpisode.Plot = eDB.Plot
|
||||||
pEpisode.Thumb = eDB.Thumb
|
|
||||||
pEpisode.Runtime = eDB.Runtime
|
pEpisode.Runtime = eDB.Runtime
|
||||||
pEpisode.Aired = eDB.Aired
|
pEpisode.Aired = eDB.Aired
|
||||||
pEpisode.Thumb = imageURL(fmt.Sprintf(
|
pEpisode.Thumb = imageURL(fmt.Sprintf(
|
||||||
|
@ -90,6 +90,7 @@ func FillMovieFromDB(mDB *movieDB, pMovie *polochon.Movie) {
|
|||||||
pMovie.SortTitle = mDB.SortTitle
|
pMovie.SortTitle = mDB.SortTitle
|
||||||
pMovie.Tagline = mDB.Tagline
|
pMovie.Tagline = mDB.Tagline
|
||||||
pMovie.Thumb = imageURL("movies/" + mDB.ImdbID + ".jpg")
|
pMovie.Thumb = imageURL("movies/" + mDB.ImdbID + ".jpg")
|
||||||
|
pMovie.Fanart = imageURL("movies/" + mDB.ImdbID + "-fanart.jpg")
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateFromMovie will update the movieDB from a Movie
|
// updateFromMovie will update the movieDB from a Movie
|
||||||
|
@ -9,7 +9,8 @@ import (
|
|||||||
// TorrentVideo reprensents a torrent embeding the video inforamtions
|
// TorrentVideo reprensents a torrent embeding the video inforamtions
|
||||||
type TorrentVideo struct {
|
type TorrentVideo struct {
|
||||||
*polochon.Torrent
|
*polochon.Torrent
|
||||||
Img string `json:"img"`
|
Thumb string `json:"thumb"`
|
||||||
|
Fanart string `json:"fanart"`
|
||||||
Video polochon.Video `json:"video,omitempty"`
|
Video polochon.Video `json:"video,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,11 +48,13 @@ func (t *TorrentVideo) Update(detailer polochon.Detailer, db *sqlx.DB, log *logr
|
|||||||
if err := GetShow(db, v.Show); err != nil {
|
if err := GetShow(db, v.Show); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.Img = v.Show.Poster
|
t.Fanart = v.Show.Fanart
|
||||||
|
t.Thumb = v.Show.Poster
|
||||||
v.Show = nil
|
v.Show = nil
|
||||||
}
|
}
|
||||||
case *polochon.Movie:
|
case *polochon.Movie:
|
||||||
t.Img = v.Thumb
|
t.Thumb = v.Thumb
|
||||||
|
t.Fanart = v.Fanart
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,20 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
// Modules
|
// Modules
|
||||||
_ "github.com/odwrtw/polochon/modules/addicted"
|
|
||||||
_ "github.com/odwrtw/polochon/modules/canape"
|
_ "github.com/odwrtw/polochon/modules/canape"
|
||||||
_ "github.com/odwrtw/polochon/modules/eztv"
|
_ "github.com/odwrtw/polochon/modules/eztv"
|
||||||
_ "github.com/odwrtw/polochon/modules/fsnotify"
|
|
||||||
_ "github.com/odwrtw/polochon/modules/imdb"
|
|
||||||
_ "github.com/odwrtw/polochon/modules/mock"
|
_ "github.com/odwrtw/polochon/modules/mock"
|
||||||
_ "github.com/odwrtw/polochon/modules/openguessit"
|
|
||||||
_ "github.com/odwrtw/polochon/modules/opensubtitles"
|
|
||||||
_ "github.com/odwrtw/polochon/modules/pushover"
|
|
||||||
_ "github.com/odwrtw/polochon/modules/tmdb"
|
_ "github.com/odwrtw/polochon/modules/tmdb"
|
||||||
_ "github.com/odwrtw/polochon/modules/tpb"
|
_ "github.com/odwrtw/polochon/modules/tpb"
|
||||||
_ "github.com/odwrtw/polochon/modules/trakttv"
|
_ "github.com/odwrtw/polochon/modules/trakttv"
|
||||||
_ "github.com/odwrtw/polochon/modules/transmission"
|
|
||||||
_ "github.com/odwrtw/polochon/modules/tvdb"
|
_ "github.com/odwrtw/polochon/modules/tvdb"
|
||||||
_ "github.com/odwrtw/polochon/modules/yifysubtitles"
|
|
||||||
_ "github.com/odwrtw/polochon/modules/yts"
|
_ "github.com/odwrtw/polochon/modules/yts"
|
||||||
)
|
)
|
||||||
|
@ -49,6 +49,51 @@ func PolochonMoviesHandler(env *web.Env, w http.ResponseWriter, r *http.Request)
|
|||||||
return env.RenderJSON(w, movies)
|
return env.RenderJSON(w, movies)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMovieHandler will return a single movie
|
||||||
|
func GetMovieHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id := vars["id"]
|
||||||
|
|
||||||
|
user := auth.GetCurrentUser(r, env.Log)
|
||||||
|
|
||||||
|
client, err := user.NewPapiClient(env.Database)
|
||||||
|
if err != nil {
|
||||||
|
return env.RenderError(w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
movies, err := client.GetMovies()
|
||||||
|
if err != nil {
|
||||||
|
return env.RenderError(w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
moviesWishlist, err := models.GetMovieWishlist(env.Database, user.ID)
|
||||||
|
if err != nil {
|
||||||
|
return env.RenderError(w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pMovie, _ := movies.Has(id)
|
||||||
|
movie := New(
|
||||||
|
id,
|
||||||
|
client,
|
||||||
|
pMovie,
|
||||||
|
moviesWishlist.IsMovieInWishlist(id),
|
||||||
|
)
|
||||||
|
|
||||||
|
detailers := []polochon.Detailer{env.Backend.Detailer}
|
||||||
|
err = movie.GetDetails(env, detailers)
|
||||||
|
if err != nil {
|
||||||
|
return env.RenderError(w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
torrenters := []polochon.Torrenter{env.Backend.Torrenter}
|
||||||
|
err = movie.GetTorrents(env, torrenters)
|
||||||
|
if err != nil {
|
||||||
|
env.Log.Errorf("error while getting movie torrents : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return env.RenderJSON(w, movie)
|
||||||
|
}
|
||||||
|
|
||||||
// RefreshMovieHandler refreshes details for a movie
|
// RefreshMovieHandler refreshes details for a movie
|
||||||
func RefreshMovieHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
func RefreshMovieHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
@ -283,6 +328,7 @@ func GetWishlistHandler(env *web.Env, w http.ResponseWriter, r *http.Request) er
|
|||||||
func RefreshMovieSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
func RefreshMovieSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
lang := polochon.Language(vars["lang"])
|
||||||
|
|
||||||
// Get the user
|
// Get the user
|
||||||
user := auth.GetCurrentUser(r, env.Log)
|
user := auth.GetCurrentUser(r, env.Log)
|
||||||
@ -294,29 +340,35 @@ func RefreshMovieSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
movie := &papi.Movie{Movie: &polochon.Movie{ImdbID: id}}
|
movie := &papi.Movie{Movie: &polochon.Movie{ImdbID: id}}
|
||||||
refreshSubs, err := client.UpdateSubtitles(movie)
|
sub, err := client.UpdateSubtitle(movie, lang)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
subs := []subtitles.Subtitle{}
|
// TODO: handle this with a better error
|
||||||
for _, lang := range refreshSubs {
|
if sub == nil {
|
||||||
subtitleURL, _ := client.SubtitleURL(movie, lang)
|
return env.RenderJSON(w, nil)
|
||||||
subs = append(subs, subtitles.Subtitle{
|
|
||||||
Language: lang,
|
|
||||||
URL: subtitleURL,
|
|
||||||
VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", id, lang),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return env.RenderJSON(w, subs)
|
url, err := client.DownloadURLWithToken(sub)
|
||||||
|
if err != nil {
|
||||||
|
return env.RenderError(w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &subtitles.Subtitle{
|
||||||
|
Subtitle: sub.Subtitle,
|
||||||
|
URL: url,
|
||||||
|
VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", id, sub.Lang),
|
||||||
|
}
|
||||||
|
|
||||||
|
return env.RenderJSON(w, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloadVVTSubtitle returns a vvt subtitle for the movie
|
// DownloadVVTSubtitle returns a vvt subtitle for the movie
|
||||||
func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
lang := vars["lang"]
|
lang := polochon.Language(vars["lang"])
|
||||||
|
|
||||||
// Get the user
|
// Get the user
|
||||||
user := auth.GetCurrentUser(r, env.Log)
|
user := auth.GetCurrentUser(r, env.Log)
|
||||||
@ -327,7 +379,14 @@ func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) e
|
|||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
url, err := client.SubtitleURL(&papi.Movie{Movie: &polochon.Movie{ImdbID: id}}, lang)
|
s := &papi.Subtitle{
|
||||||
|
Subtitle: &polochon.Subtitle{
|
||||||
|
Video: &papi.Movie{Movie: &polochon.Movie{ImdbID: id}},
|
||||||
|
Lang: lang,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := client.DownloadURLWithToken(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ func (m *Movie) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
if m.pMovie != nil {
|
if m.pMovie != nil {
|
||||||
// Get the DownloadURL
|
// Get the DownloadURL
|
||||||
movieToMarshal.PolochonURL, _ = m.client.DownloadURL(m.pMovie)
|
movieToMarshal.PolochonURL, _ = m.client.DownloadURLWithToken(m.pMovie)
|
||||||
|
|
||||||
// Get the metadata
|
// Get the metadata
|
||||||
movieToMarshal.DateAdded = m.pMovie.DateAdded
|
movieToMarshal.DateAdded = m.pMovie.DateAdded
|
||||||
@ -58,13 +58,14 @@ func (m *Movie) MarshalJSON() ([]byte, error) {
|
|||||||
movieToMarshal.Container = m.pMovie.Container
|
movieToMarshal.Container = m.pMovie.Container
|
||||||
|
|
||||||
// Append the Subtitles
|
// Append the Subtitles
|
||||||
for _, l := range m.pMovie.Subtitles {
|
for _, s := range m.pMovie.Subtitles {
|
||||||
subtitleURL, _ := m.client.SubtitleURL(m.pMovie, l)
|
sub := subtitles.Subtitle{Subtitle: s.Subtitle}
|
||||||
movieToMarshal.Subtitles = append(movieToMarshal.Subtitles, subtitles.Subtitle{
|
if !sub.Embedded {
|
||||||
Language: l,
|
subtitleURL, _ := m.client.DownloadURLWithToken(s)
|
||||||
URL: subtitleURL,
|
sub.URL = subtitleURL
|
||||||
VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", m.ImdbID, l),
|
sub.VVTFile = fmt.Sprintf("/movies/%s/subtitles/%s", m.ImdbID, s.Lang)
|
||||||
})
|
}
|
||||||
|
movieToMarshal.Subtitles = append(movieToMarshal.Subtitles, sub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,13 +74,18 @@ func (m *Movie) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
// New returns a new Movie with all the needed infos
|
// New returns a new Movie with all the needed infos
|
||||||
func New(imdbID string, client *papi.Client, pMovie *papi.Movie, isWishlisted bool) *Movie {
|
func New(imdbID string, client *papi.Client, pMovie *papi.Movie, isWishlisted bool) *Movie {
|
||||||
|
var m *polochon.Movie
|
||||||
|
if pMovie != nil && pMovie.Movie != nil {
|
||||||
|
m = pMovie.Movie
|
||||||
|
} else {
|
||||||
|
m = &polochon.Movie{ImdbID: imdbID}
|
||||||
|
}
|
||||||
|
|
||||||
return &Movie{
|
return &Movie{
|
||||||
client: client,
|
client: client,
|
||||||
pMovie: pMovie,
|
pMovie: pMovie,
|
||||||
Wishlisted: isWishlisted,
|
Wishlisted: isWishlisted,
|
||||||
Movie: &polochon.Movie{
|
Movie: m,
|
||||||
ImdbID: imdbID,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +150,13 @@ func (m *Movie) Refresh(env *web.Env, detailers []polochon.Detailer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Download poster
|
// Download poster
|
||||||
err = web.Download(m.Thumb, m.imgFile(), true)
|
err = web.Download(m.Thumb, m.imgFile("thumb"), 300)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("got error trying to download the poster %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download fanart
|
||||||
|
err = web.Download(m.Fanart, m.imgFile("fanart"), 960)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("got error trying to download the poster %q", err)
|
log.Errorf("got error trying to download the poster %q", err)
|
||||||
}
|
}
|
||||||
@ -231,13 +243,19 @@ func (m *Movie) RefreshTorrents(env *web.Env, torrenters []polochon.Torrenter) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// imgURL returns the default image url
|
// imgURL returns the default image url
|
||||||
func (m *Movie) imgURL() string {
|
func (m *Movie) imgURL(imgType string) string {
|
||||||
return fmt.Sprintf("movies/%s.jpg", m.ImdbID)
|
var location string
|
||||||
|
if imgType == "thumb" {
|
||||||
|
location = m.ImdbID
|
||||||
|
} else {
|
||||||
|
location = m.ImdbID + "-" + imgType
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("movies/%s.jpg", location)
|
||||||
}
|
}
|
||||||
|
|
||||||
// imgFile returns the image location on disk
|
// imgFile returns the image location on disk
|
||||||
func (m *Movie) imgFile() string {
|
func (m *Movie) imgFile(imgType string) string {
|
||||||
return filepath.Join(models.PublicDir, "img", m.imgURL())
|
return filepath.Join(models.PublicDir, "img", m.imgURL(imgType))
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPolochonMovies returns an array of the user's polochon movies
|
// getPolochonMovies returns an array of the user's polochon movies
|
||||||
|
@ -41,9 +41,10 @@ func setupRoutes(env *web.Env) {
|
|||||||
env.Handle("/movies/explore/options", extmedias.MovieExplorerOptions).WithRole(models.UserRole).Methods("GET")
|
env.Handle("/movies/explore/options", extmedias.MovieExplorerOptions).WithRole(models.UserRole).Methods("GET")
|
||||||
env.Handle("/movies/search/{search}", movies.SearchMovie).WithRole(models.UserRole).Methods("GET")
|
env.Handle("/movies/search/{search}", movies.SearchMovie).WithRole(models.UserRole).Methods("GET")
|
||||||
env.Handle("/movies/{id:tt[0-9]+}", movies.PolochonDeleteHandler).WithRole(models.UserRole).Methods("DELETE")
|
env.Handle("/movies/{id:tt[0-9]+}", movies.PolochonDeleteHandler).WithRole(models.UserRole).Methods("DELETE")
|
||||||
|
env.Handle("/movies/{id:tt[0-9]+}", movies.GetMovieHandler).WithRole(models.UserRole).Methods("GET")
|
||||||
env.Handle("/movies/{id:tt[0-9]+}/refresh", movies.RefreshMovieHandler).WithRole(models.UserRole).Methods("POST")
|
env.Handle("/movies/{id:tt[0-9]+}/refresh", movies.RefreshMovieHandler).WithRole(models.UserRole).Methods("POST")
|
||||||
env.Handle("/movies/{id:tt[0-9]+}/subtitles/{lang}", movies.DownloadVVTSubtitle).WithRole(models.UserRole).Methods("GET")
|
env.Handle("/movies/{id:tt[0-9]+}/subtitles/{lang}", movies.DownloadVVTSubtitle).WithRole(models.UserRole).Methods("GET")
|
||||||
env.Handle("/movies/{id:tt[0-9]+}/subtitles/refresh", movies.RefreshMovieSubtitlesHandler).WithRole(models.UserRole).Methods("POST")
|
env.Handle("/movies/{id:tt[0-9]+}/subtitles/{lang}", movies.RefreshMovieSubtitlesHandler).WithRole(models.UserRole).Methods("POST")
|
||||||
env.Handle("/movies/refresh", extmedias.RefreshMoviesHandler).WithRole(models.AdminRole).Methods("POST")
|
env.Handle("/movies/refresh", extmedias.RefreshMoviesHandler).WithRole(models.AdminRole).Methods("POST")
|
||||||
|
|
||||||
// Shows routes
|
// Shows routes
|
||||||
@ -54,7 +55,7 @@ func setupRoutes(env *web.Env) {
|
|||||||
env.Handle("/shows/{id:tt[0-9]+}", shows.GetDetailsHandler).WithRole(models.UserRole).Methods("GET")
|
env.Handle("/shows/{id:tt[0-9]+}", shows.GetDetailsHandler).WithRole(models.UserRole).Methods("GET")
|
||||||
env.Handle("/shows/{id:tt[0-9]+}/refresh", shows.RefreshShowHandler).WithRole(models.UserRole).Methods("POST")
|
env.Handle("/shows/{id:tt[0-9]+}/refresh", shows.RefreshShowHandler).WithRole(models.UserRole).Methods("POST")
|
||||||
env.Handle("/shows/{id:tt[0-9]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}", shows.RefreshEpisodeHandler).WithRole(models.UserRole).Methods("POST")
|
env.Handle("/shows/{id:tt[0-9]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}", shows.RefreshEpisodeHandler).WithRole(models.UserRole).Methods("POST")
|
||||||
env.Handle("/shows/{id:tt[0-9]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}/subtitles/refresh", shows.RefreshEpisodeSubtitlesHandler).WithRole(models.UserRole).Methods("POST")
|
env.Handle("/shows/{id:tt[0-9]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}/subtitles/{lang}", shows.RefreshEpisodeSubtitlesHandler).WithRole(models.UserRole).Methods("POST")
|
||||||
env.Handle("/shows/{id:tt[0-9]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}/subtitles/{lang}", shows.DownloadVVTSubtitle).WithRole(models.UserRole).Methods("GET")
|
env.Handle("/shows/{id:tt[0-9]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}/subtitles/{lang}", shows.DownloadVVTSubtitle).WithRole(models.UserRole).Methods("GET")
|
||||||
env.Handle("/shows/refresh", extmedias.RefreshShowsHandler).WithRole(models.AdminRole).Methods("POST")
|
env.Handle("/shows/refresh", extmedias.RefreshShowsHandler).WithRole(models.AdminRole).Methods("POST")
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ package shows
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.quimbo.fr/odwrtw/canape/backend/models"
|
"git.quimbo.fr/odwrtw/canape/backend/models"
|
||||||
"git.quimbo.fr/odwrtw/canape/backend/subtitles"
|
"git.quimbo.fr/odwrtw/canape/backend/subtitles"
|
||||||
@ -26,18 +25,13 @@ func (e *Episode) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
var downloadURL string
|
var downloadURL string
|
||||||
var subs []subtitles.Subtitle
|
var subs []subtitles.Subtitle
|
||||||
var dateAdded time.Time
|
|
||||||
var quality string
|
|
||||||
var audioCodec string
|
|
||||||
var videoCodec string
|
|
||||||
var container string
|
|
||||||
|
|
||||||
// If the episode is present, fill the downloadURL
|
// If the episode is present, fill the downloadURL
|
||||||
if e.show.pShow != nil {
|
if e.show.pShow != nil {
|
||||||
pEpisode := e.show.pShow.GetEpisode(e.Season, e.Episode)
|
pEpisode := e.show.pShow.GetEpisode(e.Season, e.Episode)
|
||||||
if pEpisode != nil {
|
if pEpisode != nil {
|
||||||
// Get the DownloadURL
|
// Get the DownloadURL
|
||||||
downloadURL, _ = e.show.client.DownloadURL(
|
downloadURL, _ = e.show.client.DownloadURLWithToken(
|
||||||
&papi.Episode{
|
&papi.Episode{
|
||||||
ShowEpisode: &polochon.ShowEpisode{
|
ShowEpisode: &polochon.ShowEpisode{
|
||||||
ShowImdbID: e.ShowImdbID,
|
ShowImdbID: e.ShowImdbID,
|
||||||
@ -46,20 +40,21 @@ func (e *Episode) MarshalJSON() ([]byte, error) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
dateAdded = pEpisode.DateAdded
|
|
||||||
quality = string(pEpisode.Quality)
|
e.ShowEpisode.VideoMetadata = pEpisode.ShowEpisode.VideoMetadata
|
||||||
audioCodec = pEpisode.AudioCodec
|
e.ShowEpisode.File = pEpisode.ShowEpisode.File
|
||||||
videoCodec = pEpisode.VideoCodec
|
|
||||||
container = pEpisode.Container
|
|
||||||
|
|
||||||
// Append the Subtitles
|
// Append the Subtitles
|
||||||
for _, l := range pEpisode.Subtitles {
|
for _, s := range pEpisode.Subtitles {
|
||||||
subtitleURL, _ := e.show.client.SubtitleURL(pEpisode, l)
|
sub := subtitles.Subtitle{Subtitle: s.Subtitle}
|
||||||
subs = append(subs, subtitles.Subtitle{
|
if !sub.Embedded {
|
||||||
Language: l,
|
subtitleURL, _ := e.show.client.DownloadURLWithToken(s)
|
||||||
URL: subtitleURL,
|
sub.URL = subtitleURL
|
||||||
VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, l),
|
sub.VVTFile = fmt.Sprintf(
|
||||||
})
|
"/shows/%s/seasons/%d/episodes/%d/subtitles/%s",
|
||||||
|
e.ShowImdbID, e.Season, e.Episode, s.Lang)
|
||||||
|
}
|
||||||
|
subs = append(subs, sub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,21 +64,11 @@ func (e *Episode) MarshalJSON() ([]byte, error) {
|
|||||||
*alias
|
*alias
|
||||||
PolochonURL string `json:"polochon_url"`
|
PolochonURL string `json:"polochon_url"`
|
||||||
Subtitles []subtitles.Subtitle `json:"subtitles"`
|
Subtitles []subtitles.Subtitle `json:"subtitles"`
|
||||||
DateAdded time.Time `json:"date_added"`
|
|
||||||
Quality string `json:"quality"`
|
|
||||||
AudioCodec string `json:"audio_codec"`
|
|
||||||
VideoCodec string `json:"video_codec"`
|
|
||||||
Container string `json:"container"`
|
|
||||||
Thumb string `json:"thumb"`
|
Thumb string `json:"thumb"`
|
||||||
}{
|
}{
|
||||||
alias: (*alias)(e),
|
alias: (*alias)(e),
|
||||||
PolochonURL: downloadURL,
|
PolochonURL: downloadURL,
|
||||||
Subtitles: subs,
|
Subtitles: subs,
|
||||||
DateAdded: dateAdded,
|
|
||||||
Quality: quality,
|
|
||||||
AudioCodec: audioCodec,
|
|
||||||
VideoCodec: videoCodec,
|
|
||||||
Container: container,
|
|
||||||
Thumb: e.Thumb,
|
Thumb: e.Thumb,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -35,15 +34,15 @@ func GetDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) err
|
|||||||
|
|
||||||
pShow, err := client.GetShow(id)
|
pShow, err := client.GetShow(id)
|
||||||
if err != nil && err != papi.ErrResourceNotFound {
|
if err != nil && err != papi.ErrResourceNotFound {
|
||||||
log.Println("Got error getting show ", err)
|
env.Log.Println("Got error getting show ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wShow, err := models.IsShowWishlisted(env.Database, user.ID, id)
|
wShow, err := models.IsShowWishlisted(env.Database, user.ID, id)
|
||||||
if err != nil && err != papi.ErrResourceNotFound {
|
if err != nil && err != papi.ErrResourceNotFound {
|
||||||
log.Println("Got error getting wishlisted show ", err)
|
env.Log.Println("Got error getting wishlisted show ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := NewWithClient(&polochon.Show{ImdbID: id}, client, pShow, wShow)
|
s := NewWithClient(id, client, pShow, wShow)
|
||||||
// First try from the db
|
// First try from the db
|
||||||
first := []polochon.Detailer{env.Backend.Detailer}
|
first := []polochon.Detailer{env.Backend.Detailer}
|
||||||
// Then try from the polochon detailers
|
// Then try from the polochon detailers
|
||||||
@ -81,15 +80,15 @@ func RefreshShowHandler(env *web.Env, w http.ResponseWriter, r *http.Request) er
|
|||||||
|
|
||||||
pShow, err := client.GetShow(id)
|
pShow, err := client.GetShow(id)
|
||||||
if err != nil && err != papi.ErrResourceNotFound {
|
if err != nil && err != papi.ErrResourceNotFound {
|
||||||
log.Println("Got error getting show ", err)
|
env.Log.Println("Got error getting show ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wShow, err := models.IsShowWishlisted(env.Database, user.ID, id)
|
wShow, err := models.IsShowWishlisted(env.Database, user.ID, id)
|
||||||
if err != nil && err != papi.ErrResourceNotFound {
|
if err != nil && err != papi.ErrResourceNotFound {
|
||||||
log.Println("Got error getting wishlisted show ", err)
|
env.Log.Println("Got error getting wishlisted show ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := NewWithClient(&polochon.Show{ImdbID: id}, client, pShow, wShow)
|
s := NewWithClient(id, client, pShow, wShow)
|
||||||
// Refresh the polochon detailers
|
// Refresh the polochon detailers
|
||||||
detailers := env.Config.Show.Detailers
|
detailers := env.Config.Show.Detailers
|
||||||
err = s.Refresh(env, detailers)
|
err = s.Refresh(env, detailers)
|
||||||
@ -163,7 +162,7 @@ func SearchShow(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
for _, s := range shows {
|
for _, s := range shows {
|
||||||
pShow, _ := pShows.Has(s.ImdbID)
|
pShow, _ := pShows.Has(s.ImdbID)
|
||||||
wShow, _ := wShows.IsShowInWishlist(s.ImdbID)
|
wShow, _ := wShows.IsShowInWishlist(s.ImdbID)
|
||||||
show := NewWithClient(s, client, pShow, wShow)
|
show := NewWithClient(s.ImdbID, client, pShow, wShow)
|
||||||
|
|
||||||
// First try from the db
|
// First try from the db
|
||||||
first := []polochon.Detailer{env.Backend.Detailer}
|
first := []polochon.Detailer{env.Backend.Detailer}
|
||||||
@ -242,8 +241,7 @@ func GetWishlistHandler(env *web.Env, w http.ResponseWriter, r *http.Request) er
|
|||||||
showList := []*Show{}
|
showList := []*Show{}
|
||||||
for _, wishedShow := range wShows.List() {
|
for _, wishedShow := range wShows.List() {
|
||||||
pShow, _ := pShows.Has(wishedShow.ImdbID)
|
pShow, _ := pShows.Has(wishedShow.ImdbID)
|
||||||
poloShow := &polochon.Show{ImdbID: wishedShow.ImdbID}
|
show := NewWithClient(wishedShow.ImdbID, client, pShow, wishedShow)
|
||||||
show := NewWithClient(poloShow, client, pShow, wishedShow)
|
|
||||||
|
|
||||||
// First check in the DB
|
// First check in the DB
|
||||||
before := []polochon.Detailer{env.Backend.Detailer}
|
before := []polochon.Detailer{env.Backend.Detailer}
|
||||||
@ -309,7 +307,7 @@ func RefreshEpisodeHandler(env *web.Env, w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
s := &Show{
|
s := &Show{
|
||||||
Show: &polochon.Show{ImdbID: id},
|
Show: pShow.Show,
|
||||||
client: client,
|
client: client,
|
||||||
pShow: pShow,
|
pShow: pShow,
|
||||||
}
|
}
|
||||||
@ -343,6 +341,7 @@ func RefreshEpisodeHandler(env *web.Env, w http.ResponseWriter, r *http.Request)
|
|||||||
func RefreshEpisodeSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
func RefreshEpisodeSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
lang := polochon.Language(vars["lang"])
|
||||||
|
|
||||||
// No need to check errors here as the router is making sure that season
|
// No need to check errors here as the router is making sure that season
|
||||||
// and episode are numbers
|
// and episode are numbers
|
||||||
@ -366,29 +365,35 @@ func RefreshEpisodeSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshedSubs, err := client.UpdateSubtitles(e)
|
sub, err := client.UpdateSubtitle(e, lang)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
subs := []subtitles.Subtitle{}
|
// TODO: handle this with a better error
|
||||||
for _, lang := range refreshedSubs {
|
if sub == nil {
|
||||||
subtitleURL, _ := client.SubtitleURL(e, lang)
|
return env.RenderJSON(w, nil)
|
||||||
subs = append(subs, subtitles.Subtitle{
|
|
||||||
Language: lang,
|
|
||||||
URL: subtitleURL,
|
|
||||||
VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, lang),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return env.RenderJSON(w, subs)
|
url, err := client.DownloadURL(sub)
|
||||||
|
if err != nil {
|
||||||
|
return env.RenderError(w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &subtitles.Subtitle{
|
||||||
|
Subtitle: sub.Subtitle,
|
||||||
|
URL: url,
|
||||||
|
VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, lang),
|
||||||
|
}
|
||||||
|
|
||||||
|
return env.RenderJSON(w, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloadVVTSubtitle returns a vvt subtitle for the movie
|
// DownloadVVTSubtitle returns a vvt subtitle for the movie
|
||||||
func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
lang := vars["lang"]
|
lang := polochon.Language(vars["lang"])
|
||||||
season, _ := strconv.Atoi(vars["season"])
|
season, _ := strconv.Atoi(vars["season"])
|
||||||
episode, _ := strconv.Atoi(vars["episode"])
|
episode, _ := strconv.Atoi(vars["episode"])
|
||||||
|
|
||||||
@ -401,13 +406,20 @@ func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) e
|
|||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
url, err := client.SubtitleURL(&papi.Episode{
|
s := &papi.Subtitle{
|
||||||
|
Subtitle: &polochon.Subtitle{
|
||||||
|
Video: &papi.Episode{
|
||||||
ShowEpisode: &polochon.ShowEpisode{
|
ShowEpisode: &polochon.ShowEpisode{
|
||||||
ShowImdbID: id,
|
ShowImdbID: id,
|
||||||
Season: season,
|
Season: season,
|
||||||
Episode: episode,
|
Episode: episode,
|
||||||
},
|
},
|
||||||
}, lang)
|
},
|
||||||
|
Lang: lang,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := client.DownloadURLWithToken(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return env.RenderError(w, err)
|
return env.RenderError(w, err)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,14 @@ func New(imdbID string) *Show {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewWithClient returns a new Show with a polochon ShowConfig
|
// NewWithClient returns a new Show with a polochon ShowConfig
|
||||||
func NewWithClient(show *polochon.Show, client *papi.Client, pShow *papi.Show, wShow *models.WishedShow) *Show {
|
func NewWithClient(imdbID string, client *papi.Client, pShow *papi.Show, wShow *models.WishedShow) *Show {
|
||||||
|
var show *polochon.Show
|
||||||
|
if pShow == nil || pShow.Show == nil {
|
||||||
|
show = &polochon.Show{ImdbID: imdbID}
|
||||||
|
} else {
|
||||||
|
show = pShow.Show
|
||||||
|
}
|
||||||
|
|
||||||
s := &Show{
|
s := &Show{
|
||||||
Show: show,
|
Show: show,
|
||||||
client: client,
|
client: client,
|
||||||
@ -165,22 +172,22 @@ func (s *Show) downloadImages(env *web.Env) {
|
|||||||
for _, img := range []struct {
|
for _, img := range []struct {
|
||||||
url string
|
url string
|
||||||
urlType string
|
urlType string
|
||||||
scale bool
|
maxWidth uint
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
url: s.Show.Banner,
|
url: s.Show.Banner,
|
||||||
urlType: "banner",
|
urlType: "banner",
|
||||||
scale: false,
|
maxWidth: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: s.Show.Fanart,
|
url: s.Show.Fanart,
|
||||||
urlType: "fanart",
|
urlType: "fanart",
|
||||||
scale: false,
|
maxWidth: 960,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: s.Show.Poster,
|
url: s.Show.Poster,
|
||||||
urlType: "poster",
|
urlType: "poster",
|
||||||
scale: true,
|
maxWidth: 300,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
if img.url == "" {
|
if img.url == "" {
|
||||||
@ -190,7 +197,7 @@ func (s *Show) downloadImages(env *web.Env) {
|
|||||||
if _, err := os.Stat(s.imgFile(img.urlType)); err == nil {
|
if _, err := os.Stat(s.imgFile(img.urlType)); err == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := web.Download(img.url, s.imgFile(img.urlType), img.scale); err != nil {
|
if err := web.Download(img.url, s.imgFile(img.urlType), img.maxWidth); err != nil {
|
||||||
env.Log.Errorf("failed to dowload %s: %s", img.urlType, err)
|
env.Log.Errorf("failed to dowload %s: %s", img.urlType, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,7 +214,7 @@ func (s *Show) downloadImages(env *web.Env) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err := web.Download(e.Thumb, fileName, false)
|
err := web.Download(e.Thumb, fileName, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.Log.Errorf("failed to dowload the thumb for season %d episode %d ( %s ) : %s", e.Season, e.Episode, e.Thumb, err)
|
env.Log.Errorf("failed to dowload the thumb for season %d episode %d ( %s ) : %s", e.Season, e.Episode, e.Thumb, err)
|
||||||
}
|
}
|
||||||
@ -252,7 +259,7 @@ func getPolochonShows(env *web.Env, user *models.User) ([]*Show, error) {
|
|||||||
// Create Shows objects from the shows retrieved
|
// Create Shows objects from the shows retrieved
|
||||||
for _, pShow := range pshows.List() {
|
for _, pShow := range pshows.List() {
|
||||||
wShow, _ := wShows.IsShowInWishlist(pShow.ImdbID)
|
wShow, _ := wShows.IsShowInWishlist(pShow.ImdbID)
|
||||||
show := NewWithClient(&polochon.Show{ImdbID: pShow.ImdbID}, client, pShow, wShow)
|
show := NewWithClient(pShow.ImdbID, client, pShow, wShow)
|
||||||
shows = append(shows, show)
|
shows = append(shows, show)
|
||||||
}
|
}
|
||||||
return shows, nil
|
return shows, nil
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package subtitles
|
package subtitles
|
||||||
|
|
||||||
|
import polochon "github.com/odwrtw/polochon/lib"
|
||||||
|
|
||||||
// Subtitle represents a Subtitle
|
// Subtitle represents a Subtitle
|
||||||
type Subtitle struct {
|
type Subtitle struct {
|
||||||
Language string `json:"language"`
|
*polochon.Subtitle
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
VVTFile string `json:"vvt_file"`
|
VVTFile string `json:"vvt_file"`
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Download used for downloading file
|
// Download used for downloading file
|
||||||
var Download = func(srcURL, dest string, scale bool) error {
|
var Download = func(srcURL, dest string, maxWidth uint) error {
|
||||||
if err := createDirectory(dest); err != nil {
|
if err := createDirectory(dest); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -36,8 +36,8 @@ var Download = func(srcURL, dest string, scale bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if scale {
|
if maxWidth != 0 {
|
||||||
image = resize.Resize(300, 0, image, resize.Lanczos3)
|
image = resize.Resize(maxWidth, 0, image, resize.Lanczos3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the file
|
// Create the file
|
||||||
|
2
dev.sh
2
dev.sh
@ -135,7 +135,7 @@ case $1 in
|
|||||||
# Apply the migrations
|
# Apply the migrations
|
||||||
_migrate -path "$MIGRATION_SCHEMA" up
|
_migrate -path "$MIGRATION_SCHEMA" up
|
||||||
|
|
||||||
(cd backend && CONFIG_FILE="../config.yml" fresh -c fresh.conf)
|
(cd backend && CONFIG_FILE="../config.yml" go run ./*.go)
|
||||||
;;
|
;;
|
||||||
docker-db)
|
docker-db)
|
||||||
_ensure_command docker
|
_ensure_command docker
|
||||||
|
@ -20,10 +20,14 @@ export function updateUser(data) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteUser(username) {
|
export function deleteUser(username, userId) {
|
||||||
return request(
|
return request(
|
||||||
"ADMIN_DELETE_USER",
|
"ADMIN_DELETE_USER",
|
||||||
configureAxios().delete("/admins/users/" + username),
|
configureAxios().delete("/admins/users/" + username),
|
||||||
[() => getUsers()]
|
null,
|
||||||
|
{
|
||||||
|
username,
|
||||||
|
id: userId,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,17 @@ export function getMovieDetails(imdbId) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchMovieDetails(imdbId) {
|
||||||
|
return request(
|
||||||
|
"MOVIE_FETCH_DETAILS",
|
||||||
|
configureAxios().get(`/movies/${imdbId}`),
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
imdbId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function deleteMovie(imdbId, lastFetchUrl) {
|
export function deleteMovie(imdbId, lastFetchUrl) {
|
||||||
return request("MOVIE_DELETE", configureAxios().delete(`/movies/${imdbId}`), [
|
return request("MOVIE_DELETE", configureAxios().delete(`/movies/${imdbId}`), [
|
||||||
fetchMovies(lastFetchUrl),
|
fetchMovies(lastFetchUrl),
|
||||||
|
@ -1,24 +1,25 @@
|
|||||||
import { configureAxios, request } from "../requests";
|
import { configureAxios, request } from "../requests";
|
||||||
|
|
||||||
export const searchMovieSubtitles = (imdbId) => {
|
export const searchMovieSubtitle = (imdbId, lang) => {
|
||||||
return request(
|
return request(
|
||||||
"MOVIE_SUBTITLES_UPDATE",
|
"MOVIE_SUBTITLES_UPDATE",
|
||||||
configureAxios().post(`/movies/${imdbId}/subtitles/refresh`),
|
configureAxios().post(`/movies/${imdbId}/subtitles/${lang}`),
|
||||||
null,
|
null,
|
||||||
{ imdbId: imdbId }
|
{ imdbId, lang }
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const searchEpisodeSubtitles = (imdbId, season, episode) => {
|
export const searchEpisodeSubtitle = (imdbId, season, episode, lang) => {
|
||||||
const url = `/shows/${imdbId}/seasons/${season}/episodes/${episode}`;
|
const url = `/shows/${imdbId}/seasons/${season}/episodes/${episode}`;
|
||||||
return request(
|
return request(
|
||||||
"EPISODE_SUBTITLES_UPDATE",
|
"EPISODE_SUBTITLES_UPDATE",
|
||||||
configureAxios().post(`${url}/subtitles/refresh`),
|
configureAxios().post(`${url}/subtitles/${lang}`),
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
imdbId: imdbId,
|
imdbId,
|
||||||
season: season,
|
season,
|
||||||
episode: episode,
|
episode,
|
||||||
|
lang,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,12 @@ import "../scss/app.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { Router, Route, Switch, Redirect } from "react-router-dom";
|
import {
|
||||||
|
HashRouter as Router,
|
||||||
|
Route,
|
||||||
|
Switch,
|
||||||
|
Redirect,
|
||||||
|
} from "react-router-dom";
|
||||||
import Container from "react-bootstrap/Container";
|
import Container from "react-bootstrap/Container";
|
||||||
|
|
||||||
// Auth
|
// Auth
|
||||||
@ -28,6 +33,7 @@ import { AdminPanel } from "./components/admins/panel";
|
|||||||
import { Notifications } from "./components/notifications/notifications";
|
import { Notifications } from "./components/notifications/notifications";
|
||||||
import { Alert } from "./components/alerts/alert";
|
import { Alert } from "./components/alerts/alert";
|
||||||
import MovieList from "./components/movies/list";
|
import MovieList from "./components/movies/list";
|
||||||
|
import { MovieDetails } from "./components/movies/details";
|
||||||
import { AppNavBar } from "./components/navbar";
|
import { AppNavBar } from "./components/navbar";
|
||||||
import { WsHandler } from "./components/websocket";
|
import { WsHandler } from "./components/websocket";
|
||||||
import { ShowDetails } from "./components/shows/details";
|
import { ShowDetails } from "./components/shows/details";
|
||||||
@ -61,6 +67,7 @@ const App = () => (
|
|||||||
/>
|
/>
|
||||||
<Route path="/movies/polochon" exact component={MovieList} />
|
<Route path="/movies/polochon" exact component={MovieList} />
|
||||||
<Route path="/movies/wishlist" exact component={MovieList} />
|
<Route path="/movies/wishlist" exact component={MovieList} />
|
||||||
|
<Route path="/movies/details/:imdbId" exact component={MovieDetails} />
|
||||||
<Route path="/movies/search/:search" exact component={MovieList} />
|
<Route path="/movies/search/:search" exact component={MovieList} />
|
||||||
<Route
|
<Route
|
||||||
path="/movies/explore/:source/:category"
|
path="/movies/explore/:source/:category"
|
||||||
@ -76,7 +83,9 @@ const App = () => (
|
|||||||
component={ShowList}
|
component={ShowList}
|
||||||
/>
|
/>
|
||||||
<Route path="/shows/details/:imdbId" exact component={ShowDetails} />
|
<Route path="/shows/details/:imdbId" exact component={ShowDetails} />
|
||||||
<Route render={() => <Redirect to="/movies/explore/yts/seeds" />} />
|
<Route
|
||||||
|
render={() => <Redirect to="/movies/explore/trakttv/trending" />}
|
||||||
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from "react";
|
import React from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { getAdminModules } from "../../actions/admins";
|
import { getAdminModules } from "../../actions/admins";
|
||||||
|
|
||||||
@ -10,9 +10,15 @@ export const AdminModules = () => {
|
|||||||
const loading = useSelector((state) => state.admin.fetchingModules);
|
const loading = useSelector((state) => state.admin.fetchingModules);
|
||||||
const modules = useSelector((state) => state.admin.modules);
|
const modules = useSelector((state) => state.admin.modules);
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchModules = () => {
|
||||||
dispatch(getAdminModules());
|
dispatch(getAdminModules());
|
||||||
}, [dispatch]);
|
};
|
||||||
|
|
||||||
return <Modules modules={modules} isLoading={loading} />;
|
return (
|
||||||
|
<Modules
|
||||||
|
modules={modules}
|
||||||
|
isLoading={loading}
|
||||||
|
fetchModules={fetchModules}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -51,7 +51,7 @@ export const UserEdit = ({ id }) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
if (confirmDelete) {
|
if (confirmDelete) {
|
||||||
dispatch(deleteUser(name));
|
dispatch(deleteUser(user.name, id));
|
||||||
setModal(false);
|
setModal(false);
|
||||||
} else {
|
} else {
|
||||||
setConfirmDelete(true);
|
setConfirmDelete(true);
|
||||||
|
@ -18,7 +18,7 @@ export const DownloadAndStream = ({ url, name, subtitles }) => {
|
|||||||
DownloadAndStream.propTypes = {
|
DownloadAndStream.propTypes = {
|
||||||
url: PropTypes.string,
|
url: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
subtitles: PropTypes.array,
|
subtitles: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DownloadButton = ({ url }) => (
|
const DownloadButton = ({ url }) => (
|
||||||
@ -67,19 +67,18 @@ const StreamButton = ({ name, url, subtitles }) => {
|
|||||||
StreamButton.propTypes = {
|
StreamButton.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
url: PropTypes.string.isRequired,
|
url: PropTypes.string.isRequired,
|
||||||
subtitles: PropTypes.array,
|
subtitles: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Player = ({ url, subtitles }) => {
|
const Player = ({ url, subtitles }) => {
|
||||||
const subs = subtitles || [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="embed-responsive embed-responsive-16by9">
|
<div className="embed-responsive embed-responsive-16by9">
|
||||||
<video className="embed-responsive-item" controls>
|
<video className="embed-responsive-item" controls>
|
||||||
<source src={url} type="video/mp4" />
|
<source src={url} type="video/mp4" />
|
||||||
{subs.map((sub, index) => (
|
{subtitles &&
|
||||||
|
[...subtitles.entries()].map(([lang, sub]) => (
|
||||||
<track
|
<track
|
||||||
key={index}
|
key={lang}
|
||||||
kind="subtitles"
|
kind="subtitles"
|
||||||
label={sub.language}
|
label={sub.language}
|
||||||
src={sub.vvt_file}
|
src={sub.vvt_file}
|
||||||
@ -91,6 +90,6 @@ const Player = ({ url, subtitles }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
Player.propTypes = {
|
Player.propTypes = {
|
||||||
subtitles: PropTypes.array,
|
subtitles: PropTypes.object,
|
||||||
url: PropTypes.string.isRequired,
|
url: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -3,25 +3,22 @@ import PropTypes from "prop-types";
|
|||||||
|
|
||||||
import Dropdown from "react-bootstrap/Dropdown";
|
import Dropdown from "react-bootstrap/Dropdown";
|
||||||
|
|
||||||
|
import { prettySize, upperCaseFirst } from "../../utils";
|
||||||
|
|
||||||
export const SubtitlesButton = ({
|
export const SubtitlesButton = ({
|
||||||
subtitles,
|
subtitles,
|
||||||
inLibrary,
|
inLibrary,
|
||||||
searching,
|
|
||||||
search,
|
search,
|
||||||
|
fetchingSubtitles,
|
||||||
}) => {
|
}) => {
|
||||||
if (inLibrary === false) {
|
if (inLibrary === false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
const onSelect = (eventKey) => {
|
|
||||||
if (eventKey === null || eventKey != 1) {
|
|
||||||
setShow(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onToggle = (isOpen, event, metadata) => {
|
const onToggle = (isOpen, event, metadata) => {
|
||||||
// Don't close on select
|
// Don't close on select
|
||||||
if (metadata && metadata.source !== "select") {
|
if (metadata && metadata.source !== "select") {
|
||||||
@ -29,10 +26,20 @@ export const SubtitlesButton = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const count = subtitles && subtitles.length !== 0 ? subtitles.length : 0;
|
const searchAll = () => {
|
||||||
|
const langs = ["fr_FR", "en_US"];
|
||||||
|
for (const lang of langs) {
|
||||||
|
search(lang);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const count = subtitles && subtitles.size !== 0 ? subtitles.size : 0;
|
||||||
|
|
||||||
|
const searching = fetchingSubtitles.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="mr-1 mb-1">
|
<span className="mr-1 mb-1">
|
||||||
<Dropdown drop="up" show={show} onToggle={onToggle} onSelect={onSelect}>
|
<Dropdown drop="up" show={show} onToggle={onToggle}>
|
||||||
<Dropdown.Toggle variant="secondary" bsPrefix="btn-sm w-md-100">
|
<Dropdown.Toggle variant="secondary" bsPrefix="btn-sm w-md-100">
|
||||||
<i className="fa fa-commenting mr-1" />
|
<i className="fa fa-commenting mr-1" />
|
||||||
Subtitles
|
Subtitles
|
||||||
@ -40,9 +47,13 @@ export const SubtitlesButton = ({
|
|||||||
</Dropdown.Toggle>
|
</Dropdown.Toggle>
|
||||||
|
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu>
|
||||||
<Dropdown.Item eventKey={1} onClick={search}>
|
<Dropdown.Item eventKey={1} onClick={searchAll}>
|
||||||
<i className={`fa ${searching ? "fa-spin" : ""} fa-refresh mr-1`} />
|
<div className="d-flex justify-content-between align-items-center">
|
||||||
Automatic search
|
<span>Automatic search</span>
|
||||||
|
<div
|
||||||
|
className={`fa ${searching ? "fa-spin" : ""} fa-refresh ml-1`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
{count > 0 && (
|
{count > 0 && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@ -53,10 +64,13 @@ export const SubtitlesButton = ({
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
{count > 0 &&
|
{count > 0 &&
|
||||||
subtitles.map((subtitle, index) => (
|
[...subtitles.entries()].map(([lang, subtitle]) => (
|
||||||
<Dropdown.Item href={subtitle.url} key={index}>
|
<SubtitleEntry
|
||||||
{subtitle.language.split("_")[1]}
|
key={lang}
|
||||||
</Dropdown.Item>
|
subtitle={subtitle}
|
||||||
|
searching={searching}
|
||||||
|
search={search}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
@ -64,8 +78,42 @@ export const SubtitlesButton = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
SubtitlesButton.propTypes = {
|
SubtitlesButton.propTypes = {
|
||||||
subtitles: PropTypes.array,
|
subtitles: PropTypes.object,
|
||||||
inLibrary: PropTypes.bool.isRequired,
|
inLibrary: PropTypes.bool.isRequired,
|
||||||
searching: PropTypes.bool.isRequired,
|
fetchingSubtitles: PropTypes.array.isRequired,
|
||||||
search: PropTypes.func.isRequired,
|
search: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SubtitleEntry = ({ subtitle, search }) => {
|
||||||
|
const lang = upperCaseFirst(subtitle.lang.split("_")[0]);
|
||||||
|
const size = subtitle.size ? subtitle.size : 0;
|
||||||
|
const embedded = subtitle.embedded ? subtitle.embedded : false;
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
search(subtitle.lang);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown.Item as="span" disabled={embedded}>
|
||||||
|
<div className="d-flex justify-content-between align-items-center">
|
||||||
|
<a href={subtitle.url ? subtitle.url : ""} className="link-unstyled">
|
||||||
|
{lang}
|
||||||
|
{embedded && <small className="ml-2">(Inside the video)</small>}
|
||||||
|
{size !== 0 && <span> ({prettySize(size)})</span>}
|
||||||
|
</a>
|
||||||
|
{!embedded && (
|
||||||
|
<div
|
||||||
|
onClick={handleRefresh}
|
||||||
|
className={`clickable fa ${
|
||||||
|
subtitle.searching ? "fa-spin" : ""
|
||||||
|
} fa-refresh`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Dropdown.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
SubtitleEntry.propTypes = {
|
||||||
|
search: PropTypes.func.isRequired,
|
||||||
|
subtitle: PropTypes.object,
|
||||||
|
};
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useSelector } from "react-redux";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const Fanart = () => {
|
export const Fanart = ({ url }) => {
|
||||||
const url = useSelector((state) => state.show.show.fanart_url);
|
if (url == "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="show-fanart mx-n3 mt-n1">
|
<div className="show-fanart mx-n3 mt-n1">
|
||||||
<img
|
<img
|
||||||
@ -12,3 +14,9 @@ export const Fanart = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Fanart.propTypes = {
|
||||||
|
url: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
Fanart.defaultProps = {
|
||||||
|
url: "",
|
||||||
|
};
|
@ -1,18 +1,23 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
import { prettySize } from "../../utils";
|
||||||
|
|
||||||
export const PolochonMetadata = ({
|
export const PolochonMetadata = ({
|
||||||
quality,
|
quality,
|
||||||
container,
|
container,
|
||||||
videoCodec,
|
videoCodec,
|
||||||
audioCodec,
|
audioCodec,
|
||||||
releaseGroup,
|
releaseGroup,
|
||||||
|
size,
|
||||||
}) => {
|
}) => {
|
||||||
if (!quality || quality === "") {
|
if (!quality || quality === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = [quality, container, videoCodec, audioCodec, releaseGroup]
|
const s = size === 0 ? "" : prettySize(size);
|
||||||
|
|
||||||
|
const metadata = [quality, container, videoCodec, audioCodec, releaseGroup, s]
|
||||||
.filter((m) => m && m !== "")
|
.filter((m) => m && m !== "")
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
|
||||||
@ -29,4 +34,5 @@ PolochonMetadata.propTypes = {
|
|||||||
videoCodec: PropTypes.string,
|
videoCodec: PropTypes.string,
|
||||||
audioCodec: PropTypes.string,
|
audioCodec: PropTypes.string,
|
||||||
releaseGroup: PropTypes.string,
|
releaseGroup: PropTypes.string,
|
||||||
|
size: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
@ -53,6 +53,7 @@ const ListDetails = (props) => {
|
|||||||
container={props.data.container}
|
container={props.data.container}
|
||||||
audioCodec={props.data.audio_codec}
|
audioCodec={props.data.audio_codec}
|
||||||
videoCodec={props.data.video_codec}
|
videoCodec={props.data.video_codec}
|
||||||
|
size={props.data.size}
|
||||||
/>
|
/>
|
||||||
<Plot plot={props.data.plot} />
|
<Plot plot={props.data.plot} />
|
||||||
{props.children}
|
{props.children}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import Loader from "../loader/loader";
|
import Loader from "../loader/loader";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
@ -7,11 +7,29 @@ import { upperCaseFirst } from "../../utils";
|
|||||||
// TODO: udpate this
|
// TODO: udpate this
|
||||||
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||||
|
|
||||||
const Modules = ({ isLoading, modules }) => {
|
const Modules = ({ isLoading, modules, fetchModules }) => {
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
fetchModules();
|
||||||
|
setShow(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!show) {
|
||||||
|
return (
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-12 col-md-8 offset-md-2 mb-3">
|
||||||
|
<div className="btn btn-secondary w-100" onClick={handleClick}>
|
||||||
|
Show modules status
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{Object.keys(modules).map((type) => (
|
{Object.keys(modules).map((type) => (
|
||||||
@ -23,6 +41,7 @@ const Modules = ({ isLoading, modules }) => {
|
|||||||
Modules.propTypes = {
|
Modules.propTypes = {
|
||||||
isLoading: PropTypes.bool.isRequired,
|
isLoading: PropTypes.bool.isRequired,
|
||||||
modules: PropTypes.object.isRequired,
|
modules: PropTypes.object.isRequired,
|
||||||
|
fetchModules: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
export default Modules;
|
export default Modules;
|
||||||
|
|
||||||
|
162
frontend/js/components/movies/details.js
Normal file
162
frontend/js/components/movies/details.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
|
||||||
|
import {
|
||||||
|
fetchMovieDetails,
|
||||||
|
getMovieDetails,
|
||||||
|
movieWishlistToggle,
|
||||||
|
} from "../../actions/movies";
|
||||||
|
import { searchMovieSubtitle } from "../../actions/subtitles";
|
||||||
|
|
||||||
|
import Loader from "../loader/loader";
|
||||||
|
|
||||||
|
import { Fanart } from "../details/fanart";
|
||||||
|
import { Plot } from "../details/plot";
|
||||||
|
import { Rating } from "../details/rating";
|
||||||
|
import { ReleaseDate } from "../details/releaseDate";
|
||||||
|
import { Title } from "../details/title";
|
||||||
|
import { PolochonMetadata } from "../details/polochon";
|
||||||
|
import { TrackingLabel } from "../details/tracking";
|
||||||
|
import { Genres } from "../details/genres";
|
||||||
|
import { Runtime } from "../details/runtime";
|
||||||
|
|
||||||
|
import { DownloadAndStream } from "../buttons/download";
|
||||||
|
import { ImdbBadge } from "../buttons/imdb";
|
||||||
|
import { TorrentsButton } from "../buttons/torrents";
|
||||||
|
import { SubtitlesButton } from "../buttons/subtitles";
|
||||||
|
import { ShowMore } from "../buttons/showMore";
|
||||||
|
|
||||||
|
export const MovieDetails = ({ match }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const loading = useSelector((state) => state.movie.loading);
|
||||||
|
const fanartUrl = useSelector((state) => state.movie.movie.fanart);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchMovieDetails(match.params.imdbId));
|
||||||
|
}, [dispatch, match]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Loader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Fanart url={fanartUrl} />
|
||||||
|
<Header />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MovieDetails.propTypes = {
|
||||||
|
match: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Header = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
audioCodec,
|
||||||
|
container,
|
||||||
|
fetchingDetails,
|
||||||
|
fetchingSubtitles,
|
||||||
|
genres,
|
||||||
|
imdb_id: imdbId,
|
||||||
|
plot,
|
||||||
|
polochon_url: polochonUrl,
|
||||||
|
poster_url: posterUrl,
|
||||||
|
quality,
|
||||||
|
rating,
|
||||||
|
runtime,
|
||||||
|
size,
|
||||||
|
subtitles,
|
||||||
|
title,
|
||||||
|
torrents,
|
||||||
|
videoCodec,
|
||||||
|
votes,
|
||||||
|
wishlisted,
|
||||||
|
year,
|
||||||
|
release_group: releaseGroup,
|
||||||
|
} = useSelector((state) => state.movie.movie);
|
||||||
|
|
||||||
|
const inLibrary = polochonUrl !== "";
|
||||||
|
|
||||||
|
if (!imdbId || imdbId === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-12 col-lg-10 offset-lg-1 mb-3">
|
||||||
|
<div className="d-flex flex-column flex-md-row">
|
||||||
|
<div className="d-flex justify-content-center">
|
||||||
|
<img className="overflow-hidden object-fit-cover" src={posterUrl} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ml-sm-1">
|
||||||
|
<div className="card-body">
|
||||||
|
<p className="card-title">
|
||||||
|
<Title
|
||||||
|
title={title}
|
||||||
|
wishlisted={wishlisted}
|
||||||
|
wishlist={() =>
|
||||||
|
dispatch(movieWishlistToggle(imdbId, wishlisted))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<ReleaseDate date={year} />
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<Runtime runtime={runtime} />
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<Genres genres={genres} />
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<Rating rating={rating} votes={votes} />
|
||||||
|
</p>
|
||||||
|
<div className="card-text">
|
||||||
|
<ImdbBadge imdbId={imdbId} />
|
||||||
|
<DownloadAndStream
|
||||||
|
url={polochonUrl}
|
||||||
|
name={title}
|
||||||
|
subtitles={subtitles}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="card-text">
|
||||||
|
<TrackingLabel inLibrary={inLibrary} wishlisted={wishlisted} />
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<PolochonMetadata
|
||||||
|
quality={quality}
|
||||||
|
releaseGroup={releaseGroup}
|
||||||
|
container={container}
|
||||||
|
audioCodec={audioCodec}
|
||||||
|
videoCodec={videoCodec}
|
||||||
|
size={size}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<Plot plot={plot} />
|
||||||
|
</p>
|
||||||
|
<div className="card-text">
|
||||||
|
<ShowMore id={imdbId} inLibrary={inLibrary}>
|
||||||
|
<TorrentsButton
|
||||||
|
torrents={torrents}
|
||||||
|
searching={fetchingDetails}
|
||||||
|
search={() => dispatch(getMovieDetails(imdbId))}
|
||||||
|
url={`#/torrents/search/movies/${encodeURI(title)}`}
|
||||||
|
/>
|
||||||
|
<SubtitlesButton
|
||||||
|
inLibrary={inLibrary}
|
||||||
|
fetchingSubtitles={fetchingSubtitles}
|
||||||
|
subtitles={subtitles}
|
||||||
|
search={(lang) => dispatch(searchMovieSubtitle(imdbId, lang))}
|
||||||
|
/>
|
||||||
|
</ShowMore>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -37,7 +37,7 @@ const fetchUrl = (match) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const MovieList = ({ match }) => {
|
const MovieList = ({ match, history }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -63,6 +63,10 @@ const MovieList = ({ match }) => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const movieDetails = (imdbId) => {
|
||||||
|
history.push("/movies/details/" + imdbId);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<ListPosters
|
<ListPosters
|
||||||
@ -73,8 +77,8 @@ const MovieList = ({ match }) => {
|
|||||||
exploreOptions={exploreOptions}
|
exploreOptions={exploreOptions}
|
||||||
selectedImdbId={selectedImdbId}
|
selectedImdbId={selectedImdbId}
|
||||||
onClick={selectFunc}
|
onClick={selectFunc}
|
||||||
onDoubleClick={() => {}}
|
onDoubleClick={movieDetails}
|
||||||
onKeyEnter={() => {}}
|
onKeyEnter={movieDetails}
|
||||||
params={match.params}
|
params={match.params}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
@ -105,6 +109,7 @@ MovieList.propTypes = {
|
|||||||
updateFilter: PropTypes.func,
|
updateFilter: PropTypes.func,
|
||||||
movieWishlistToggle: PropTypes.func,
|
movieWishlistToggle: PropTypes.func,
|
||||||
selectMovie: PropTypes.func,
|
selectMovie: PropTypes.func,
|
||||||
|
history: PropTypes.object,
|
||||||
match: PropTypes.object,
|
match: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
import { searchMovieSubtitles } from "../../actions/subtitles";
|
import { searchMovieSubtitle } from "../../actions/subtitles";
|
||||||
|
|
||||||
import { SubtitlesButton } from "../buttons/subtitles";
|
import { SubtitlesButton } from "../buttons/subtitles";
|
||||||
|
|
||||||
@ -15,16 +15,16 @@ export const MovieSubtitlesButton = () => {
|
|||||||
const subtitles = useSelector(
|
const subtitles = useSelector(
|
||||||
(state) => state.movies.movies.get(imdbId).subtitles
|
(state) => state.movies.movies.get(imdbId).subtitles
|
||||||
);
|
);
|
||||||
const searching = useSelector(
|
const fetchingSubtitles = useSelector(
|
||||||
(state) => state.movies.movies.get(imdbId).fetchingSubtitles
|
(state) => state.movies.movies.get(imdbId).fetchingSubtitles
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubtitlesButton
|
<SubtitlesButton
|
||||||
inLibrary={inLibrary}
|
inLibrary={inLibrary}
|
||||||
searching={searching}
|
fetchingSubtitles={fetchingSubtitles}
|
||||||
subtitles={subtitles}
|
subtitles={subtitles}
|
||||||
search={() => dispatch(searchMovieSubtitles(imdbId))}
|
search={(lang) => dispatch(searchMovieSubtitle(imdbId, lang))}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -99,7 +99,7 @@ Search.propTypes = {
|
|||||||
|
|
||||||
const MoviesDropdown = () => (
|
const MoviesDropdown = () => (
|
||||||
<NavDropdown title="Movies" id="navbar-movies-dropdown">
|
<NavDropdown title="Movies" id="navbar-movies-dropdown">
|
||||||
<LinkContainer to="/movies/explore/yts/seeds">
|
<LinkContainer to="/movies/explore/trakttv/trending">
|
||||||
<NavDropdown.Item>Discover</NavDropdown.Item>
|
<NavDropdown.Item>Discover</NavDropdown.Item>
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
<LinkContainer to="/movies/polochon">
|
<LinkContainer to="/movies/polochon">
|
||||||
|
@ -4,7 +4,7 @@ import { useSelector, useDispatch } from "react-redux";
|
|||||||
|
|
||||||
import Loader from "../loader/loader";
|
import Loader from "../loader/loader";
|
||||||
|
|
||||||
import { Fanart } from "./details/fanart";
|
import { Fanart } from "../details/fanart";
|
||||||
import { Header } from "./details/header";
|
import { Header } from "./details/header";
|
||||||
import { SeasonsList } from "./details/seasons";
|
import { SeasonsList } from "./details/seasons";
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ import { fetchShowDetails } from "../../actions/shows";
|
|||||||
export const ShowDetails = ({ match }) => {
|
export const ShowDetails = ({ match }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const loading = useSelector((state) => state.show.loading);
|
const loading = useSelector((state) => state.show.loading);
|
||||||
|
const fanartUrl = useSelector((state) => state.show.show.fanart_url);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetchShowDetails(match.params.imdbId));
|
dispatch(fetchShowDetails(match.params.imdbId));
|
||||||
@ -24,7 +25,7 @@ export const ShowDetails = ({ match }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Fanart />
|
<Fanart url={fanartUrl} />
|
||||||
<div className="row no-gutters">
|
<div className="row no-gutters">
|
||||||
<Header />
|
<Header />
|
||||||
<SeasonsList />
|
<SeasonsList />
|
||||||
|
@ -74,6 +74,7 @@ export const Episode = ({ season, episode }) => {
|
|||||||
container={data.container}
|
container={data.container}
|
||||||
audioCodec={data.audio_codec}
|
audioCodec={data.audio_codec}
|
||||||
videoCodec={data.video_codec}
|
videoCodec={data.video_codec}
|
||||||
|
size={data.size}
|
||||||
light
|
light
|
||||||
/>
|
/>
|
||||||
<ShowMore
|
<ShowMore
|
||||||
|
@ -2,7 +2,7 @@ import React from "react";
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
import { searchEpisodeSubtitles } from "../../../actions/subtitles";
|
import { searchEpisodeSubtitle } from "../../../actions/subtitles";
|
||||||
|
|
||||||
import { SubtitlesButton } from "../../buttons/subtitles";
|
import { SubtitlesButton } from "../../buttons/subtitles";
|
||||||
|
|
||||||
@ -10,30 +10,27 @@ export const EpisodeSubtitlesButton = ({ season, episode }) => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const imdbId = useSelector((state) => state.show.show.imdb_id);
|
const imdbId = useSelector((state) => state.show.show.imdb_id);
|
||||||
const searching = useSelector((state) =>
|
const fetchingSubtitles = useSelector(
|
||||||
|
(state) =>
|
||||||
state.show.show.seasons.get(season).get(episode).fetchingSubtitles
|
state.show.show.seasons.get(season).get(episode).fetchingSubtitles
|
||||||
? state.show.show.seasons.get(season).get(episode).fetchingSubtitles
|
|
||||||
: false
|
|
||||||
);
|
);
|
||||||
const inLibrary = useSelector(
|
const inLibrary = useSelector(
|
||||||
(state) =>
|
(state) =>
|
||||||
state.show.show.seasons.get(season).get(episode).polochon_url !== ""
|
state.show.show.seasons.get(season).get(episode).polochon_url !== ""
|
||||||
);
|
);
|
||||||
const subtitles = useSelector((state) =>
|
const subtitles = useSelector(
|
||||||
state.show.show.seasons.get(season).get(episode).subtitles
|
(state) => state.show.show.seasons.get(season).get(episode).subtitles
|
||||||
? state.show.show.seasons.get(season).get(episode).subtitles
|
|
||||||
: []
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const search = () => {
|
const search = (lang) => {
|
||||||
dispatch(searchEpisodeSubtitles(imdbId, season, episode));
|
dispatch(searchEpisodeSubtitle(imdbId, season, episode, lang));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubtitlesButton
|
<SubtitlesButton
|
||||||
subtitles={subtitles}
|
subtitles={subtitles}
|
||||||
inLibrary={inLibrary}
|
inLibrary={inLibrary}
|
||||||
searching={searching}
|
fetchingSubtitles={fetchingSubtitles}
|
||||||
search={search}
|
search={search}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,17 +1,27 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const Poster = ({ url }) => {
|
export const Poster = ({ thumb, fanart }) => {
|
||||||
if (!url || url === "") {
|
if (thumb === "" && fanart === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-md-2 d-none d-md-block">
|
<div className="col-md-2">
|
||||||
<img className="card-img" src={url} />
|
{thumb !== "" && (
|
||||||
|
<img className="card-img d-none d-sm-block" src={thumb} />
|
||||||
|
)}
|
||||||
|
{fanart !== "" && (
|
||||||
|
<img className="card-img d-block d-sm-none" src={fanart} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Poster.propTypes = {
|
Poster.propTypes = {
|
||||||
url: PropTypes.string,
|
thumb: PropTypes.string,
|
||||||
|
fanart: PropTypes.string,
|
||||||
|
};
|
||||||
|
Poster.defaultProps = {
|
||||||
|
thumb: "",
|
||||||
|
fanart: "",
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
import { Torrent } from "./torrent";
|
import { Torrent } from "./torrent";
|
||||||
import { Poster } from "./poster";
|
import { Poster } from "./poster";
|
||||||
@ -17,18 +18,32 @@ export const TorrentGroup = ({ torrentKey }) => {
|
|||||||
const title = (torrent) => {
|
const title = (torrent) => {
|
||||||
switch (torrent.type) {
|
switch (torrent.type) {
|
||||||
case "movie":
|
case "movie":
|
||||||
return torrent.video.title;
|
return (
|
||||||
|
<Link
|
||||||
|
className="link-unstyled"
|
||||||
|
to={`/movies/details/${torrent.video.imdb_id}`}
|
||||||
|
>
|
||||||
|
{torrent.video.title}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
case "episode":
|
case "episode":
|
||||||
return torrent.video.show_title;
|
return (
|
||||||
|
<Link
|
||||||
|
className="link-unstyled"
|
||||||
|
to={`/shows/details/${torrent.video.show_imdb_id}`}
|
||||||
|
>
|
||||||
|
{torrent.video.show_title}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return "Files";
|
return <span>Files</span>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-100 mb-3 card">
|
<div className="w-100 mb-3 card">
|
||||||
<div className="row no-gutters">
|
<div className="row no-gutters">
|
||||||
<Poster url={torrents[0].img} />
|
<Poster thumb={torrents[0].thumb} fanart={torrents[0].fanart} />
|
||||||
<div className="col-sm">
|
<div className="col-sm">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h4 className="card-title">{title(torrents[0])}</h4>
|
<h4 className="card-title">{title(torrents[0])}</h4>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from "react";
|
import React from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
import { PolochonList } from "../polochons/list";
|
import { PolochonList } from "../polochons/list";
|
||||||
@ -12,15 +12,19 @@ export const UserProfile = () => {
|
|||||||
const modules = useSelector((state) => state.user.modules);
|
const modules = useSelector((state) => state.user.modules);
|
||||||
const modulesLoading = useSelector((state) => state.user.modulesLoading);
|
const modulesLoading = useSelector((state) => state.user.modulesLoading);
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchModules = () => {
|
||||||
dispatch(getUserModules());
|
dispatch(getUserModules());
|
||||||
}, [dispatch]);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<UserEdit />
|
<UserEdit />
|
||||||
<PolochonList />
|
<PolochonList />
|
||||||
<Modules modules={modules} isLoading={modulesLoading} />
|
<Modules
|
||||||
|
modules={modules}
|
||||||
|
isLoading={modulesLoading}
|
||||||
|
fetchModules={fetchModules}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,11 @@ export default (state = defaultState, action) =>
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "ADMIN_DELETE_USER_FULFILLED": {
|
||||||
|
draft.users.delete(action.payload.main.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "ADMIN_GET_STATS_FULFILLED": {
|
case "ADMIN_GET_STATS_FULFILLED": {
|
||||||
draft.stats = action.payload.response.data;
|
draft.stats = action.payload.response.data;
|
||||||
break;
|
break;
|
||||||
|
@ -5,6 +5,7 @@ import { enableMapSet } from "immer";
|
|||||||
enableMapSet();
|
enableMapSet();
|
||||||
|
|
||||||
import movies from "./movies";
|
import movies from "./movies";
|
||||||
|
import movie from "./movie";
|
||||||
import shows from "./shows";
|
import shows from "./shows";
|
||||||
import show from "./show";
|
import show from "./show";
|
||||||
import user from "./users";
|
import user from "./users";
|
||||||
@ -16,6 +17,7 @@ import notifications from "./notifications";
|
|||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
movies,
|
movies,
|
||||||
|
movie,
|
||||||
shows,
|
shows,
|
||||||
show,
|
show,
|
||||||
user,
|
user,
|
||||||
|
77
frontend/js/reducers/movie.js
Normal file
77
frontend/js/reducers/movie.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { produce } from "immer";
|
||||||
|
|
||||||
|
import { formatSubtitle, formatMovie } from "./utils";
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
loading: false,
|
||||||
|
movie: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (state = defaultState, action) =>
|
||||||
|
produce(state, (draft) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "MOVIE_FETCH_DETAILS_PENDING":
|
||||||
|
draft.loading = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "MOVIE_FETCH_DETAILS_FULFILLED": {
|
||||||
|
draft.movie = formatMovie(action.payload.response.data);
|
||||||
|
draft.loading = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "MOVIE_GET_DETAILS_PENDING": {
|
||||||
|
let imdbId = action.payload.main.imdbId;
|
||||||
|
if (draft.movie.imdb_id !== imdbId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
draft.movie.fetchingDetails = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "MOVIE_GET_DETAILS_FULFILLED": {
|
||||||
|
let imdbId = action.payload.main.imdbId;
|
||||||
|
if (draft.movie.imdb_id !== imdbId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
draft.movie = formatMovie(action.payload.response.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "MOVIE_SUBTITLES_UPDATE_PENDING": {
|
||||||
|
let imdbId = action.payload.main.imdbId;
|
||||||
|
if (draft.movie.imdb_id !== imdbId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lang = action.payload.main.lang;
|
||||||
|
draft.movie.fetchingSubtitles.push(lang);
|
||||||
|
if (draft.movie.subtitles.get(lang)) {
|
||||||
|
draft.movie.subtitles.get(lang).searching = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "MOVIE_SUBTITLES_UPDATE_FULFILLED": {
|
||||||
|
let imdbId = action.payload.main.imdbId;
|
||||||
|
if (draft.movie.imdb_id !== imdbId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lang = action.payload.main.lang;
|
||||||
|
let data = action.payload.response.data;
|
||||||
|
draft.movie.fetchingSubtitles = draft.movie.fetchingSubtitles.filter(
|
||||||
|
(l) => l != lang
|
||||||
|
);
|
||||||
|
if (data) {
|
||||||
|
draft.movie.subtitles.set(lang, formatSubtitle(data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return draft;
|
||||||
|
}
|
||||||
|
});
|
@ -1,6 +1,6 @@
|
|||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
|
|
||||||
import { formatTorrents } from "../utils";
|
import { formatSubtitle, formatMovie } from "./utils";
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -10,13 +10,6 @@ const defaultState = {
|
|||||||
exploreOptions: {},
|
exploreOptions: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatMovie = (movie) => {
|
|
||||||
movie.fetchingDetails = false;
|
|
||||||
movie.fetchingSubtitles = false;
|
|
||||||
movie.torrents = formatTorrents(movie);
|
|
||||||
return movie;
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatMovies = (movies = []) => {
|
const formatMovies = (movies = []) => {
|
||||||
let allMoviesInPolochon = true;
|
let allMoviesInPolochon = true;
|
||||||
movies.map((movie) => {
|
movies.map((movie) => {
|
||||||
@ -64,10 +57,17 @@ export default (state = defaultState, action) =>
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "MOVIE_GET_DETAILS_PENDING":
|
case "MOVIE_GET_DETAILS_PENDING":
|
||||||
|
if (!draft.movies.get(action.payload.main.imdbId)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
draft.movies.get(action.payload.main.imdbId).fetchingDetails = true;
|
draft.movies.get(action.payload.main.imdbId).fetchingDetails = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "MOVIE_GET_DETAILS_FULFILLED":
|
case "MOVIE_GET_DETAILS_FULFILLED":
|
||||||
|
if (!draft.movies.get(action.payload.main.imdbId)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
draft.movies.set(
|
draft.movies.set(
|
||||||
action.payload.response.data.imdb_id,
|
action.payload.response.data.imdb_id,
|
||||||
formatMovie(action.payload.response.data)
|
formatMovie(action.payload.response.data)
|
||||||
@ -87,15 +87,36 @@ export default (state = defaultState, action) =>
|
|||||||
draft.lastFetchUrl = action.payload.url;
|
draft.lastFetchUrl = action.payload.url;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "MOVIE_SUBTITLES_UPDATE_PENDING":
|
case "MOVIE_SUBTITLES_UPDATE_PENDING": {
|
||||||
draft.movies.get(action.payload.main.imdbId).fetchingSubtitles = true;
|
let imdbId = action.payload.main.imdbId;
|
||||||
|
let lang = action.payload.main.lang;
|
||||||
|
if (!draft.movies.get(imdbId)) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "MOVIE_SUBTITLES_UPDATE_FULFILLED":
|
draft.movies.get(imdbId).fetchingSubtitles.push(lang);
|
||||||
draft.movies.get(action.payload.main.imdbId).fetchingSubtitles = false;
|
if (draft.movies.get(imdbId).subtitles.get(lang)) {
|
||||||
draft.movies.get(action.payload.main.imdbId).subtitles =
|
draft.movies.get(imdbId).subtitles.get(lang).searching = true;
|
||||||
action.payload.response.data;
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "MOVIE_SUBTITLES_UPDATE_FULFILLED": {
|
||||||
|
let imdbId = action.payload.main.imdbId;
|
||||||
|
let lang = action.payload.main.lang;
|
||||||
|
let data = action.payload.response.data;
|
||||||
|
if (!draft.movies.get(imdbId)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
draft.movies.get(imdbId).fetchingSubtitles = draft.movies
|
||||||
|
.get(imdbId)
|
||||||
|
.fetchingSubtitles.filter((l) => l != lang);
|
||||||
|
if (data) {
|
||||||
|
draft.movies.get(imdbId).subtitles.set(lang, formatSubtitle(data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "SELECT_MOVIE":
|
case "SELECT_MOVIE":
|
||||||
draft.selectedImdbId = action.payload.imdbId;
|
draft.selectedImdbId = action.payload.imdbId;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
|
|
||||||
import { formatTorrents } from "../utils";
|
import { formatTorrents, formatSubtitle, formatSubtitles } from "./utils";
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -10,10 +10,12 @@ const defaultState = {
|
|||||||
const formatEpisode = (episode) => {
|
const formatEpisode = (episode) => {
|
||||||
// Format the episode's torrents
|
// Format the episode's torrents
|
||||||
episode.torrents = formatTorrents(episode);
|
episode.torrents = formatTorrents(episode);
|
||||||
|
episode.subtitles = formatSubtitles(episode.subtitles);
|
||||||
|
|
||||||
// Set the default fetching data
|
// Set the default fetching data
|
||||||
episode.fetching = false;
|
episode.fetching = false;
|
||||||
episode.fetchingSubtitles = false;
|
// Holds the languages of the subtitles currently fetching
|
||||||
|
episode.fetchingSubtitles = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (state = defaultState, action) =>
|
export default (state = defaultState, action) =>
|
||||||
@ -98,22 +100,42 @@ export default (state = defaultState, action) =>
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "EPISODE_SUBTITLES_UPDATE_PENDING":
|
case "EPISODE_SUBTITLES_UPDATE_PENDING": {
|
||||||
|
let season = action.payload.main.season;
|
||||||
|
let episode = action.payload.main.episode;
|
||||||
|
let lang = action.payload.main.lang;
|
||||||
draft.show.seasons
|
draft.show.seasons
|
||||||
.get(action.payload.main.season)
|
.get(season)
|
||||||
.get(action.payload.main.episode).fetchingSubtitles = true;
|
.get(episode)
|
||||||
break;
|
.fetchingSubtitles.push(lang);
|
||||||
|
if (draft.show.seasons.get(season).get(episode).subtitles.get(lang)) {
|
||||||
case "EPISODE_SUBTITLES_UPDATE_FULFILLED": {
|
|
||||||
draft.show.seasons
|
draft.show.seasons
|
||||||
.get(action.payload.main.season)
|
.get(season)
|
||||||
.get(action.payload.main.episode).subtitles =
|
.get(episode)
|
||||||
action.payload.response.data;
|
.subtitles.get(lang).searching = true;
|
||||||
draft.show.seasons
|
}
|
||||||
.get(action.payload.main.season)
|
|
||||||
.get(action.payload.main.episode).fetchingSubtitles = false;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "EPISODE_SUBTITLES_UPDATE_FULFILLED": {
|
||||||
|
let season = action.payload.main.season;
|
||||||
|
let episode = action.payload.main.episode;
|
||||||
|
let lang = action.payload.main.lang;
|
||||||
|
let data = action.payload.response.data;
|
||||||
|
draft.show.seasons.get(season).get(episode).fetchingSubtitles =
|
||||||
|
draft.show.seasons
|
||||||
|
.get(season)
|
||||||
|
.get(episode)
|
||||||
|
.fetchingSubtitles.filter((l) => l != lang);
|
||||||
|
if (data) {
|
||||||
|
draft.show.seasons
|
||||||
|
.get(season)
|
||||||
|
.get(episode)
|
||||||
|
.subtitles.set(lang, formatSubtitle(data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return draft;
|
return draft;
|
||||||
}
|
}
|
||||||
|
51
frontend/js/reducers/utils.js
Normal file
51
frontend/js/reducers/utils.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
export const formatSubtitles = (subtitles) => {
|
||||||
|
if (!subtitles || subtitles.length == 0) {
|
||||||
|
return new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
let map = new Map();
|
||||||
|
subtitles.forEach((subtitle) => {
|
||||||
|
subtitle = formatSubtitle(subtitle);
|
||||||
|
map.set(subtitle.lang, subtitle);
|
||||||
|
});
|
||||||
|
|
||||||
|
return map;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatSubtitle = (subtitle) => {
|
||||||
|
if (!subtitle) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
subtitle.searching = false;
|
||||||
|
return subtitle;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatTorrents = (input) => {
|
||||||
|
if (!input.torrents || input.torrents.length == 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let torrentMap = new Map();
|
||||||
|
input.torrents.forEach((torrent) => {
|
||||||
|
if (!torrent.result || !torrent.result.source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!torrentMap.has(torrent.result.source)) {
|
||||||
|
torrentMap.set(torrent.result.source, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
torrentMap.get(torrent.result.source).set(torrent.quality, torrent);
|
||||||
|
});
|
||||||
|
|
||||||
|
return torrentMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatMovie = (movie) => {
|
||||||
|
movie.fetchingDetails = false;
|
||||||
|
movie.fetchingSubtitles = [];
|
||||||
|
movie.torrents = formatTorrents(movie);
|
||||||
|
movie.subtitles = formatSubtitles(movie.subtitles);
|
||||||
|
return movie;
|
||||||
|
};
|
@ -35,26 +35,5 @@ export const prettySize = (fileSizeInBytes) => {
|
|||||||
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
|
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatTorrents = (input) => {
|
|
||||||
if (!input.torrents || input.torrents.length == 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let torrentMap = new Map();
|
|
||||||
input.torrents.forEach((torrent) => {
|
|
||||||
if (!torrent.result || !torrent.result.source) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!torrentMap.has(torrent.result.source)) {
|
|
||||||
torrentMap.set(torrent.result.source, new Map());
|
|
||||||
}
|
|
||||||
|
|
||||||
torrentMap.get(torrent.result.source).set(torrent.quality, torrent);
|
|
||||||
});
|
|
||||||
|
|
||||||
return torrentMap;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const upperCaseFirst = (string) =>
|
export const upperCaseFirst = (string) =>
|
||||||
string.charAt(0).toUpperCase() + string.slice(1);
|
string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
18888
frontend/package-lock.json
generated
18888
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,64 +1,62 @@
|
|||||||
{
|
{
|
||||||
"name": "canape",
|
"name": "canape",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "NODE_ENV=development ./node_modules/webpack/bin/webpack.js -d --progress --colors --watch",
|
"start": "NODE_ENV=development npx webpack --progress --color --watch",
|
||||||
"build": "NODE_ENV=production ./node_modules/webpack/bin/webpack.js -p --progress --colors",
|
"build": "NODE_ENV=production npx webpack --progress --color",
|
||||||
"lint": "./node_modules/eslint/bin/eslint.js js"
|
"lint": "npx eslint js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^4.4.1",
|
"bootstrap": "^4.4.1",
|
||||||
"bootswatch": "^4.4.1",
|
"bootswatch": "^4.4.1",
|
||||||
"font-awesome": "^4.7.0",
|
"font-awesome": "^4.7.0",
|
||||||
"fuzzy": "^0.1.3",
|
"fuzzy": "^0.1.3",
|
||||||
"history": "^4.9.0",
|
"history": "^5.0.0",
|
||||||
"immer": "^6.0.3",
|
"immer": "^9.0.5",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.6.0",
|
||||||
"jwt-decode": "^2.1.0",
|
"jwt-decode": "^3.1.2",
|
||||||
"moment": "^2.20.1",
|
"moment": "^2.29.1",
|
||||||
"popper.js": "^1.15.0",
|
"popper.js": "^1.15.0",
|
||||||
"prop-types": "^15.6.0",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^16.13.1",
|
"react": "^17.0.2",
|
||||||
"react-bootstrap": "^1.0.0",
|
"react-bootstrap": "^1.6.1",
|
||||||
"react-bootstrap-sweetalert": "^5.1.9",
|
"react-bootstrap-sweetalert": "^5.2.0",
|
||||||
"react-bootstrap-toggle": "^2.2.6",
|
"react-bootstrap-toggle": "^2.3.2",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^17.0.2",
|
||||||
"react-infinite-scroll-component": "^5.0.4",
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
"react-loading": "2.0.3",
|
"react-loading": "2.0.3",
|
||||||
"react-redux": "^7.2.0",
|
"react-redux": "^7.2.4",
|
||||||
"react-router-bootstrap": "^0.25.0",
|
"react-router-bootstrap": "^0.25.0",
|
||||||
"react-router-dom": "^5.1.2",
|
"react-router-dom": "^5.2.0",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.1.1",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"ua-parser-js": "^0.7.17"
|
"ua-parser-js": "^0.7.28"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.9.0",
|
"@babel/core": "^7.15.0",
|
||||||
"@babel/preset-env": "^7.9.0",
|
"@babel/preset-env": "^7.15.0",
|
||||||
"@babel/preset-react": "^7.9.4",
|
"@babel/preset-react": "^7.14.5",
|
||||||
"autoprefixer": "^9.7.5",
|
"autoprefixer": "^10.3.1",
|
||||||
"axios": "^0.19.2",
|
"axios": "^0.21.1",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.2.2",
|
||||||
"clean-webpack-plugin": "^3.0.0",
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
"css-loader": "^3.4.2",
|
"css-loader": "^6.2.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-config-prettier": "^6.10.1",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-import": "^2.20.2",
|
"eslint-plugin-import": "^2.24.0",
|
||||||
"eslint-plugin-prettier": "^3.1.2",
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
"eslint-plugin-react": "^7.19.0",
|
"eslint-plugin-react": "^7.24.0",
|
||||||
"eslint-plugin-react-hooks": "^2.5.1",
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"file-loader": "^5.1.0",
|
"html-webpack-plugin": "^5.3.2",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"postcss-loader": "^6.1.1",
|
||||||
"node-sass": "^4.12.0",
|
"prettier": "^2.3.2",
|
||||||
"postcss-loader": "^3.0.0",
|
"sass": "^1.37.5",
|
||||||
"prettier": "^2.0.2",
|
"sass-loader": "^12.1.0",
|
||||||
"sass-loader": "^8.0.2",
|
"style-loader": "^3.2.1",
|
||||||
"style-loader": "^1.1.3",
|
"universal-cookie": "^4.0.4",
|
||||||
"universal-cookie": "^4.0.3",
|
"webpack": "^5.50.0",
|
||||||
"url-loader": "^3.0.0",
|
"webpack-cli": "^4.7.2",
|
||||||
"webpack": "^4.42.1",
|
"webpack-pwa-manifest": "^4.3.0"
|
||||||
"webpack-cli": "^3.3.11",
|
|
||||||
"webpack-pwa-manifest": "^4.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,3 +146,8 @@ div.sweet-alert > h2 {
|
|||||||
.toast {
|
.toast {
|
||||||
background-color: $card-bg;
|
background-color: $card-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link-unstyled, .link-unstyled:link, .link-unstyled:hover {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
@ -20,19 +20,9 @@ const config = {
|
|||||||
output: {
|
output: {
|
||||||
path: BUILD_DIR,
|
path: BUILD_DIR,
|
||||||
filename: "[contenthash]-app.js",
|
filename: "[contenthash]-app.js",
|
||||||
|
assetModuleFilename: "[hash]-[name][ext][query]",
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {},
|
||||||
runtimeChunk: "single",
|
|
||||||
splitChunks: {
|
|
||||||
cacheGroups: {
|
|
||||||
vendor: {
|
|
||||||
test: /[\\/]node_modules[\\/]/,
|
|
||||||
name: "vendors",
|
|
||||||
chunks: "all",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
@ -51,15 +41,15 @@ const config = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|jpg|svg|ico)$/,
|
test: /\.(png|jpg|svg|ico)$/,
|
||||||
use: ["file-loader?name=[hash]-[name].[ext]"],
|
type: "asset/resource",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
use: ["url-loader?limit=10000&mimetype=application/font-woff"],
|
type: "asset",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
use: ["file-loader"],
|
type: "asset",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -67,7 +57,6 @@ const config = {
|
|||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
|
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
|
||||||
}),
|
}),
|
||||||
new webpack.HashedModuleIdsPlugin(),
|
|
||||||
new CleanWebpackPlugin({
|
new CleanWebpackPlugin({
|
||||||
cleanOnceBeforeBuildPatterns: ["**/*", "!img/**/*", "!img"],
|
cleanOnceBeforeBuildPatterns: ["**/*", "!img/**/*", "!img"],
|
||||||
}),
|
}),
|
||||||
@ -87,6 +76,7 @@ const config = {
|
|||||||
theme_color: "#4e5d6c", // eslint-disable-line camelcase
|
theme_color: "#4e5d6c", // eslint-disable-line camelcase
|
||||||
display: "standalone",
|
display: "standalone",
|
||||||
orientation: "omit",
|
orientation: "omit",
|
||||||
|
publicPath: "/",
|
||||||
scope: "/",
|
scope: "/",
|
||||||
start_url: "/", // eslint-disable-line camelcase
|
start_url: "/", // eslint-disable-line camelcase
|
||||||
icons: [
|
icons: [
|
||||||
@ -105,7 +95,7 @@ const config = {
|
|||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".js"],
|
extensions: [".js"],
|
||||||
},
|
},
|
||||||
devtool: mode === "production" ? "source-map" : "inline-source-map",
|
devtool: mode === "production" ? false : "source-map",
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user