Merge branch 'master' of github.com:KevinMidboe/seasonedShows

This commit is contained in:
2017-09-12 20:15:41 +02:00
23 changed files with 773 additions and 312 deletions

View File

@@ -1,6 +1,8 @@
# *Seasoned*: an intelligent organizer for your shows
# 🌶 seasonedShows
Your customly seasoned movie and show requester, downloader and organizer
*Seasoned* is a intelligent organizer for your tv show episodes. It is made to automate and simplify to process of renaming and moving newly downloaded tv show episodes following Plex file naming and placement.
## About
seasonedShows is a intelligent organizer for your tv show episodes. It is made to automate and simplify to process of renaming and moving newly downloaded tv show episodes following Plex file naming and placement.
## Architecture
The flow of the system will first check for new folders in your tv shows directory, if a new file is found it's contents are analyzed, stored and tweets suggested changes to it's contents to use_admin.
@@ -8,3 +10,21 @@ The flow of the system will first check for new folders in your tv shows directo
Then there is a script for looking for replies on twitter by user_admin, if caanges are needed, it handles the changes specified and updates dtabbase.
After approval by user the files are modified and moved to folders in resptected area. If error occours, pasteee link if log is sent to user.
#### External
+ Seasoned: request, discover and manage.
+ Stray: Overview of downloaded episodes before they are organized.
+ (+) Admin Panel: Overview of all stray episodes/movies.
#### Api
+ All communication between public website to server.
+ Plex: All querying to what is localy available in your plex library.
+ Stray (seasoned) -> also calls services (moveStray) through api.
+ Tmdb: Requesting information from tmdb.
+ (+) Admin Panel: Use secure login and session tokens to handle logged in viewer.
#### Services
+ Parse directories for new content.
+ Extract and save in db information about stray item.
+ Move a confirmed stray item.
+ (+) Search for torrents matching new content.

1
_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

@@ -1,102 +1,32 @@
import React from 'react';
import glamorous from 'glamorous';
// StyleComponents
import mediaResultItem from './styledComponents/mediaResultItem.jsx';
import movieStyle from './styles/movieObjectStyle.jsx';
class MovieObject {
constructor(object) {
this.id = object.id;
this.title = object.title;
this.year = object.year;
this.type = object.type;
// Check if object.poster != undefined
this.poster = object.poster;
this.matchedInPlex = object.matchedInPlex;
this.overview = object.overview;
this.summary = object.summary;
}
requestExisting(movie) {
console.log('Exists', movie);
}
requestMovie(id) {
fetch('https://apollo.kevinmidboe.com/api/v1/plex/request/' + id, {
requestMovie() {
// fetch('https://apollo.kevinmidboe.com/api/v1/plex/request/' + id, {
fetch('http://localhost:31459/api/v1/plex/request/' + this.id + '?type='+this.type, {
method: 'POST'
});
}
getElement() {
// TODO move this to separate files.
var resultItem = {
maxWidth: '95%',
margin: '0 auto',
minHeight: '230px'
}
var movie_content = {
marginLeft: '15px'
}
var resultTitle = {
color: 'black',
fontSize: '2em',
}
var resultPoster = {
float: 'left',
zIndex: '3',
position: 'relative',
marginRight: '30px'
}
var resultPosterImg = {
border: '2px none',
borderRadius: '2px',
width: '150px'
}
var buttons = {
paddingTop: '20px'
}
var requestButton = {
color: '#e9a131',
marginRight: '10px',
background: 'white',
border: '#e9a131 2px solid',
borderRadius: '4px',
textAlign: 'center',
padding: '10px',
minWidth: '100px',
float: 'left',
fontSize: '13px',
fontWeight: '800',
cursor: 'pointer'
}
var tmdbButton = {
color: '#00d17c',
marginRight: '10px',
background: 'white',
border: '#00d17c 2px solid',
borderRadius: '4px',
textAlign: 'center',
padding: '10px',
minWidth: '100px',
float: 'left',
fontSize: '13px',
fontWeight: '800',
cursor: 'pointer'
}
var row = {
width: '100%'
}
var itemDivider = {
width: '90%',
borderBottom: '1px solid grey',
margin: '2rem auto'
}
// TODO set the poster image async by updating the dom after this is returned
if (this.poster == null || this.poster == undefined) {
var posterPath = 'https://openclipart.org/image/2400px/svg_to_png/211479/Simple-Image-Not-Found-Icon.png'
@@ -106,10 +36,10 @@ class MovieObject {
var foundInPlex;
if (this.matchedInPlex) {
foundInPlex = <button onClick={() => {this.requestExisting(this)}}
style={requestButton}><span>Request Anyway</span></button>;
style={movieStyle.requestButton}><span>Request Anyway</span></button>;
} else {
foundInPlex = <button onClick={() => {this.requestMovie(this.id)}}
style={requestButton}><span>&#x0002B; Request</span></button>;
foundInPlex = <button onClick={() => {this.requestMovie()}}
style={movieStyle.requestButton}><span>&#x0002B; Request</span></button>;
}
var themoviedbLink = 'https://www.themoviedb.org/movie/' + this.id
@@ -117,27 +47,29 @@ class MovieObject {
return (
<div>
<div style={resultItem} key={this.id}>
<div style={resultPoster}>
<img style={resultPosterImg} id='poster' src={posterPath}></img>
<div style={movieStyle.resultItem} key={this.id}>
<div style={movieStyle.resultPoster}>
<img style={movieStyle.resultPosterImg} id='poster' src={posterPath}></img>
</div>
<div>
<span style={resultTitle}>{this.title} ({this.year})</span>
<span style={movieStyle.resultTitle}>{this.title} ({this.year})</span>
<br></br>
<span>{this.overview}</span>
<span>{this.summary}</span>
<br></br>
<span className='imdbLogo'>
</span>
<div style={buttons}>
<div style={movieStyle.buttons}>
{foundInPlex}
<a href={themoviedbLink}><button style={tmdbButton}><span>Info</span></button></a>
<a href={themoviedbLink}>
<button style={movieStyle.tmdbButton}><span>Info</span></button>
</a>
</div>
</div>
</div>
<div style={row}>
<div style={itemDivider}></div>
<div style={movieStyle.row}>
<div style={movieStyle.itemDivider}></div>
</div>
</div>)

View File

@@ -2,67 +2,128 @@ import React from 'react';
import MovieObject from './MovieObject.jsx';
// TODO add option for searching multi, movies or tv shows
// StyleComponents
import searchStyle from './styles/searchRequestStyle.jsx';
import URI from 'urijs';
// TODO add option for searching multi, movies or tv shows
class SearchRequest extends React.Component {
constructor(props){
constructor(props){
super(props)
// Constructor with states holding the search query and the element of reponse.
this.state = {
searchQuery: '',
responseMovieList: null,
movieFilter: true,
tvshowFilter: false
showFilter: false,
discoverType: '',
page: 1
}
this.allowedDiscoverTypes = [
'discover', 'popular', 'nowplaying', 'upcoming'
]
// this.baseUrl = 'https://apollo.kevinmidboe.com/api/v1/';
this.baseUrl = 'http://localhost:31459/api/v1/tmdb/';
this.URLs = {
request: 'https://apollo.kevinmidboe.com/api/v1/plex/request?query=',
sendRequest: 'https://apollo.kevinmidboe.com/api/v1/plex/request?query='
// request: 'https://apollo.kevinmidboe.com/api/v1/plex/request?page='+this.state.page+'&query=',
request: 'http://localhost:31459/api/v1/plex/request?page='+this.state.page+'&query=',
// upcoming: 'https://apollo.kevinmidboe.com/api/v1/tmdb/upcoming',
upcoming: 'http://localhost:31459/api/v1/tmdb/upcoming',
// sendRequest: 'https://apollo.kevinmidboe.com/api/v1/plex/request?query='
sendRequest: 'http://localhost:31459/api/v1/plex/request?query='
}
}
componentDidMount(){
var that = this;
this.setState({responseMovieList: null})
var that = this;
// this.setState({responseMovieList: null})
this.fetchDiscover('upcoming');
}
// Handles all errors of the response of a fetch call
handleErrors(response) {
if (!response.ok) {
throw Error(response.status);
}
return response;
if (!response.ok) {
throw Error(response.status);
}
return response;
}
fetchDiscover(queryDiscoverType) {
if (this.allowedDiscoverTypes.indexOf(queryDiscoverType) === -1)
throw Error('Invalid discover type: ' + queryDiscoverType);
var uri = new URI(this.baseUrl);
uri.segment(queryDiscoverType)
uri = uri.setSearch('page', this.state.page);
if (this.state.showFilter)
uri = uri.addSearch('type', 'show');
console.log(uri)
this.setState({
responseMovieList: 'Loading...'
});
fetch(uri)
// Check if the response is ok
.then(response => this.handleErrors(response))
.then(response => response.json()) // Convert to json object and pass to next then
.then(data => { // Parse the data of the JSON response
// If it is something here it updates the state variable with the HTML list of all
// movie objects that where returned by the search request
if (data.results.length > 0) {
this.setState({
responseMovieList: data.results.map(item => this.createMovieObjects(item))
})
}
})
// If the --------
.catch(error => {
console.log(error)
this.setState({
responseMovieList: <h1>Not Found</h1>
})
console.log('Error submit: ', error.toString());
});
}
fetchQuery() {
let url = this.URLs.request + this.state.searchQuery
if (this.state.tvshowFilter) {
if (this.state.showFilter) {
url = url + '&type=tv'
}
fetch(url)
// Check if the response is ok
.then(response => this.handleErrors(response))
.then(response => response.json()) // Convert to json object and pass to next then
.then(data => { // Parse the data of the JSON response
// If it is something here it updates the state variable with the HTML list of all
// movie objects that where returned by the search request
if (data.length > 0) {
this.setState({
responseMovieList: data.map(item => this.createMovieObjects(item))
})
}
})
// If the --------
.catch(error => {
console.log(error)
this.setState({
responseMovieList: <h1>Not Found</h1>
})
fetch(url)
// Check if the response is ok
.then(response => this.handleErrors(response))
.then(response => response.json()) // Convert to json object and pass to next then
.then(data => { // Parse the data of the JSON response
// If it is something here it updates the state variable with the HTML list of all
// movie objects that where returned by the search request
console.log(data)
if (data.length > 0) {
this.setState({
responseMovieList: data.map(item => this.createMovieObjects(item))
})
}
})
// If the --------
.catch(error => {
console.log(error)
this.setState({
responseMovieList: <h1>Not Found</h1>
})
console.log('Error submit: ', error.toString());
});
console.log('Error submit: ', error.toString());
});
}
// Updates the internal state of the query search field.
@@ -93,186 +154,77 @@ class SearchRequest extends React.Component {
})
console.log(this.state.movieFilter);
}
else if (filterType == 'tvshows') {
else if (filterType == 'shows') {
this.setState({
tvshowFilter: !this.state.tvshowFilter
showFilter: !this.state.showFilter
})
console.log(this.state.tvshowFilter);
console.log(this.state.showFilter);
}
}
pageBackwards() {
if (this.state.page > 1) {
console.log('backwards');
this.state.page--;
this.getUpcoming();
}
console.log(this.state.page)
}
pageForwards() {
this.state.page++;
this.getUpcoming();
console.log('forwards');
console.log(this.state.page)
}
render(){
var body = {
fontFamily: "'Open Sans', sans-serif",
backgroundColor: '#f7f7f7',
margin: 0,
padding: 0,
minHeight: '100%',
position: 'relative'
}
var backgroundHeader = {
width: '100%',
minHeight: '400px',
backgroundColor: '#011c23',
zIndex: 1,
position: 'absolute'
}
var requestWrapper = {
top: '300px',
width: '90%',
maxWidth: '1200px',
margin: 'auto',
paddingTop: '20px',
backgroundColor: 'white',
position: 'relative',
zIndex: '10',
boxShadow: '0 2px 10px grey'
}
var pageTitle = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
var pageTitleSpan = {
color: 'white',
fontSize: '3em',
marginTop: '4vh',
marginBottom: '6vh'
}
var box = {
width: '90%',
height: '50px',
maxWidth: '1200px',
margin: '0 auto'
}
var container = {
verticalAlign: 'middle',
whiteSpace: 'nowrap',
position: 'relative',
display: 'flex',
justifyContent: 'center'
}
var searchIcon = {
position: 'absolute',
marginLeft: '17px',
marginTop: '17px',
zIndex: '1',
color: '#4f5b66'
}
var searchBar = {
width: '60%',
minWidth: '120px',
height: '50px',
background: '#ffffff',
border: 'none',
fontSize: '10pt',
float: 'left',
color: '#63717f',
paddingLeft: '45px',
borderRadius: '5px',
marginRight: '15px'
}
var searchFilter = {
color: 'white',
fontSize: '1em',
paddingTop: '12px',
marginBottom: '12px',
marginLeft: '10px',
cursor: 'pointer'
}
var hvrUnderlineFromCenter = {
color: 'white',
fontSize: '1em',
paddingTop: '12px',
marginBottom: '12px',
marginLeft: '10px',
cursor: 'pointer',
display: 'inline-block',
verticalAlign: 'middle',
WebkitTransform: 'perspective(1px) translateZ(0)',
transform: 'perspective(1px) translateZ(0)',
boxShadow: '0 0 1px transparent',
position: 'relative',
overflow: 'hidden',
':before': {
content: "",
position: 'absolute',
zIndex: '-1',
left: '50%',
right: '50%',
bottom: '0',
background: '#00d17c',
height: '2px',
WebkitTransitionProperty: 'left, right',
transitionProperty: 'left, right',
WebkitTransitionDuration: '0.3s',
transitionDuration: '0.3s',
WebkitTransitionTimingFunction: 'ease-out',
transitionTimingFunction: 'ease-out'
},
':hover:before': {
left: 0,
right: 0
},
'focus:before': {
left: 0,
right: 0
},
'active:before': {
left: 0,
right: 0
}
}
return(
<div style={body}>
<div className='backgroundHeader' style={backgroundHeader}>
<div className='pageTitle' style={pageTitle}>
<span style={pageTitleSpan}>Request new movies or tv shows</span>
<div style={searchStyle.body}>
<button onClick={() => {this.fetchDiscover('discover')}}>Discover</button>
<button onClick={() => {this.fetchDiscover('popular')}}>Popular</button>
<button onClick={() => {this.fetchDiscover('nowplaying')}}>Nowplaying</button>
<button onClick={() => {this.fetchDiscover('upcoming')}}>Upcoming</button>
<div className='backgroundHeader' style={searchStyle.backgroundHeader}>
<div className='pageTitle' style={searchStyle.pageTitle}>
<span style={searchStyle.pageTitleSpan}>Request new movies or tv shows</span>
</div>
<div className='box' style={box}>
<div style={container}>
<span style={searchIcon}><i className="fa fa-search"></i></span>
<div className='box' style={searchStyle.box}>
<div style={searchStyle.container}>
<span style={searchStyle.searchIcon}><i className="fa fa-search"></i></span>
<input style={searchBar} type="text" id="search" placeholder="Search for new content..."
<input style={searchStyle.searchBar} type="text" id="search" placeholder="Search for new content..."
onKeyPress={(event) => this._handleQueryKeyPress(event)}
onChange={event => this.updateQueryState(event)}
value={this.state.searchQuery}/>
<span style={searchFilter}
<span style={searchStyle.searchFilter}
className="search_category hvrUnderlineFromCenter"
onClick={() => {this.toggleFilter('movies')}}
id="category_active">Movies</span>
<span style={searchFilter}
<span style={searchStyle.searchFilter}
className="search_category hvrUnderlineFromCenter"
onClick={() => {this.toggleFilter('tvshows')}}
onClick={() => {this.toggleFilter('shows')}}
id="category_inactive">TV Shows</span>
</div>
</div>
</div>
<div id='requestMovieList' ref='requestMovieList' style={requestWrapper}>
<div id='requestMovieList' ref='requestMovieList' style={searchStyle.requestWrapper}>
{this.state.responseMovieList}
</div>
<div>
<button onClick={() => {this.pageBackwards()}}>Back</button>
<button onClick={() => {this.pageForwards()}}>Forward</button>
</div>
</div>
)
}
}
export default SearchRequest;

View File

@@ -0,0 +1,80 @@
export default {
resultItem: {
maxWidth: '95%',
margin: '0 auto',
minHeight: '230px'
},
resultItem: {
maxWidth: '95%',
margin: '0 auto',
minHeight: '230px'
},
movie_content: {
marginLeft: '15px'
},
resultTitle: {
color: 'black',
fontSize: '2em',
},
resultPoster: {
float: 'left',
zIndex: '3',
position: 'relative',
marginRight: '30px'
},
resultPosterImg: {
border: '2px none',
borderRadius: '2px',
width: '150px'
},
buttons: {
paddingTop: '20px'
},
requestButton: {
color: '#e9a131',
marginRight: '10px',
background: 'white',
border: '#e9a131 2px solid',
borderRadius: '4px',
textAlign: 'center',
padding: '10px',
minWidth: '100px',
float: 'left',
fontSize: '13px',
fontWeight: '800',
cursor: 'pointer'
},
tmdbButton: {
color: '#00d17c',
marginRight: '10px',
background: 'white',
border: '#00d17c 2px solid',
borderRadius: '4px',
textAlign: 'center',
padding: '10px',
minWidth: '100px',
float: 'left',
fontSize: '13px',
fontWeight: '800',
cursor: 'pointer'
},
row: {
width: '100%'
},
itemDivider: {
width: '90%',
borderBottom: '1px solid grey',
margin: '2rem auto'
}
}

View File

@@ -0,0 +1,134 @@
export default {
body: {
fontFamily: "'Open Sans', sans-serif",
backgroundColor: '#f7f7f7',
margin: 0,
padding: 0,
minHeight: '100%',
position: 'relative'
},
backgroundHeader: {
width: '100%',
minHeight: '400px',
backgroundColor: '#011c23',
zIndex: 1,
position: 'absolute'
},
requestWrapper: {
top: '300px',
width: '90%',
maxWidth: '1200px',
margin: 'auto',
paddingTop: '20px',
backgroundColor: 'white',
position: 'relative',
zIndex: '10',
boxShadow: '0 2px 10px grey'
},
pageTitle: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
},
pageTitleSpan: {
color: 'white',
fontSize: '3em',
marginTop: '4vh',
marginBottom: '6vh'
},
box: {
width: '90%',
height: '50px',
maxWidth: '1200px',
margin: '0 auto'
},
container: {
verticalAlign: 'middle',
whiteSpace: 'nowrap',
position: 'relative',
display: 'flex',
justifyContent: 'center'
},
searchIcon: {
position: 'absolute',
marginLeft: '17px',
marginTop: '17px',
zIndex: '1',
color: '#4f5b66'
},
searchBar: {
width: '60%',
minWidth: '120px',
height: '50px',
background: '#ffffff',
border: 'none',
fontSize: '10pt',
float: 'left',
color: '#63717f',
paddingLeft: '45px',
borderRadius: '5px',
marginRight: '15px'
},
searchFilter: {
color: 'white',
fontSize: '1em',
paddingTop: '12px',
marginBottom: '12px',
marginLeft: '10px',
cursor: 'pointer'
},
hvrUnderlineFromCenter: {
color: 'white',
fontSize: '1em',
paddingTop: '12px',
marginBottom: '12px',
marginLeft: '10px',
cursor: 'pointer',
display: 'inline-block',
verticalAlign: 'middle',
WebkitTransform: 'perspective(1px) translateZ(0)',
transform: 'perspective(1px) translateZ(0)',
boxShadow: '0 0 1px transparent',
position: 'relative',
overflow: 'hidden',
':before': {
content: "",
position: 'absolute',
zIndex: '-1',
left: '50%',
right: '50%',
bottom: '0',
background: '#00d17c',
height: '2px',
WebkitTransitionProperty: 'left, right',
transitionProperty: 'left, right',
WebkitTransitionDuration: '0.3s',
transitionDuration: '0.3s',
WebkitTransitionTimingFunction: 'ease-out',
transitionTimingFunction: 'ease-out'
},
':hover:before': {
left: 0,
right: 0
},
'focus:before': {
left: 0,
right: 0
},
'active:before': {
left: 0,
right: 0
}
}
}

View File

@@ -9,7 +9,7 @@
"cross-env": "^3.1.3",
"express": "~4.0.0",
"mongoose": "^3.6.13",
"moviedb": "^0.2.7",
"moviedb": "^0.2.10",
"node-cache": "^4.1.1",
"nodemailer": "^4.0.1",
"python-shell": "^0.4.0",

View File

@@ -1,12 +1,14 @@
class MediaInfo {
constructor(device, platform) {
this.device = undefined;
this.platform = undefined;
this.ip = undefined;
this.product = undefined;
this.title = undefined;
this.state = undefined;
constructor() {
this.duration = undefined;
this.height = undefined;
this.width = undefined;
this.bitrate = undefined;
this.resolution = undefined;
this.framerate = undefined;
this.protocol = undefined;
this.container = undefined;
this.audioCodec = undefined;
}
}

View File

@@ -5,11 +5,13 @@ const convertStreamToUser = require('src/plex/stream/convertStreamToUser');
const ConvertStreamToPlayback = require('src/plex/stream/convertStreamToPlayback');
function convertPlexToStream(plexStream) {
const stream = convertPlexToSeasoned(plexStream);
stream.mediaInfo = convertStreamToMediaInfo(plexStream.Media);
const stream = convertPlexToSeasoned(plexStream)
const plexStreamMedia = plexStream.Media[0]
stream.mediaInfo = convertStreamToMediaInfo(plexStreamMedia);
stream.player = convertStreamToPlayer(plexStream.Player);
stream.user = convertStreamToUser(plexStream.User);
stream.playback = new ConvertStreamToPlayback(plexStream.Media.Part);
stream.playback = new ConvertStreamToPlayback(plexStreamMedia.Part[0]);
return stream;
}

View File

@@ -6,6 +6,7 @@ function convertStreamToMediaInfo(plexStream) {
mediaInfo.duration = plexStream.duration;
mediaInfo.height = plexStream.height;
mediaInfo.width = plexStream.width;
if (plexStream.bitrate) {
mediaInfo.bitrate = plexStream.bitrate;
}

View File

@@ -51,7 +51,7 @@ class PlexRepository {
}
})
.catch((err) => {
throw new Error(err);
throw new Error('Error handling plex playing. Error: ' + err);
})
}
}

View File

@@ -67,13 +67,16 @@ class RequestRepository {
});
}
sendRequest(identifier) {
/**
* Send request for given media id.
* @param {identifier, type} the id of the media object and type of media must be defined
* @returns {Promise} If nothing has gone wrong.
*/
sendRequest(identifier, type) {
// TODO add to DB so can have a admin page
// TODO try a cache hit on the movie item
tmdb.lookup(identifier).then(movie => {
console.log(movie.title)
tmdb.lookup(identifier, type).then(movie => {
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
@@ -110,6 +113,7 @@ class RequestRepository {
})
// TODO add better response when done.
return Promise.resolve();
}

View File

@@ -4,6 +4,11 @@ class convertStreamToPlayback {
this.width = plexStream.width;
this.height = plexStream.height;
this.decision = plexStream.decision;
this.audioProfile = plexStream.audioProfile;
this.videoProfile = plexStream.videoProfile;
this.duration = plexStream.duration;
this.container = plexStream.container;
}
}

View File

@@ -1,8 +1,8 @@
const Movie = require('src/media_classes/movie');
const Show = require('src/media_classes/show');
function convertTmdbToSeasoned(tmdbObject) {
const mediaType = tmdbObject.media_type;
function convertTmdbToSeasoned(tmdbObject, strictType=undefined) {
const mediaType = strictType || tmdbObject.media_type;
// There are many diff types of content, we only want to look at movies and tv shows
if (mediaType === 'movie') {
@@ -16,6 +16,7 @@ function convertTmdbToSeasoned(tmdbObject) {
const movie = new Movie(title, year, mediaType);
movie.id = tmdbObject.id;
movie.summary = tmdbObject.overview;
movie.rating = tmdbObject.vote_average;
movie.poster = tmdbObject.poster_path;
@@ -27,11 +28,12 @@ function convertTmdbToSeasoned(tmdbObject) {
return movie;
}
else if (mediaType === 'tv') {
else if (mediaType === 'tv' || mediaType === 'show') {
const year = new Date(tmdbObject.first_air_date).getFullYear();
const show = new Show(tmdbObject.title, year, mediaType);
const show = new Show(tmdbObject.name, year, 'show');
show.id = tmdbObject.id;
show.summary = tmdbObject.overview;
show.rating = tmdbObject.vote_average;
show.poster = tmdbObject.poster_path;

View File

@@ -1,7 +1,10 @@
const moviedb = require('moviedb');
const convertTmdbToSeasoned = require('src/tmdb/convertTmdbToSeasoned');
var methodTypes = { 'movie': 'searchMovie', 'tv': 'searchTv', 'multi': 'searchMulti', 'movieInfo': 'movieInfo',
'tvInfo': 'tvInfo' };
var methodTypes = { 'movie': 'searchMovie', 'show': 'searchTv', 'multi': 'searchMulti', 'movieInfo': 'movieInfo',
'tvInfo': 'tvInfo', 'upcomingMovies': 'miscUpcomingMovies', 'discoverMovie': 'discoverMovie',
'discoverShow': 'discoverTv', 'popularMovies': 'miscPopularMovies', 'popularShows': 'miscPopularTvs',
'nowPlayingMovies': 'miscNowPlayingMovies', 'nowAiringShows': 'tvOnTheAir', 'movieSimilar': 'movieSimilar',
'showSimilar': 'tvSimilar' };
class TMDB {
constructor(apiKey, tmdbLibrary) {
@@ -9,7 +12,7 @@ class TMDB {
}
search(text, page = 1, type = 'multi') {
const query = { query: text, page };
const query = { 'query': text, 'page': page };
return Promise.resolve()
.then(() => this.tmdb(type, query))
.catch(() => { throw new Error('Could not search for movies.'); })
@@ -26,27 +29,235 @@ class TMDB {
}
/**
* Retrive list of discover section of movies from TMDB.
* @param {Page, type} the page number to specify in the request for discover,
* and type for movie or show
* @returns {Promise} dict with query results, current page and total_pages
*/
discover(page, type='movie') {
// Sets the tmdb function type to the corresponding type from query
var tmdbType;
if (type === 'movie') {
tmdbType = 'discoverMovie';
} else if (type === 'show') {
tmdbType = 'discoverShow';
} else {
// Throw error if invalid type from query
return Promise.resolve()
.then(() => {
throw new Error('Invalid type declaration.')
})
}
// Build a query for tmdb with pagenumber
const query = { 'page': page }
return Promise.resolve()
.then(() => this.tmdb(tmdbType, query))
.catch(() => { throw new Error('Could not fetch discover.'); })
.then((response) => {
try {
// Return a object that has the results and a variable for page, total_pages
// and seasonedResponse
var seasonedResponse = response.results.map((result) => {
return convertTmdbToSeasoned(result, type); }
);
return { 'results': seasonedResponse,
'page': response.page, 'total_pages': response.total_pages };
} catch (error) {
console.log(error)
throw new Error('Error while parsing discover list.')
}
});
}
/**
* Retrive list of popular section of movies or shows from TMDB.
* @param {Page, type} the page number to specify in the request for popular,
* and type for movie or show
* @returns {Promise} dict with query results, current page and total_pages
*/
// TODO add filter for language
popular(page, type='movie') {
// Sets the tmdb function type to the corresponding type from query
var tmdbType;
if (type === 'movie') {
tmdbType = 'popularMovies';
} else if (type === 'show') {
tmdbType = 'popularShows';
} else {
// Throw error if invalid type from query
return Promise.resolve()
.then(() => {
throw new Error('Invalid type declaration.')
})
}
// Build a query for tmdb with pagenumber
const query = { 'page': page }
return Promise.resolve()
.then(() => this.tmdb(tmdbType, query))
.catch(() => { throw new Error('Could not fetch popular.'); })
.then((response) => {
try {
var seasonedResponse = response.results.map((result) => {
return convertTmdbToSeasoned(result, type); }
);
// Return a object that has the results and a variable for page, total_pages
// and seasonedResponse
return { 'results': seasonedResponse,
'page': response.page, 'total_pages': response.total_pages };
} catch (error) {
console.log(error)
throw new Error('Error while parsing discover list.')
}
});
}
/**
* Retrive list of now playing/airing section of movies or shows from TMDB.
* @param {Page, type} the page number to specify in the request for now playing/airing,
* and type for movie or show
* @returns {Promise} dict with query results, current page and total_pages
*/
// TODO add filter for language
nowplaying(page, type='movie') {
// Sets the tmdb function type to the corresponding type from query
var tmdbType;
if (type === 'movie') {
tmdbType = 'nowPlayingMovies';
} else if (type === 'show') {
tmdbType = 'nowAiringShows';
} else {
// Throw error if invalid type from query
return Promise.resolve()
.then(() => {
throw new Error('Invalid type declaration.')
})
}
// Build a query for tmdb with pagenumber
const query = { 'page': page }
return Promise.resolve()
.then(() => this.tmdb(tmdbType, query))
.catch(() => { throw new Error('Could not fetch popular.'); })
.then((response) => {
try {
var seasonedResponse = response.results.map((result) => {
return convertTmdbToSeasoned(result, type); }
);
// Return a object that has the results and a variable for page, total_pages
// and seasonedResponse
return { 'results': seasonedResponse,
'page': response.page, 'total_pages': response.total_pages };
} catch (error) {
console.log(error)
throw new Error('Error while parsing discover list.')
}
});
}
/**
* Retrive list of upcmoing movies from TMDB.
* @param {Page} the page number to specify in the request for upcoming movies
* @returns {Promise} dict with query results, current page and total_pages
*/
// TODO add filter for language
upcoming(page) {
const query = { 'page': page }
return Promise.resolve()
.then(() => this.tmdb('upcomingMovies', query))
.catch(() => { throw new Error('Could not fetch upcoming movies.'); })
.then((response) => {
try {
var seasonedResponse = response.results.map((result) => {
return convertTmdbToSeasoned(result, 'movie'); }
);
// Return a object that has the results and a variable for page, total_pages
// and seasonedResponse
return { 'results': seasonedResponse,
'page': response.page, 'total_pages': response.total_pages };
} catch (parseError) {
throw new Error('Error while parsing upcoming movies list.')
}
});
}
/**
* Retrive list of upcmoing movies from TMDB.
* @param {Page} the page number to specify in the request for upcoming movies
* @returns {Promise} dict with query results, current page and total_pages
*/
// TODO add filter for language
similar(identifier, type) {
var tmdbType;
if (type === 'movie') {
tmdbType = 'movieSimilar';
} else if (type === 'show') {
tmdbType = 'showSimilar';
} else {
// Throw error if invalid type from query
return Promise.resolve()
.then(() => {
throw new Error('Invalid type declaration.')
})
}
const query = { id: identifier }
return Promise.resolve()
.then(() => this.tmdb(tmdbType, query))
.catch(() => { throw new Error('Could not fetch upcoming movies.'); })
.then((response) => {
try {
var seasonedResponse = response.results.map((result) => {
return convertTmdbToSeasoned(result, type); }
);
// Return a object that has the results and a variable for page, total_pages
// and seasonedResponse
return { 'results': seasonedResponse,
'page': response.page, 'total_pages': response.total_pages };
} catch (parseError) {
throw new Error('Error while parsing silimar media list.')
}
});
}
/**
* Retrieve a specific movie by id from TMDB.
* @param {Number} identifier of the movie you want to retrieve
* @returns {Promise} succeeds if movie was found
*/
lookup(identifier, type = 'movie') {
if (type === 'movie') { type = 'movieInfo'}
else if (type === 'tv') { type = 'tvInfo'}
lookup(identifier, queryType = 'movie') {
var type;
if (queryType === 'movie') { type = 'movieInfo'}
else if (queryType === 'show') { type = 'tvInfo'}
else {
return Promise.resolve()
.then(() => {
throw new Error('Invalid type declaration.')
})
}
const query = { id: identifier };
return Promise.resolve()
.then(() => this.tmdb(type, query))
.catch(() => { throw new Error('Could not find a movie with that id.'); })
.then((response) => {
try {
return convertTmdbToSeasoned(response);
} catch (parseError) {
throw new Error('Could not parse movie.');
}
});
.then(() => this.tmdb(type, query))
.catch(() => { throw new Error('Could not find a movie with that id.'); })
.then((response) => {
try {
var car = convertTmdbToSeasoned(response, queryType);
console.log(car);
return car;
} catch (parseError) {
throw new Error('Could not parse movie.');
}
});
}
// TODO ADD CACHE LOOKUP
tmdb(method, argument) {
return new Promise((resolve, reject) => {
const callback = (error, reponse) => {
@@ -58,6 +269,7 @@ class TMDB {
if (!argument) {
this.tmdbLibrary[methodTypes[method]](callback);
// this.tmdbLibrary['miscUpcomingMovies']
} else {
this.tmdbLibrary[methodTypes[method]](argument, callback);
}

View File

@@ -15,7 +15,8 @@ var allowedOrigins = ['https://kevinmidboe.com', 'http://localhost:8080']
router.use(function(req, res, next) {
console.log('Something is happening.');
// TODO add logging of all incoming
console.log('Request: ', req.originalUrl);
var origin = req.headers.origin;
if (allowedOrigins.indexOf(origin) > -1) {
res.setHeader('Access-Control-Allow-Origin', origin);
@@ -40,6 +41,12 @@ router.post('/v1/plex/request/:mediaId', require('./controllers/plex/submitReque
router.get('/v1/plex/hook', require('./controllers/plex/hookDump.js'));
router.get('/v1/tmdb/search', require('./controllers/tmdb/searchMedia.js'));
router.get('/v1/tmdb/discover', require('./controllers/tmdb/discoverMedia.js'));
router.get('/v1/tmdb/popular', require('./controllers/tmdb/popularMedia.js'));
router.get('/v1/tmdb/nowplaying', require('./controllers/tmdb/nowPlayingMedia.js'));
router.get('/v1/tmdb/upcoming', require('./controllers/tmdb/getUpcoming.js'));
router.get('/v1/tmdb/similar/:mediaId', require('./controllers/tmdb/searchSimilar.js'));
router.get('/v1/tmdb/:mediaId', require('./controllers/tmdb/readMedia.js'));
router.post('/v1/git/dump', require('./controllers/git/dumpHook.js'));

View File

@@ -11,8 +11,9 @@ const requestRepository = new RequestRepository();
function submitRequestController(req, res) {
// This is the id that is the param of the url
const id = req.params.mediaId;
const type = req.query.type;
requestRepository.sendRequest(id)
requestRepository.sendRequest(id, type)
.then(() => {
res.send({ success: true, message: 'Media item sucessfully requested!' });
})

View File

@@ -0,0 +1,21 @@
const configuration = require('src/config/configuration').getInstance();
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve a list of movies or shows in discover section in TMDB
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function discoverMediaController(req, res) {
const { page, type } = req.query;
tmdb.discover(page, type)
.then((results) => {
res.send(results);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = discoverMediaController;

View File

@@ -0,0 +1,21 @@
const configuration = require('src/config/configuration').getInstance();
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve upcoming movies
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function getUpcomingController(req, res) {
const { page } = req.query;
tmdb.upcoming(page)
.then((results) => {
res.send(results);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = getUpcomingController;

View File

@@ -0,0 +1,21 @@
const configuration = require('src/config/configuration').getInstance();
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve nowplaying movies / now airing shows
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function nowPlayingMediaController(req, res) {
const { page, type } = req.query;
tmdb.nowplaying(page, type)
.then((results) => {
res.send(results);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = nowPlayingMediaController;

View File

@@ -0,0 +1,21 @@
const configuration = require('src/config/configuration').getInstance();
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve information for a movie
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function popularMediaController(req, res) {
const { page, type } = req.query;
tmdb.popular(page, type)
.then((results) => {
res.send(results);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = popularMediaController;

View File

@@ -14,7 +14,7 @@ function searchMoviesController(req, res) {
Promise.resolve()
.then(() => tmdb.search(query, page, type))
.then((movies) => {
if (movies.length > 0) {
if (movies !== undefined || movies.length > 0) {
res.send(movies);
} else {
res.status(404).send({ success: false, error: 'Search query did not return any results.'})

View File

@@ -0,0 +1,22 @@
const configuration = require('src/config/configuration').getInstance();
const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve similar movies or shows
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function similarMediaController(req, res) {
const mediaId = req.params.mediaId;
const { type } = req.query;
tmdb.similar(mediaId, type)
.then((results) => {
res.send(results);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = similarMediaController;