diff --git a/package.json b/package.json
index 5543d11..cbcf306 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,6 @@
"react-dom": "^15.3.2",
"react-infinite-scroller": "^1.0.4",
"react-loading": "^0.0.9",
- "react-player": "^0.14.1",
"react-redux": "^4.4.6",
"react-redux-form": "^1.2.4",
"react-router": "^3.0.0",
@@ -27,7 +26,8 @@
"react-router-redux": "^4.0.7",
"redux": "^3.6.0",
"redux-logger": "^2.7.4",
- "redux-thunk": "^2.1.0"
+ "redux-thunk": "^2.1.0",
+ "universal-cookie": "^2.0.7"
},
"devDependencies": {
"axios": "^0.15.2",
diff --git a/src/internal/auth/auth.go b/src/internal/auth/auth.go
index 0597981..b072cb1 100644
--- a/src/internal/auth/auth.go
+++ b/src/internal/auth/auth.go
@@ -80,14 +80,26 @@ func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username,
// CurrentUser returns the logged in username from session and verifies the token
func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (User, error) {
+ var tokenStr string
h := req.Header.Get("Authorization")
- // No user logged
- if h == "" {
- return nil, nil
+ if h != "" {
+ // Get the token from the header
+ tokenStr = strings.Replace(h, "Bearer ", "", -1)
}
- // Get the token from the header
- tokenStr := strings.Replace(h, "Bearer ", "", -1)
+ // If the token string is still empty, check in the cookies
+ if tokenStr == "" {
+ tokenCookie, err := req.Cookie("token")
+ if err != nil || tokenCookie == nil {
+ return nil, nil
+ }
+ tokenStr = tokenCookie.Value
+ }
+
+ // No user logged
+ if tokenStr == "" {
+ return nil, nil
+ }
// Keyfunc to decode the token
var keyfunc jwt.Keyfunc = func(token *jwt.Token) (interface{}, error) {
diff --git a/src/internal/movies/handlers.go b/src/internal/movies/handlers.go
index fda3117..49cc7b1 100644
--- a/src/internal/movies/handlers.go
+++ b/src/internal/movies/handlers.go
@@ -14,6 +14,7 @@ import (
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/backend"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/config"
+ "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/subtitles"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
)
@@ -331,3 +332,30 @@ func RefreshMovieSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http.R
return env.RenderOK(w, "Subtitles refreshed")
}
+
+// DownloadVVTSubtitle returns a vvt subtitle for the movie
+func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) error {
+ vars := mux.Vars(r)
+ id := vars["id"]
+ lang := vars["lang"]
+
+ // Get the user
+ v := auth.GetCurrentUser(r, env.Log)
+ user, ok := v.(*users.User)
+ if !ok {
+ return fmt.Errorf("invalid user type")
+ }
+
+ // Create a new papi client
+ client, err := user.NewPapiClient()
+ if err != nil {
+ return env.RenderError(w, err)
+ }
+
+ url, err := client.SubtitleURL(&papi.Movie{ImdbID: id}, lang)
+ if err != nil {
+ return env.RenderError(w, err)
+ }
+
+ return subtitles.ConvertSubtitle(url, w)
+}
diff --git a/src/internal/movies/movies.go b/src/internal/movies/movies.go
index e4147c4..9b07363 100644
--- a/src/internal/movies/movies.go
+++ b/src/internal/movies/movies.go
@@ -34,6 +34,7 @@ func (m *Movie) MarshalJSON() ([]byte, error) {
type Subtitle struct {
Language string `json:"language"`
URL string `json:"url"`
+ VVTFile string `json:"vvt_file"`
}
var subtitles []Subtitle
// If the episode is present, fill the downloadURL
@@ -46,6 +47,7 @@ func (m *Movie) MarshalJSON() ([]byte, error) {
subtitles = append(subtitles, Subtitle{
Language: l,
URL: subtitleURL,
+ VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", m.ImdbID, l),
})
}
}
diff --git a/src/internal/shows/episodes.go b/src/internal/shows/episodes.go
index b3535e7..269e59f 100644
--- a/src/internal/shows/episodes.go
+++ b/src/internal/shows/episodes.go
@@ -2,6 +2,7 @@ package shows
import (
"encoding/json"
+ "fmt"
"github.com/Sirupsen/logrus"
"github.com/odwrtw/papi"
@@ -26,6 +27,7 @@ func (e *Episode) MarshalJSON() ([]byte, error) {
type Subtitle struct {
Language string `json:"language"`
URL string `json:"url"`
+ VVTFile string `json:"vvt_file"`
}
var subtitles []Subtitle
// If the episode is present, fill the downloadURL
@@ -46,6 +48,7 @@ func (e *Episode) MarshalJSON() ([]byte, error) {
subtitles = append(subtitles, Subtitle{
Language: l,
URL: subtitleURL,
+ VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, l),
})
}
}
diff --git a/src/internal/shows/handlers.go b/src/internal/shows/handlers.go
index 3d3a011..5a3b9fe 100644
--- a/src/internal/shows/handlers.go
+++ b/src/internal/shows/handlers.go
@@ -9,6 +9,7 @@ import (
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/backend"
+ "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/subtitles"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
@@ -384,3 +385,36 @@ func RefreshEpisodeSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http
return env.RenderOK(w, "Subtitles refreshed")
}
+
+// DownloadVVTSubtitle returns a vvt subtitle for the movie
+func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) error {
+ vars := mux.Vars(r)
+ id := vars["id"]
+ lang := vars["lang"]
+ season, _ := strconv.Atoi(vars["season"])
+ episode, _ := strconv.Atoi(vars["episode"])
+
+ // Get the user
+ v := auth.GetCurrentUser(r, env.Log)
+ user, ok := v.(*users.User)
+ if !ok {
+ return fmt.Errorf("invalid user type")
+ }
+
+ // Create a new papi client
+ client, err := user.NewPapiClient()
+ if err != nil {
+ return env.RenderError(w, err)
+ }
+
+ url, err := client.SubtitleURL(&papi.Episode{
+ ShowImdbID: id,
+ Season: season,
+ Episode: episode,
+ }, lang)
+ if err != nil {
+ return env.RenderError(w, err)
+ }
+
+ return subtitles.ConvertSubtitle(url, w)
+}
diff --git a/src/internal/subtitles/handler.go b/src/internal/subtitles/handler.go
new file mode 100644
index 0000000..6cb79b9
--- /dev/null
+++ b/src/internal/subtitles/handler.go
@@ -0,0 +1,43 @@
+package subtitles
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gregdel/srt2vtt"
+)
+
+// ConvertSubtitle downloads and converts a subtitle from srt to vvt
+func ConvertSubtitle(url string, w http.ResponseWriter) error {
+ // Client with compression disabled
+ c := &http.Client{
+ Transport: &http.Transport{DisableCompression: true},
+ }
+
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf("Invalid subitle response code: %d", resp.StatusCode)
+ }
+
+ reader, err := srt2vtt.NewReader(resp.Body)
+ if err != nil {
+ return err
+ }
+
+ _, err = reader.WriteTo(w)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/src/public/js/app.js b/src/public/js/app.js
index 3324d12..366c3b8 100644
--- a/src/public/js/app.js
+++ b/src/public/js/app.js
@@ -11,9 +11,11 @@ import 'file-loader?name=[name].png!../img/apple-touch-icon.png'
import 'file-loader?name=[name].png!../img/favicon-16x16.png'
import 'file-loader?name=[name].png!../img/favicon-32x32.png'
import 'file-loader?name=[name].png!../img/favicon.ico'
-import 'file-loader?name=[name].png!../img/manifest.json'
import 'file-loader?name=[name].png!../img/safari-pinned-tab.svg'
+// Import manifest
+import 'file-loader?name=[name].json!../manifest.json'
+
// Styles
import '../less/app.less'
@@ -79,15 +81,15 @@ export function startPollingTorrents() {
)
}
-var pollingTorrentsId;
-const loginCheck = function(nextState, replace, next, f) {
+// This function returns true if the user is logged in, false otherwise
+function isLoggedIn() {
const state = store.getState();
const isLogged = state.userStore.isLogged;
let token = localStorage.getItem('token');
// Let's check if the user has a token, if he does let's assume he's logged
// in. If that's not the case he will be logged out on the fisrt query
- if (token !== "") {
+ if (token && token !== "") {
store.dispatch({
type: 'USER_SET_TOKEN',
payload: {
@@ -96,10 +98,23 @@ const loginCheck = function(nextState, replace, next, f) {
});
}
- if (!isLogged && token === "") {
+ if (isLogged || (token && token !== "")) {
+ return true
+ }
+
+ return false
+}
+
+var pollingTorrentsId;
+const loginCheck = function(nextState, replace, next, f = null) {
+ const loggedIn = isLoggedIn();
+ if (!loggedIn) {
replace('/users/login');
} else {
- f();
+ if (f) {
+ f();
+ }
+
// Poll torrents once logged
if (!pollingTorrentsId) {
// Fetch the torrents every 10s
@@ -112,18 +127,42 @@ const loginCheck = function(nextState, replace, next, f) {
next();
}
+const defaultRoute = '/movies/explore/yts/seeds';
const routes = {
path: '/',
component: App,
- indexRoute: {onEnter: ({params}, replace) => replace('/movies/explore/yts/seeds')},
+ indexRoute: {onEnter: ({params}, replace) => replace(defaultRoute)},
childRoutes: [
- { path: '/users/login' , component: UserLoginForm },
- { path: '/users/signup' , component: UserSignUp },
- { path: '/users/edit' , component: UserEdit },
- { path: '/users/signup' , component: UserSignUp },
+ {
+ path: '/users/signup',
+ component: UserSignUp
+ },
+ {
+ path: '/users/login',
+ component: UserLoginForm,
+ onEnter: function(nextState, replace, next) {
+ if (isLoggedIn()) {
+ // User is already logged in, redirect him to the default route
+ replace(defaultRoute);
+ }
+ next();
+ },
+ },
+ {
+ path: '/users/edit',
+ component: UserEdit,
+ onEnter: function(nextState, replace, next) {
+ loginCheck(nextState, replace, next);
+ },
+ },
{
path: '/users/logout',
onEnter: function(nextState, replace, next) {
+ // Stop polling
+ if (pollingTorrentsId !== null) {
+ clearInterval(pollingTorrentsId);
+ pollingTorrentsId = null;
+ }
store.dispatch(actionCreators.userLogout());
replace('/users/login');
next();
diff --git a/src/public/js/components/buttons/download.js b/src/public/js/components/buttons/download.js
index 1089be6..8c18ece 100644
--- a/src/public/js/components/buttons/download.js
+++ b/src/public/js/components/buttons/download.js
@@ -1,5 +1,4 @@
import React from 'react'
-import ReactPlayer from 'react-player'
import { Button, Dropdown, MenuItem, Modal } from 'react-bootstrap'
@@ -56,12 +55,46 @@ export default class DownloadButton extends React.Component {