Stuff stuff stuff #44

Merged
PouuleT merged 24 commits from update-node into master 2021-08-30 12:59:08 +00:00
15 changed files with 376 additions and 42 deletions
Showing only changes of commit b6be9488c9 - Show all commits

View File

@ -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)

View File

@ -41,6 +41,7 @@ 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/{lang}", movies.RefreshMovieSubtitlesHandler).WithRole(models.UserRole).Methods("POST") env.Handle("/movies/{id:tt[0-9]+}/subtitles/{lang}", movies.RefreshMovieSubtitlesHandler).WithRole(models.UserRole).Methods("POST")

View File

@ -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),

View File

@ -33,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";
@ -66,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"

View File

@ -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: "",
};

View 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>
);
};

View File

@ -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,
}; };

View File

@ -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 />

View File

@ -18,7 +18,14 @@ export const TorrentGroup = ({ torrentKey }) => {
const title = (torrent) => { const title = (torrent) => {
switch (torrent.type) { switch (torrent.type) {
case "movie": case "movie":
return <span>{torrent.video.title}</span>; return (
<Link
className="link-unstyled"
to={`/movies/details/${torrent.video.imdb_id}`}
>
{torrent.video.title}
</Link>
);
case "episode": case "episode":
return ( return (
<Link <Link

View File

@ -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,

View 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;
}
});

View File

@ -1,7 +1,6 @@
import { produce } from "immer"; import { produce } from "immer";
import { formatTorrents } from "../utils"; import { formatSubtitle, formatMovie } from "./utils";
import { formatSubtitle, formatSubtitles } from "./utils";
const defaultState = { const defaultState = {
loading: false, loading: false,
@ -11,14 +10,6 @@ const defaultState = {
exploreOptions: {}, exploreOptions: {},
}; };
const formatMovie = (movie) => {
movie.fetchingDetails = false;
movie.fetchingSubtitles = [];
movie.torrents = formatTorrents(movie);
movie.subtitles = formatSubtitles(movie.subtitles);
return movie;
};
const formatMovies = (movies = []) => { const formatMovies = (movies = []) => {
let allMoviesInPolochon = true; let allMoviesInPolochon = true;
movies.map((movie) => { movies.map((movie) => {
@ -66,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)
@ -92,6 +90,10 @@ export default (state = defaultState, action) =>
case "MOVIE_SUBTITLES_UPDATE_PENDING": { case "MOVIE_SUBTITLES_UPDATE_PENDING": {
let imdbId = action.payload.main.imdbId; let imdbId = action.payload.main.imdbId;
let lang = action.payload.main.lang; let lang = action.payload.main.lang;
if (!draft.movies.get(imdbId)) {
break;
}
draft.movies.get(imdbId).fetchingSubtitles.push(lang); draft.movies.get(imdbId).fetchingSubtitles.push(lang);
if (draft.movies.get(imdbId).subtitles.get(lang)) { if (draft.movies.get(imdbId).subtitles.get(lang)) {
draft.movies.get(imdbId).subtitles.get(lang).searching = true; draft.movies.get(imdbId).subtitles.get(lang).searching = true;
@ -103,6 +105,10 @@ export default (state = defaultState, action) =>
let imdbId = action.payload.main.imdbId; let imdbId = action.payload.main.imdbId;
let lang = action.payload.main.lang; let lang = action.payload.main.lang;
let data = action.payload.response.data; let data = action.payload.response.data;
if (!draft.movies.get(imdbId)) {
break;
}
draft.movies.get(imdbId).fetchingSubtitles = draft.movies draft.movies.get(imdbId).fetchingSubtitles = draft.movies
.get(imdbId) .get(imdbId)
.fetchingSubtitles.filter((l) => l != lang); .fetchingSubtitles.filter((l) => l != lang);

View File

@ -1,7 +1,6 @@
import { produce } from "immer"; import { produce } from "immer";
import { formatTorrents } from "../utils"; import { formatTorrents, formatSubtitle, formatSubtitles } from "./utils";
import { formatSubtitle, formatSubtitles } from "./utils";
const defaultState = { const defaultState = {
loading: false, loading: false,

View File

@ -20,3 +20,32 @@ export const formatSubtitle = (subtitle) => {
subtitle.searching = false; subtitle.searching = false;
return subtitle; 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;
};

View File

@ -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);