Add popular list for Sonarr import

This commit is contained in:
mjrode
2019-04-13 21:13:39 -05:00
parent 72c59dd746
commit 23cdf48293
14 changed files with 304 additions and 34 deletions

View File

@@ -7,6 +7,7 @@ import Header from './Header';
import Hero from './Hero';
import Plex from './plex/Plex';
import SimilarList from './SimilarList';
import PopularList from './PopularList';
class App extends Component {
componentDidMount() {
@@ -26,6 +27,7 @@ class App extends Component {
path="/similar/:show"
render={props => <SimilarList {...props} />}
/>
<Route path="/popular" component={PopularList} />
</div>
</BrowserRouter>
</div>

View File

@@ -1,9 +1,10 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';
import '../css/materialize.css';
class Header extends Component {
renderContent() {
const isMobile = window.innerWidth < 480;
switch (this.props.auth) {
case null:
return;
@@ -14,16 +15,21 @@ class Header extends Component {
</li>
);
default:
return (
<div>
<li key="1" style={{margin: '0 10px'}}>
<Link to={'/most-watched'}>Most Watched</Link>
</li>
<li key="2" style={{margin: '0 10px'}}>
<a href="/api/auth/logout">Logout</a>
</li>
</div>
);
if (!isMobile) {
return (
<div className="hide-mobile">
<li key="1" style={{margin: '0 10px'}}>
<Link to={'/most-watched'}>Most Watched</Link>
</li>
<li key="2" style={{margin: '0 10px'}}>
<Link to={'/popular'}>Popular</Link>
</li>
<li key="3" style={{margin: '0 10px'}}>
<a href="/api/auth/logout">Logout</a>
</li>
</div>
);
}
}
}
render() {

View File

@@ -5,8 +5,8 @@ import {connect} from 'react-redux';
import Header from './helpers/Header';
import styles from '../css/materialize.css';
import '../css/materialize.css';
import {Link} from 'react-router-dom';
class MediaCard extends Component {
render() {
const show = this.props.media;

View File

@@ -0,0 +1,143 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {withStyles} from '@material-ui/core/styles';
import {connect} from 'react-redux';
import Header from './helpers/Header';
import styles from '../css/materialize.css';
import {ToastContainer} from 'react-toastify';
import * as actions from '../actions';
import {Link} from 'react-router-dom';
import 'react-toastify/dist/ReactToastify.css';
class PopularCard extends Component {
renderContent() {
if (
this.props.loading &&
this.props.currentShow === this.props.media.name
) {
return (
<div className="progress">
<div className="indeterminate" />
</div>
);
}
}
renderToast() {
if (this.props.currentShow === this.props.media.name) {
return <ToastContainer />;
}
}
render() {
const show = this.props.media;
const isMobile = window.innerWidth < 480;
if (!isMobile) {
return (
<div>
{this.renderToast()}
{this.renderContent()}
<div className="row hide-mobile">
<div className="col s12 ">
<div className="card medium horizontal">
<div
className="card-image"
style={{
boxShadow:
'0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12), 0 1px 5px 0 rgba(0,0,0,0.2)',
}}
>
<img
src={`https://image.tmdb.org/t/p/w500/${show.poster_path}`}
alt="pic"
className="circle"
/>
</div>
<div className="card-stacked">
<div className="card-content">
<div className="header">
<Header text={show.name} />
</div>
<p>{show.overview}</p>
</div>
<div className="card-action">
<h6 className="robots abs">
Rating: {` ${show.vote_average} `}| Popularity:{' '}
{` ${show.popularity}`}
</h6>
<button
className="waves-effect waves-light btn-large right Button margin-left"
style={{backgroundColor: '#f9a1bc'}}
type="submit"
name="action"
key={show.name}
onClick={() =>
this.props.addSeries({showName: show.name})
}
>
Add to Sonarr
<i className="material-icons right">send</i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
return (
<div className="row hide-desktop">
<ToastContainer />
<div className="col s12 m12">
<div className="card ">
<div
className="card-image"
style={{
boxShadow:
'0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12), 0 1px 5px 0 rgba(0,0,0,0.2)',
}}
>
<img
src={`https://image.tmdb.org/t/p/w500/${show.poster_path}`}
alt="pic"
className="circle"
/>
</div>
<div className="card-content">
<p>{show.overview}</p>
</div>
<div className="card-action flex-center">
<Link
to={`/similar/${show.title}`}
className="waves-effect waves-light btn-large center Button"
style={{backgroundColor: '#f9a1bc'}}
key={show.name}
onClick={() => this.props.addSeries({showName: show.name})}
>
<i className="material-icons right">send</i>Add to Sonarr
</Link>
</div>
</div>
</div>
</div>
);
}
}
PopularCard.propTypes = {
classes: PropTypes.object.isRequired,
};
function mapStateToProps(state) {
return {
loading: state.sonarr.loading,
sonarrAddSeries: state.sonarr.sonarrAddSeries,
currentShow: state.plex.currentShow,
};
}
export default connect(
mapStateToProps,
actions,
)(withStyles(styles)(PopularCard));

View File

@@ -0,0 +1,50 @@
import React, {Component} from 'react';
import {Redirect} from 'react-router-dom';
import {withStyles} from '@material-ui/core/styles';
import {connect} from 'react-redux';
import styles from '../css/materialize.css.js';
import axios from 'axios';
import PopularCard from './PopularCard';
import * as actions from '../actions';
class PopularList extends Component {
state = {
shows: [],
};
componentDidMount() {
this.getSimilar();
}
getSimilar = async () => {
const res = await axios.get('/api/moviedb/tv/popular');
const shows = res.data;
this.setState({shows: shows});
};
render() {
if (!this.props.auth) {
return <Redirect to="/" />;
}
if (this.state.shows.length > 0) {
const mediaList = this.state.shows.map((show, index) => {
return <PopularCard media={show} key={index} />;
});
return <div>{mediaList}</div>;
}
return (
<div className="progress">
<div className="indeterminate" />
</div>
);
}
}
function mapStateToProps({plex, auth, sonarr}) {
return {loading: plex.loading, auth, sonarrAddSeries: sonarr.sonarrAddSeries};
}
export default connect(
mapStateToProps,
actions,
)(withStyles(styles)(PopularList));

View File

@@ -0,0 +1,24 @@
import React from 'react';
import {Redirect, Route} from 'react-router-dom';
import {connect} from 'react-redux';
// Utils
const PrivateRoute = ({component: Component, ...rest}) => (
<Route
{...rest}
render={props =>
props.auth !== null ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: '/login',
state: {from: props.location},
}}
/>
)
}
/>
);
export default PrivateRoute;

View File

@@ -7,6 +7,7 @@ import styles from '../css/materialize.css';
import {ToastContainer} from 'react-toastify';
import * as actions from '../actions';
import 'react-toastify/dist/ReactToastify.css';
import {Link} from 'react-router-dom';
class MediaCard extends Component {
renderContent() {
@@ -106,23 +107,16 @@ class MediaCard extends Component {
<div className="card-content">
<p>{show.overview}</p>
</div>
<div className="card-action">
{' '}
<div className="card-action">
<i className="material-icons left">live_tv</i>Rating:
{` ${show.vote_average}`} Popularity: {` ${show.popularity}`}
<button
className="waves-effect waves-light btn-large right Button margin-left"
style={{backgroundColor: '#f9a1bc'}}
type="submit"
name="action"
key={show.name}
onClick={() => this.props.addSeries({showName: show.name})}
>
Add to Sonarr
<i className="material-icons right">send</i>
</button>
</div>
<div className="card-action flex-center">
<Link
to={`/similar/${show.title}`}
className="waves-effect waves-light btn-large center Button"
style={{backgroundColor: '#f9a1bc'}}
key={show.name}
onClick={() => this.props.addSeries({showName: show.name})}
>
<i className="material-icons right">send</i>Add to Sonarr
</Link>
</div>
</div>
</div>

View File

@@ -1,6 +1,7 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PlexTokenForm from './PlexTokenForm';
import {Link} from 'react-router-dom';
import ImportPlexLibrary from './ImportPlexLibrary';
import MediaList from '../MediaList';
@@ -20,6 +21,14 @@ class Plex extends Component {
<div>
<ImportPlexLibrary />
<MediaList />
<div className="row flex-center">
<Link
to="/"
className="waves-effect waves-light btn-large min-button-width"
>
<i className="material-icons left">send</i>Get Started
</Link>
</div>
</div>
);
}

View File

@@ -10,7 +10,14 @@ import TextHeader from '../helpers/Header';
import styles from '../../css/materialize.css';
class PlexTokenForm extends React.Component {
state = {email: '', password: '', sonarrUrl: '', sonarrApiKey: ''};
state = {
email: '',
password: '',
sonarrUrl: '',
sonarrApiKey: '',
errorMessage: '',
redirect: false,
};
onFormSubmit = event => {
event.preventDefault();
@@ -18,8 +25,13 @@ class PlexTokenForm extends React.Component {
};
getPlexToken = async params => {
await axios.get('/api/plex/token', {params});
window.location.reload();
const res = await axios.get('/api/plex/token', {params});
console.log('response', res.data);
if (res.data.includes('Invalid')) {
this.setState({errorMessage: res.data});
} else {
window.location.reload();
}
};
render() {
@@ -37,6 +49,9 @@ class PlexTokenForm extends React.Component {
<TextHeader text="Connect to Plex and Sonarr" />
</div>
<hr />
<div className="flex-center">
<h5 className="robots center ">{this.state.errorMessage}</h5>
</div>
</div>
<div className="section center-align">
<div className="row">

View File

@@ -3,6 +3,7 @@ import {types} from '../actions/index';
export default function(state = {}, action) {
switch (action.type) {
case types.FETCH_USER:
console.log('actionpayload', action.payload);
return action.payload || false;
default:
return state;

View File

@@ -5,5 +5,6 @@ const router = Router();
router.get('/tv/search', movieDbService.searchTv);
router.get('/tv/similar', movieDbService.similarTv);
router.get('/tv/popular', movieDbService.popularTv);
export default router;

View File

@@ -10,6 +10,17 @@ const searchTv = async (req, res) => {
res.json(response);
};
const popularTv = async (req, res) => {
const response = await movieDbApi.popularTv();
const library = await sonarrService.getSeries(req.user);
const jsonLibrary = JSON.parse(library);
const libraryTitles = jsonLibrary.map(show => show.title.toLowerCase());
const filteredResponse = response.results.filter(
show => !libraryTitles.includes(show.name.toLowerCase()),
);
res.json(filteredResponse);
};
const similarTv = async (req, res) => {
const {showName} = req.query;
const searchResponse = await movieDbApi.searchTv(showName);
@@ -22,7 +33,6 @@ const similarTv = async (req, res) => {
// });
// Use Sonarr list instead
const libraryTitles = jsonLibrary.map(show => show.title.toLowerCase());
console.log('titles', libraryTitles);
const filteredResponse = similarResponse.results.filter(
show => !libraryTitles.includes(show.name.toLowerCase()),
);
@@ -32,4 +42,5 @@ const similarTv = async (req, res) => {
export default {
searchTv,
similarTv,
popularTv,
};

View File

@@ -4,6 +4,15 @@ import models from '../../db/models';
import MovieDb from 'moviedb-promise';
const mdb = new MovieDb(config.server.movieApiKey);
const popularTv = async () => {
try {
const response = await mdb.miscPopularTvs();
return response;
} catch (error) {
helpers.handleError(error, 'popularTv');
}
};
const searchTv = async showName => {
try {
const response = await mdb.searchTv({
@@ -29,4 +38,4 @@ const similarTV = async showId => {
}
};
export default {searchTv, similarTV};
export default {searchTv, similarTV, popularTv};

View File

@@ -8,6 +8,10 @@ const getAuthToken = async (req, res) => {
try {
const {email, password, sonarrUrl, sonarrApiKey} = req.query;
const plexToken = await auth.fetchToken(email, password);
console.log('plex token', plexToken.includes('401'));
if (plexToken.includes('401')) {
return res.json('Invalid username or password.');
}
const plexUrl = await auth.plexUrl(plexToken);
const [rowsUpdate, updatedUser] = await models.User.update(
{plexUrl, plexToken, sonarrUrl, sonarrApiKey},
@@ -16,6 +20,7 @@ const getAuthToken = async (req, res) => {
return res.json(updatedUser);
} catch (error) {
console.log('error in auth', error);
return res.status(201).json(error.message);
}
};