Compare commits

..

No commits in common. "9488795186720e835c74af08aab4abae43dbb0a9" and "451fea735537aa6ab2f32386874b4283d7e61f25" have entirely different histories.

29 changed files with 244 additions and 412 deletions

View File

@ -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(id, client, pShow, wShow) show := shows.NewWithClient(&polochon.Show{ImdbID: 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}

6
backend/fresh.conf Normal file
View File

@ -0,0 +1,6 @@
root: .
valid_ext: .go
colors: 1
build_name: dev-build
build_log: dev-build.log
tmp_path: ../build

View File

@ -2,12 +2,20 @@ 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"
) )

View File

@ -283,7 +283,6 @@ 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)
@ -295,35 +294,29 @@ 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}}
sub, err := client.UpdateSubtitle(movie, lang) refreshSubs, err := client.UpdateSubtitles(movie)
if err != nil { if err != nil {
return env.RenderError(w, err) return env.RenderError(w, err)
} }
// TODO: handle this with a better error subs := []subtitles.Subtitle{}
if sub == nil { for _, lang := range refreshSubs {
return env.RenderJSON(w, nil) subtitleURL, _ := client.SubtitleURL(movie, lang)
subs = append(subs, subtitles.Subtitle{
Language: lang,
URL: subtitleURL,
VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", id, lang),
})
} }
url, err := client.DownloadURLWithToken(sub) return env.RenderJSON(w, subs)
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 := polochon.Language(vars["lang"]) lang := vars["lang"]
// Get the user // Get the user
user := auth.GetCurrentUser(r, env.Log) user := auth.GetCurrentUser(r, env.Log)
@ -334,14 +327,7 @@ func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) e
return env.RenderError(w, err) return env.RenderError(w, err)
} }
s := &papi.Subtitle{ url, err := client.SubtitleURL(&papi.Movie{Movie: &polochon.Movie{ImdbID: id}}, lang)
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)
} }

View File

@ -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.DownloadURLWithToken(m.pMovie) movieToMarshal.PolochonURL, _ = m.client.DownloadURL(m.pMovie)
// Get the metadata // Get the metadata
movieToMarshal.DateAdded = m.pMovie.DateAdded movieToMarshal.DateAdded = m.pMovie.DateAdded
@ -58,14 +58,13 @@ func (m *Movie) MarshalJSON() ([]byte, error) {
movieToMarshal.Container = m.pMovie.Container movieToMarshal.Container = m.pMovie.Container
// Append the Subtitles // Append the Subtitles
for _, s := range m.pMovie.Subtitles { for _, l := range m.pMovie.Subtitles {
sub := subtitles.Subtitle{Subtitle: s.Subtitle} subtitleURL, _ := m.client.SubtitleURL(m.pMovie, l)
if !sub.Embedded { movieToMarshal.Subtitles = append(movieToMarshal.Subtitles, subtitles.Subtitle{
subtitleURL, _ := m.client.DownloadURLWithToken(s) Language: l,
sub.URL = subtitleURL URL: subtitleURL,
sub.VVTFile = fmt.Sprintf("/movies/%s/subtitles/%s", m.ImdbID, s.Lang) VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", m.ImdbID, l),
} })
movieToMarshal.Subtitles = append(movieToMarshal.Subtitles, sub)
} }
} }
@ -74,18 +73,13 @@ 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: m, Movie: &polochon.Movie{
ImdbID: imdbID,
},
} }
} }

View File

@ -43,7 +43,7 @@ func setupRoutes(env *web.Env) {
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]+}/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/{lang}", movies.RefreshMovieSubtitlesHandler).WithRole(models.UserRole).Methods("POST") env.Handle("/movies/{id:tt[0-9]+}/subtitles/refresh", 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 +54,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/{lang}", shows.RefreshEpisodeSubtitlesHandler).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.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")

View File

@ -3,6 +3,7 @@ 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"
@ -25,13 +26,18 @@ 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.DownloadURLWithToken( downloadURL, _ = e.show.client.DownloadURL(
&papi.Episode{ &papi.Episode{
ShowEpisode: &polochon.ShowEpisode{ ShowEpisode: &polochon.ShowEpisode{
ShowImdbID: e.ShowImdbID, ShowImdbID: e.ShowImdbID,
@ -40,21 +46,20 @@ func (e *Episode) MarshalJSON() ([]byte, error) {
}, },
}, },
) )
dateAdded = pEpisode.DateAdded
e.ShowEpisode.VideoMetadata = pEpisode.ShowEpisode.VideoMetadata quality = string(pEpisode.Quality)
e.ShowEpisode.File = pEpisode.ShowEpisode.File audioCodec = pEpisode.AudioCodec
videoCodec = pEpisode.VideoCodec
container = pEpisode.Container
// Append the Subtitles // Append the Subtitles
for _, s := range pEpisode.Subtitles { for _, l := range pEpisode.Subtitles {
sub := subtitles.Subtitle{Subtitle: s.Subtitle} subtitleURL, _ := e.show.client.SubtitleURL(pEpisode, l)
if !sub.Embedded { subs = append(subs, subtitles.Subtitle{
subtitleURL, _ := e.show.client.DownloadURLWithToken(s) Language: l,
sub.URL = subtitleURL URL: subtitleURL,
sub.VVTFile = fmt.Sprintf( VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, l),
"/shows/%s/seasons/%d/episodes/%d/subtitles/%s", })
e.ShowImdbID, e.Season, e.Episode, s.Lang)
}
subs = append(subs, sub)
} }
} }
} }
@ -64,11 +69,21 @@ 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,
} }

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"log"
"strconv" "strconv"
"net/http" "net/http"
@ -34,15 +35,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 {
env.Log.Println("Got error getting show ", err) 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 {
env.Log.Println("Got error getting wishlisted show ", err) log.Println("Got error getting wishlisted show ", err)
} }
s := NewWithClient(id, client, pShow, wShow) s := NewWithClient(&polochon.Show{ImdbID: 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
@ -80,15 +81,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 {
env.Log.Println("Got error getting show ", err) 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 {
env.Log.Println("Got error getting wishlisted show ", err) log.Println("Got error getting wishlisted show ", err)
} }
s := NewWithClient(id, client, pShow, wShow) s := NewWithClient(&polochon.Show{ImdbID: 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)
@ -162,7 +163,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.ImdbID, client, pShow, wShow) show := NewWithClient(s, 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}
@ -241,7 +242,8 @@ 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)
show := NewWithClient(wishedShow.ImdbID, client, pShow, wishedShow) poloShow := &polochon.Show{ImdbID: wishedShow.ImdbID}
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}
@ -307,7 +309,7 @@ func RefreshEpisodeHandler(env *web.Env, w http.ResponseWriter, r *http.Request)
} }
s := &Show{ s := &Show{
Show: pShow.Show, Show: &polochon.Show{ImdbID: id},
client: client, client: client,
pShow: pShow, pShow: pShow,
} }
@ -341,7 +343,6 @@ 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
@ -365,35 +366,29 @@ func RefreshEpisodeSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http
}, },
} }
sub, err := client.UpdateSubtitle(e, lang) refreshedSubs, err := client.UpdateSubtitles(e)
if err != nil { if err != nil {
return env.RenderError(w, err) return env.RenderError(w, err)
} }
// TODO: handle this with a better error subs := []subtitles.Subtitle{}
if sub == nil { for _, lang := range refreshedSubs {
return env.RenderJSON(w, nil) subtitleURL, _ := client.SubtitleURL(e, lang)
} subs = append(subs, subtitles.Subtitle{
Language: lang,
url, err := client.DownloadURL(sub) URL: subtitleURL,
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), VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, lang),
})
} }
return env.RenderJSON(w, s) return env.RenderJSON(w, subs)
} }
// 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 := polochon.Language(vars["lang"]) lang := vars["lang"]
season, _ := strconv.Atoi(vars["season"]) season, _ := strconv.Atoi(vars["season"])
episode, _ := strconv.Atoi(vars["episode"]) episode, _ := strconv.Atoi(vars["episode"])
@ -406,20 +401,13 @@ func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) e
return env.RenderError(w, err) return env.RenderError(w, err)
} }
s := &papi.Subtitle{ url, err := client.SubtitleURL(&papi.Episode{
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)
} }

View File

@ -62,14 +62,7 @@ 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(imdbID string, client *papi.Client, pShow *papi.Show, wShow *models.WishedShow) *Show { func NewWithClient(show *polochon.Show, 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,
@ -259,7 +252,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(pShow.ImdbID, client, pShow, wShow) show := NewWithClient(&polochon.Show{ImdbID: pShow.ImdbID}, client, pShow, wShow)
shows = append(shows, show) shows = append(shows, show)
} }
return shows, nil return shows, nil

View File

@ -1,10 +1,8 @@
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 {
*polochon.Subtitle Language string `json:"language"`
URL string `json:"url"` URL string `json:"url"`
VVTFile string `json:"vvt_file"` VVTFile string `json:"vvt_file"`
} }

2
dev.sh
View File

@ -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" go run ./*.go) (cd backend && CONFIG_FILE="../config.yml" fresh -c fresh.conf)
;; ;;
docker-db) docker-db)
_ensure_command docker _ensure_command docker

View File

@ -20,14 +20,10 @@ export function updateUser(data) {
); );
} }
export function deleteUser(username, userId) { export function deleteUser(username) {
return request( return request(
"ADMIN_DELETE_USER", "ADMIN_DELETE_USER",
configureAxios().delete("/admins/users/" + username), configureAxios().delete("/admins/users/" + username),
null, [() => getUsers()]
{
username,
id: userId,
}
); );
} }

View File

@ -1,25 +1,24 @@
import { configureAxios, request } from "../requests"; import { configureAxios, request } from "../requests";
export const searchMovieSubtitle = (imdbId, lang) => { export const searchMovieSubtitles = (imdbId) => {
return request( return request(
"MOVIE_SUBTITLES_UPDATE", "MOVIE_SUBTITLES_UPDATE",
configureAxios().post(`/movies/${imdbId}/subtitles/${lang}`), configureAxios().post(`/movies/${imdbId}/subtitles/refresh`),
null, null,
{ imdbId, lang } { imdbId: imdbId }
); );
}; };
export const searchEpisodeSubtitle = (imdbId, season, episode, lang) => { export const searchEpisodeSubtitles = (imdbId, season, episode) => {
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/${lang}`), configureAxios().post(`${url}/subtitles/refresh`),
null, null,
{ {
imdbId, imdbId: imdbId,
season, season: season,
episode, episode: episode,
lang,
} }
); );
}; };

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useEffect } 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,15 +10,9 @@ 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);
const fetchModules = () => { useEffect(() => {
dispatch(getAdminModules()); dispatch(getAdminModules());
}; }, [dispatch]);
return ( return <Modules modules={modules} isLoading={loading} />;
<Modules
modules={modules}
isLoading={loading}
fetchModules={fetchModules}
/>
);
}; };

View File

@ -51,7 +51,7 @@ export const UserEdit = ({ id }) => {
e.preventDefault(); e.preventDefault();
} }
if (confirmDelete) { if (confirmDelete) {
dispatch(deleteUser(user.name, id)); dispatch(deleteUser(name));
setModal(false); setModal(false);
} else { } else {
setConfirmDelete(true); setConfirmDelete(true);

View File

@ -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.object, subtitles: PropTypes.array,
}; };
const DownloadButton = ({ url }) => ( const DownloadButton = ({ url }) => (
@ -67,18 +67,19 @@ 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.object, subtitles: PropTypes.array,
}; };
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" />
{subtitles && {subs.map((sub, index) => (
[...subtitles.entries()].map(([lang, sub]) => (
<track <track
key={lang} key={index}
kind="subtitles" kind="subtitles"
label={sub.language} label={sub.language}
src={sub.vvt_file} src={sub.vvt_file}
@ -90,6 +91,6 @@ const Player = ({ url, subtitles }) => {
); );
}; };
Player.propTypes = { Player.propTypes = {
subtitles: PropTypes.object, subtitles: PropTypes.array,
url: PropTypes.string.isRequired, url: PropTypes.string.isRequired,
}; };

View File

@ -3,22 +3,25 @@ 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") {
@ -26,20 +29,10 @@ export const SubtitlesButton = ({
} }
}; };
const searchAll = () => { const count = subtitles && subtitles.length !== 0 ? subtitles.length : 0;
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}> <Dropdown drop="up" show={show} onToggle={onToggle} onSelect={onSelect}>
<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
@ -47,13 +40,9 @@ export const SubtitlesButton = ({
</Dropdown.Toggle> </Dropdown.Toggle>
<Dropdown.Menu> <Dropdown.Menu>
<Dropdown.Item eventKey={1} onClick={searchAll}> <Dropdown.Item eventKey={1} onClick={search}>
<div className="d-flex justify-content-between align-items-center"> <i className={`fa ${searching ? "fa-spin" : ""} fa-refresh mr-1`} />
<span>Automatic search</span> Automatic search
<div
className={`fa ${searching ? "fa-spin" : ""} fa-refresh ml-1`}
/>
</div>
</Dropdown.Item> </Dropdown.Item>
{count > 0 && ( {count > 0 && (
<React.Fragment> <React.Fragment>
@ -64,13 +53,10 @@ export const SubtitlesButton = ({
</React.Fragment> </React.Fragment>
)} )}
{count > 0 && {count > 0 &&
[...subtitles.entries()].map(([lang, subtitle]) => ( subtitles.map((subtitle, index) => (
<SubtitleEntry <Dropdown.Item href={subtitle.url} key={index}>
key={lang} {subtitle.language.split("_")[1]}
subtitle={subtitle} </Dropdown.Item>
searching={searching}
search={search}
/>
))} ))}
</Dropdown.Menu> </Dropdown.Menu>
</Dropdown> </Dropdown>
@ -78,42 +64,8 @@ export const SubtitlesButton = ({
); );
}; };
SubtitlesButton.propTypes = { SubtitlesButton.propTypes = {
subtitles: PropTypes.object, subtitles: PropTypes.array,
inLibrary: PropTypes.bool.isRequired, inLibrary: PropTypes.bool.isRequired,
fetchingSubtitles: PropTypes.array.isRequired, searching: PropTypes.bool.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,
};

View File

@ -1,23 +1,18 @@
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 s = size === 0 ? "" : prettySize(size); const metadata = [quality, container, videoCodec, audioCodec, releaseGroup]
const metadata = [quality, container, videoCodec, audioCodec, releaseGroup, s]
.filter((m) => m && m !== "") .filter((m) => m && m !== "")
.join(", "); .join(", ");
@ -34,5 +29,4 @@ PolochonMetadata.propTypes = {
videoCodec: PropTypes.string, videoCodec: PropTypes.string,
audioCodec: PropTypes.string, audioCodec: PropTypes.string,
releaseGroup: PropTypes.string, releaseGroup: PropTypes.string,
size: PropTypes.number,
}; };

View File

@ -53,7 +53,6 @@ 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}

View File

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React from "react";
import Loader from "../loader/loader"; import Loader from "../loader/loader";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
@ -7,29 +7,11 @@ 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, fetchModules }) => { const Modules = ({ isLoading, modules }) => {
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) => (
@ -41,7 +23,6 @@ const Modules = ({ isLoading, modules, fetchModules }) => {
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;

View File

@ -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 { searchMovieSubtitle } from "../../actions/subtitles"; import { searchMovieSubtitles } 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 fetchingSubtitles = useSelector( const searching = useSelector(
(state) => state.movies.movies.get(imdbId).fetchingSubtitles (state) => state.movies.movies.get(imdbId).fetchingSubtitles
); );
return ( return (
<SubtitlesButton <SubtitlesButton
inLibrary={inLibrary} inLibrary={inLibrary}
fetchingSubtitles={fetchingSubtitles} searching={searching}
subtitles={subtitles} subtitles={subtitles}
search={(lang) => dispatch(searchMovieSubtitle(imdbId, lang))} search={() => dispatch(searchMovieSubtitles(imdbId))}
/> />
); );
}; };

View File

@ -74,7 +74,6 @@ 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

View File

@ -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 { searchEpisodeSubtitle } from "../../../actions/subtitles"; import { searchEpisodeSubtitles } from "../../../actions/subtitles";
import { SubtitlesButton } from "../../buttons/subtitles"; import { SubtitlesButton } from "../../buttons/subtitles";
@ -10,27 +10,30 @@ 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 fetchingSubtitles = useSelector( const searching = useSelector((state) =>
(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( const subtitles = useSelector((state) =>
(state) => state.show.show.seasons.get(season).get(episode).subtitles state.show.show.seasons.get(season).get(episode).subtitles
? state.show.show.seasons.get(season).get(episode).subtitles
: []
); );
const search = (lang) => { const search = () => {
dispatch(searchEpisodeSubtitle(imdbId, season, episode, lang)); dispatch(searchEpisodeSubtitles(imdbId, season, episode));
}; };
return ( return (
<SubtitlesButton <SubtitlesButton
subtitles={subtitles} subtitles={subtitles}
inLibrary={inLibrary} inLibrary={inLibrary}
fetchingSubtitles={fetchingSubtitles} searching={searching}
search={search} search={search}
/> />
); );

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useEffect } 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,19 +12,15 @@ 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);
const fetchModules = () => { useEffect(() => {
dispatch(getUserModules()); dispatch(getUserModules());
}; }, [dispatch]);
return ( return (
<div> <div>
<UserEdit /> <UserEdit />
<PolochonList /> <PolochonList />
<Modules <Modules modules={modules} isLoading={modulesLoading} />
modules={modules}
isLoading={modulesLoading}
fetchModules={fetchModules}
/>
</div> </div>
); );
}; };

View File

@ -17,11 +17,6 @@ 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;

View File

@ -1,7 +1,6 @@
import { produce } from "immer"; import { produce } from "immer";
import { formatTorrents } from "../utils"; import { formatTorrents } from "../utils";
import { formatSubtitle, formatSubtitles } from "./utils";
const defaultState = { const defaultState = {
loading: false, loading: false,
@ -13,9 +12,8 @@ const defaultState = {
const formatMovie = (movie) => { const formatMovie = (movie) => {
movie.fetchingDetails = false; movie.fetchingDetails = false;
movie.fetchingSubtitles = []; movie.fetchingSubtitles = false;
movie.torrents = formatTorrents(movie); movie.torrents = formatTorrents(movie);
movie.subtitles = formatSubtitles(movie.subtitles);
return movie; return movie;
}; };
@ -89,28 +87,15 @@ 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":
let imdbId = action.payload.main.imdbId; draft.movies.get(action.payload.main.imdbId).fetchingSubtitles = true;
let lang = action.payload.main.lang;
draft.movies.get(imdbId).fetchingSubtitles.push(lang);
if (draft.movies.get(imdbId).subtitles.get(lang)) {
draft.movies.get(imdbId).subtitles.get(lang).searching = true;
}
break; break;
}
case "MOVIE_SUBTITLES_UPDATE_FULFILLED": { case "MOVIE_SUBTITLES_UPDATE_FULFILLED":
let imdbId = action.payload.main.imdbId; draft.movies.get(action.payload.main.imdbId).fetchingSubtitles = false;
let lang = action.payload.main.lang; draft.movies.get(action.payload.main.imdbId).subtitles =
let data = action.payload.response.data; action.payload.response.data;
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; break;
}
case "SELECT_MOVIE": case "SELECT_MOVIE":
draft.selectedImdbId = action.payload.imdbId; draft.selectedImdbId = action.payload.imdbId;

View File

@ -1,7 +1,6 @@
import { produce } from "immer"; import { produce } from "immer";
import { formatTorrents } from "../utils"; import { formatTorrents } from "../utils";
import { formatSubtitle, formatSubtitles } from "./utils";
const defaultState = { const defaultState = {
loading: false, loading: false,
@ -11,12 +10,10 @@ 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;
// Holds the languages of the subtitles currently fetching episode.fetchingSubtitles = false;
episode.fetchingSubtitles = [];
}; };
export default (state = defaultState, action) => export default (state = defaultState, action) =>
@ -101,42 +98,22 @@ 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(season) .get(action.payload.main.season)
.get(episode) .get(action.payload.main.episode).fetchingSubtitles = true;
.fetchingSubtitles.push(lang);
if (draft.show.seasons.get(season).get(episode).subtitles.get(lang)) {
draft.show.seasons
.get(season)
.get(episode)
.subtitles.get(lang).searching = true;
}
break; break;
}
case "EPISODE_SUBTITLES_UPDATE_FULFILLED": { 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 draft.show.seasons
.get(season) .get(action.payload.main.season)
.get(episode) .get(action.payload.main.episode).subtitles =
.fetchingSubtitles.filter((l) => l != lang); action.payload.response.data;
if (data) {
draft.show.seasons draft.show.seasons
.get(season) .get(action.payload.main.season)
.get(episode) .get(action.payload.main.episode).fetchingSubtitles = false;
.subtitles.set(lang, formatSubtitle(data));
}
break; break;
} }
default: default:
return draft; return draft;
} }

View File

@ -1,22 +0,0 @@
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;
};

View File

@ -146,8 +146,3 @@ 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;
}