91 Commits

Author SHA1 Message Date
Renovate Bot
e981b32f6b Add renovate.json 2019-07-13 04:53:32 +00:00
3a9131a022 Removed, commented and added comments 2019-07-02 23:53:26 +02:00
77433e8505 Query of plex search is now encoded 2019-07-02 23:53:08 +02:00
3845000b3f Allow filtering for requested items by status 2019-06-28 23:32:11 +02:00
071fd54825 Pages for requests are only calulated for items not yet downloaded and use ceil isteadof floor to get last items on a page 2019-06-28 22:51:09 +02:00
537f237e83 Updated lock file 2019-06-28 22:45:31 +02:00
d3bc854e03 Pirate repository has relative and use virtuale python when running python commands 2019-06-28 22:44:39 +02:00
15826a00ba plexRepo now using class instance plexIp address 2019-06-28 22:42:11 +02:00
4019d63f3b Created example config and added development config to gitignore 2019-06-28 22:39:24 +02:00
91dcfaccb9 Rewrote posting request controller to handle body parameters and use new requestsRepository function istead of plexRepository functions 2019-06-28 22:31:52 +02:00
270a259cee Request list also gets and returns total pages 2019-06-28 21:51:43 +02:00
162d20ae52 Submitting requests now use requests repository 2019-06-28 21:51:11 +02:00
9f1badc1b1 Get a request item by id and type 2019-06-28 19:21:54 +02:00
ac027a97d6 Added pagination and removed sort & filtering for requested items 2019-06-28 18:50:30 +02:00
127db88ded Renamed function name and comment to make for sense. Also deconstruct page from query 2019-06-28 18:45:53 +02:00
4b07434615 Imported configuration with incorrect name 2019-06-05 00:05:54 +02:00
5d6f2baa34 plex hook should have post not get 2019-06-05 00:00:31 +02:00
1a1a7328a3 Map results with total_results before returing. TODO this should be mapped with all wanted list return vars 2019-06-04 23:54:39 +02:00
b9dec2344e Added timeout to plex requests and include error in error message when unable to search 2019-06-04 23:53:54 +02:00
476a34fb69 Changed the order of execution between getting tmdb movie and searching plex for it. Now we await tmdb movie and then check if exists in plex. This is better when we miss plex request 2019-06-04 23:47:10 +02:00
e3ed08e8dd Now a plex ip address is dynamically passed into the plexrepository, fetched from the config 2019-06-04 23:45:22 +02:00
70f6497404 All converter function from tmdb to movie, show and person takes optional cast object and maps to response 2019-06-04 23:35:21 +02:00
99bab3fb73 Movie and show can also return credits for a item. Enabled by query parameter credits=true 2019-06-04 23:32:38 +02:00
e6796aff8b Hotfix 🧯 for returning new poster object variable 2019-06-04 21:41:10 +02:00
784aa2616a Fetchall gets docstring 2018-11-12 01:20:18 +01:00
7cb55ce054 Fetchall uses promises smarter. Now the utils functions also return promises to be able to nicely chain the steps a request needs to go through. Promise all lets us wait for all items return in the map function. Without the map function would return immidiately and resolve before the map operation completed. 2018-11-10 20:26:28 +01:00
87eb6de802 🔨 test now requests with id in body not in query params. 2018-11-10 01:57:19 +01:00
840816c930 request returns all requested items. Optional sort, query and filter params. 2018-11-10 01:50:24 +01:00
91d238de7c Request id is now passed as body param. Database default timestamp value changed to epoch time. 2018-11-09 22:13:00 +01:00
0ac17d3d0a Removed unused const declaration. 2018-11-01 00:24:47 +01:00
87c76e3f1d Tests now suppoer the new list endpoints. Also updated response for interstellar query (movieInfo). 2018-11-01 00:18:54 +01:00
e64c4d5d01 Lists are now reachable by movie or show / listname. Endpoints added & removed outdated comments. 2018-11-01 00:17:51 +01:00
22e57c03de Controller for movie and shows. Each have multiple small export functions; one for each list search type 2018-11-01 00:16:56 +01:00
d80386da40 Implementing lists lookups for movie and shows. Add new cachetags for the lists & created a helper function for returning response with convertFunction as parameter. 2018-11-01 00:15:49 +01:00
e7c66af3f6 Merge branch 'master' into api/v2 2018-10-30 21:02:47 +01:00
8ece7b84c4 test configuration also gets plex ip parameter. 2018-10-30 20:38:05 +01:00
4250b1bd17 request endpoint finds type by body not query. Better error handling on what goes wrong if incorrect type or missing body parameter. 2018-10-30 20:34:26 +01:00
7e46d32e30 More unit tests for checking correct definition of movie. Changed some test to match update functions in tmdb. 2018-10-30 20:32:55 +01:00
5a48158f07 Request now happens at /request with id parameter and query for type selection. Only allows movie or show type and is static set in the controller. AddRequest adds tmdb item to database with time of request. 2018-10-30 19:20:52 +01:00
161a466ab7 Rewrote how local plex library is indexed and what it returns. After searching plex the response is separated into three classes by types (movie, show & episode). Plex also has a function for inputing a (tmdb)movie object and searching for matches of name & type in plex. If a match the object matchedInPlex variable is set to true. 2018-10-29 21:01:16 +01:00
8f5bd44e4d Added endpoint for new plex search. 2018-10-29 20:57:22 +01:00
5d8869e042 Rewrote every function for searching and looking up items from tmdb library. Now there are separate functions for the four categories of search and three for info (multi, movie, show & person). Each function now has its own endpoint and matching controller. Converting tmdb results into a class has been alterted from using three classes; movie, show & person, and each have each their own convertTmdbTo function. Now the structure of the three types are more structured and no longer a single "seasoned" class object. 2018-10-29 20:55:18 +01:00
90b8ee005e Changed moviedb package to my own fork of it. The old package had vulnerabilities and needed updating. 2018-10-29 20:49:21 +01:00
1b0525063f New parameter in config and added axios package for new plex connect command. 2018-10-29 20:47:57 +01:00
41d6bba743 v2 endpoints added for clearer intent in endpoints. Two new controller categories; info and search. 2018-10-28 12:21:47 +01:00
8977a4b195 Merge pull request #109 from KevinMidboe/package/upgrade
Changed moviedb node package to my own fork (km-tmdb) with updated to…
2018-10-26 01:01:43 +02:00
7e0da028de Imported new version of moviedb package 2018-10-26 00:59:22 +02:00
2250cf2c4b Changed moviedb node package to my own fork (km-tmdb) with updated to vulnerability in the superagent package 2018-10-26 00:20:37 +02:00
b2bd7b6a1f Update README.md 2018-08-27 11:58:16 +02:00
a2ad7f5628 Removed old content 2018-08-27 11:56:28 +02:00
f85d31991f Removed versioneye badge 2018-08-27 11:54:48 +02:00
08dc2153ae Updated build config for codeclimate test reporting. 2018-08-26 10:56:50 +02:00
bc64e69b3e Fixed syntax error. 2018-08-13 00:01:51 +02:00
a29bca7361 Controller now expects three parameters for posting to addMagnet; magnet, name and tmdb_id. 2018-08-12 23:59:26 +02:00
d84aa5f173 Merge branch 'master' of github.com:KevinMidboe/seasonedShows 2018-08-12 23:55:40 +02:00
48ebd398bc Changed name values of tables. 2018-08-12 23:50:07 +02:00
1b95103acd Removed test that caused breaking changes. 2018-08-12 23:31:18 +02:00
6a1d6687eb Updated gitmodules 2018-07-28 19:00:32 +02:00
e849864bc2 Added delugeClient to gitmodules. 2018-07-28 18:55:39 +02:00
ecc2a67d48 Updated readme for cloning requrse submodules. 2018-07-28 17:55:28 +02:00
bfe0d55f71 Excetion node-pre-gyp failed for version check for sqlite3. The package was set to static version and needed a patch because of errors caused by newer versions of node. 2018-07-28 17:51:59 +02:00
634d4513eb Merge pull request #106 from KevinMidboe/remove_verified
Remove verified
2018-07-28 16:17:43 +02:00
0a1276a474 Merge pull request #105 from KevinMidboe/plex_api_update
Plex api update
2018-07-28 16:17:33 +02:00
3a34d8995e Merge pull request #104 from KevinMidboe/misc
Misc
2018-07-28 16:15:18 +02:00
918e629a06 Merge pull request #103 from KevinMidboe/stray_fixes
Stray fixes
2018-07-28 16:15:03 +02:00
7dd016a56e Changed the python run variable for stray eps. 2018-07-28 16:05:22 +02:00
c10bbcf518 Added update function to package.json fole. 2018-07-28 15:58:02 +02:00
3402a52633 Change to log the parent name of the element instead of the name of file. 2018-07-28 10:50:57 +02:00
86e9188a5c The api from plex has changed. This reflects the changes from Video to metadata in the api url. 2018-07-28 10:46:25 +02:00
8918b7906e Merge branch 'remove_verified' of github.com:KevinMidboe/seasonedShows into HEAD 2018-05-13 19:21:52 +02:00
7e028a461d Merge branch 'master' of github.com:KevinMidboe/seasonedShows into HEAD 2018-05-13 19:21:14 +02:00
fe5f0c815e Deluge is now imported and a Deluge class is created and remove function called for name of torrent that is being verified. 2018-05-13 19:18:45 +02:00
d02e79e59e Merge pull request #100 from KevinMidboe/linting
Linting
2018-05-09 11:00:02 +02:00
5b49216c9d Fixed linting issues for json objects and tailing semicolon. 2018-05-09 10:52:08 +02:00
657ab10034 Removed unused comments. 2018-05-06 18:36:26 +02:00
ed07c77b13 Updated files with tripple === and some linting issues. 2018-05-06 18:23:29 +02:00
4fe85d9fae Change the ordering of user requests to be newest-first. 2018-05-06 18:15:54 +02:00
b99b5b32ec Updated to use abspath of the file not the call location. 2018-05-06 18:09:13 +02:00
e8058c5e4c Update README.md 2018-04-19 21:13:14 +02:00
7332b7d474 Update README.md 2018-04-19 20:22:26 +02:00
7980f14426 Update README.md 2018-04-19 20:21:44 +02:00
65540fafbd Merge pull request #99 from KevinMidboe/dependency_update
Dependency update
2018-04-10 11:29:56 +02:00
64ede43dec Added custom rule for object-shorthand and comma-dangle. 2018-04-08 11:58:16 +02:00
bed12cff72 Set branch to master for the submodule torrent_search 2018-04-08 11:50:15 +02:00
59e7f96643 Updated submodule torrent_search with new commits. 2018-04-08 11:43:12 +02:00
71e9a5a46e Merge pull request #98 from KevinMidboe/strayParser_fix
Updated when parsing for show name allow names with numbers.
2018-04-06 16:27:42 +02:00
fce6dc7658 Updated when parsing for show name allow names with numbers. 2018-04-06 16:22:58 +02:00
0592cca16b Updated string_decoder. 2018-04-04 23:08:00 +02:00
e4d5f5085c Updated yarn lockfile. 2018-04-04 14:41:01 +02:00
66a2a06f9b Compile error, if-statement. 2018-04-03 23:02:54 +02:00
e984feeb8d Would fail if the parent was not a directory. 2018-04-03 23:00:14 +02:00
69 changed files with 3690 additions and 1392 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.DS_Store
development.json
env
shows.db

7
.gitmodules vendored
View File

@@ -1,3 +1,10 @@
# Docs : https://git-scm.com/book/en/v2/Git-Tools-Submodules
[submodule "torrent_search"]
path = torrent_search
url = https://github.com/KevinMidboe/torrent_search.git
branch = master
[submodule "delugeClient"]
path = delugeClient
url = https://github.com/KevinMidboe/delugeClient.git

View File

@@ -4,9 +4,15 @@ git:
submodules: true
script:
- yarn test
- yarn coverage
- yarn coverage
before_install:
- cd seasoned_api
before_script: yarn
before_script:
- yarn
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
- chmod +x ./cc-test-reporter
- ./cc-test-reporter before-build
after-script:
- ./cc-test-resporter after-build --exit-code $TRAVIS_TEST_RESULT
cache: false
os: linux

152
README.md
View File

@@ -1,14 +1,130 @@
# 🌶 seasonedShows
[![Build Status](https://travis-ci.org/KevinMidboe/seasonedShows.svg?branch=master)](https://travis-ci.org/KevinMidboe/seasonedShows)
[![Coverage Status](https://coveralls.io/repos/github/KevinMidboe/seasonedShows/badge.svg?branch=coverage)](https://coveralls.io/github/KevinMidboe/seasonedShows?branch=coverage)
[![Dependency Status](https://www.versioneye.com/user/projects/5ac541370fb24f4489396e02/badge.svg)](https://www.versioneye.com/user/projects/5ac541370fb24f4489396e02)
[![Known Vulnerabilities](https://snyk.io/test/github/KevinMidboe/seasonedShows/badge.svg?targetFile=seasoned_api/package.json)](https://snyk.io/test/github/KevinMidboe/seasonedShows?targetFile=seasoned_api/package.json)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Your customly *seasoned* movie and show requester, downloader and organizer.
📺 [Demo](https://kevinmidboe.com/request)
<h1 align="center">
🌶 seasonedShows
</h1>
<h4 align="center"> Season your media library with the shows and movies that you and your friends want.</h4>
<p align="center">
<a href="https://travis-ci.org/KevinMidboe/seasonedShows">
<img src="https://travis-ci.org/KevinMidboe/seasonedShows.svg?branch=master"
alt="Travis CI">
</a>
<a href="https://coveralls.io/github/KevinMidboe/seasonedShows?branch=coverage">
<img src="https://coveralls.io/repos/github/KevinMidboe/seasonedShows/badge.svg?branch=coverage" alt="">
</a>
<a href="https://snyk.io/test/github/KevinMidboe/seasonedShows?targetFile=seasoned_api/package.json">
<img src="https://snyk.io/test/github/KevinMidboe/seasonedShows/badge.svg?targetFile=seasoned_api/package.json" alt="">
</a>
<a href="https://opensource.org/licenses/MIT">
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="">
</a>
</p>
<p align="center">
<a href="#demo-documentation">D & D</a>
<a href="#about">About</a>
<a href="#key-features">Key features</a>
<a href="#installation">Installation</a>
<a href="#setup">Setup</a>
<a href="#running">Running</a>
<a href="#daemon">Setup daemon</a>
<a href="#contributing">Contributing</a>
</p>
## <a name="demo-documentation"></a> Demo & Documentation
📺 [DEMO](https://kevinmidboe.com/request)
📝 Documentation of the api.
💖 Checkout my [fancy vue.js page](https://github.com/KevinMidboe/seasonedRequest) for interfacing the api.
## <a name="about"></a> About
This is the backend api for [seasoned request] that allows for uesrs to request movies and shows by fetching movies from themoviedb api and checks them with your plex library to identify if a movie is already present or not. This api allows to search my query, get themoviedb movie lists like popular and now playing, all while checking if the item is already in your plex library. Your friends can create users to see what movies or shows they have requested and searched for.
The api also uses torrent_search to search for matching torrents and returns results from any site or service available from torrent_search. As a admin of the site you can query torrent_search and return a magnet link that can be added to a autoadd folder of your favorite torrent client.
## <a name="key-features"></a> Key features
### Code
- Uses [tmdb api](https://www.themoviedb.org/documentation/api) with over 350k movies and 70k tv shows
- Written asynchronously
- Uses caching for external requests
- Test coverage
- CI and dependency integrated
- Use either config file or env_variables
### Functionality
- Queries plex library to check if items exists
- Create admin and normal user accounts
- [torrent_search](https://github.com/KevinMidboe/torrent_search) to search for torrents
- Fetch curated lists from tmdb
## <a name="installation"></a> Installation
Before we can use seasonedShows we need to download node and a package manager. For instructions on how to install [yarn](https://yarnpkg.com/en/) or [npm](https://www.npmjs.com) package managers refer to [wiki: install package manager](https://github.com/KevinMidboe/seasonedShows/wiki/Install-package-manager). This api is written with express using node.js as the JavaScript runtime engine. To download node.js head over the the official [node.js download page](https://nodejs.org/en/download/).
### Install seasonedShows
After you have downloaded a package manager and node.js javascript engine, the following will guide you through how to download, install and run seasonedShows.
### macOS
- Open terminal
- Install git. This can be done by running `xcode-select --install` in your favorite terminal.
- Install a package manager, refer to this [wiki page] for yarn or [wiki page] for npm
- Type: `git clone --recurse-submodules git@github.com:KevinMidboe/seasonedShows.git`
- Type: `cd seasonedShows/`
- Install required packages
* yarn: `yarn install`
* npm: `npm install`
- Start server:
* yarn: `yarn start`
* npm: `npm run start`
- seasonedShows will now be running at http://localhost:31459
- To have seasonedShows run headless on startup, check out this wiki page to [install as a daemon].
### Linux
- Open terminal
- Install git
* Ubuntu/Debian: `sudo apt-get install git-core`
* Fedora: `sudo yum install git`
- Type: `git clone --recurse-submodules git@github.com:KevinMidboe/seasonedShows.git`
- Type: `cd seasonedShows/`
- Install required packages
* yarn: `yarn install`
* npm: `npm install`
- Start server:
* yarn: `yarn start`
* npm: `npm run start`
- seasonedShows will now be running at http://localhost:31459
- To have seasonedShows run headless on startup, check out this wiki page to [install as a daemon].
-- same --
(install yarn or npm in a different way)
After you have installed the required packages you will have a node_modules directory with all the packages required in packages.json.
### Requirements
- Node 7.6 < [wiki page]
- Plex library
## <a name="setup"></a> Setup and/ configuration
There is a config file template, what the values mean and how to change them.
Also show how to hide file from git if not want to show up as uncommitted file.
Also set variables in environment.
## <a name="running"></a> Running/using
yarn/npm start. (can also say this above)
How to create service on linux. This means that
## <a name="daemon"></a> Setup a daemon
The next step is to setup seasonedShows api to run in the background as a daemon. I have written a [wiki page](https://github.com/KevinMidboe/seasonedShows/wiki/Install-as-a-daemon) on how to create a daemon on several unix distors and macOS.
*Please don't hesitate to add your own system if you get it running on something that is not yet lists on the formentioned wiki page.*
## <a name="contributing"></a> Contributing
- Fork it!
- Create your feature branch: git checkout -b my-new-feature
- Commit your changes: git commit -am 'Add some feature'
- Push to the branch: git push origin my-new-feature
- Submit a pull request
## Api documentation
## About
The goal of this project is to create a full custom stack that can to everything surround downloading, organizing and notifiyng of new media. From the top down we have a website using [tmdb](https://www.themoviedb.com) api to search for from over 350k movies and 70k tv shows. Using [hjone72](https://github.com/hjone72/PlexAuth) great PHP reverse proxy we can have a secure way of allowing users to login with their plex credentials which limits request capabilites to only users that are authenticated to use your plex library.
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.
@@ -23,21 +139,3 @@ 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.

View File

@@ -3,7 +3,7 @@
# @Author: KevinMidboe
# @Date: 2017-04-05 18:40:11
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-06-18 21:49:33
# @Last Modified time: 2018-04-03 22:58:20
import os.path, hashlib, time, glob, sqlite3, re, json, tweepy
import logging
from functools import reduce
@@ -61,7 +61,7 @@ class strayEpisode(object):
return hashlib.md5("b'{}'".format(self.parent).encode()).hexdigest()[:8]
def findSeriesName(self):
find = re.compile("^[a-zA-Z. ]*")
find = re.compile("^[a-zA-Z0-9. ]*")
m = re.match(find, self.parent)
if m:
name, hit = process.extractOne(m.group(0), getShowNames().keys())

View File

@@ -3,13 +3,15 @@
# @Author: KevinMidboe
# @Date: 2017-04-12 23:27:51
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-06-27 15:58:09
# @Last Modified time: 2018-05-13 19:17:17
import sys, sqlite3, json, os.path
import logging
import env_variables as env
import shutil
import delugeClient.deluge_cli as delugeCli
class episode(object):
def __init__(self, id):
self.id = id
@@ -91,8 +93,18 @@ def moveStray(strayId):
except FileNotFoundError:
logging.warning('Cannot remove ' + ep.typeDir('parent_input') + ', file no longer exists.')
# Remove from deluge client
logging.info('Removing {} for deluge'.format(ep.parent))
deluge = delugeCli.Deluge()
response = deluge.remove(ep.parent)
logging.info('Deluge response after delete: {}'.format(response))
if __name__ == '__main__':
if (os.path.exists(env.logfile)):
abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
if (os.path.exists(os.path.join(dname, env.logfile))):
logging.basicConfig(filename=env.logfile, level=logging.INFO)
else:
print('Logfile could not be found at ' + env.logfile + '. Verifiy presence or disable logging in config.')

5
renovate.json Normal file
View File

@@ -0,0 +1,5 @@
{
"extends": [
"config:base"
]
}

View File

@@ -7,6 +7,8 @@
"prefer-destructuring": 0,
"camelcase": 0,
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": 0
"import/no-extraneous-dependencies": 0,
"object-shorthand": 0,
"comma-dangle": 0
}
}

View File

@@ -8,17 +8,13 @@
"tmdb": {
"apiKey": ""
},
"plex": {
"ip": ""
},
"raven": {
"DSN": ""
},
"mail": {
"host": "",
"user": "",
"password": "",
"user_pi": "",
"password_pi": ""
},
"authentication": {
"secret": "secret"
}
"secret": "secret"
}
}

View File

@@ -8,6 +8,9 @@
"tmdb": {
"apiKey": "bogus-api-key"
},
"plex": {
"ip": "0.0.0.0"
},
"raven": {
"DSN": ""
},

View File

@@ -7,24 +7,27 @@
},
"main": "webserver/server.js",
"scripts": {
"start": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/webserver/server.js",
"test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --recursive test",
"start": "cross-env SEASONED_CONFIG=conf/development.json PROD=true NODE_PATH=. node src/webserver/server.js",
"test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --recursive test/unit test/system",
"coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --recursive test && nyc report --reporter=text-lcov | coveralls",
"lint": "./node_modules/.bin/eslint src/"
"lint": "./node_modules/.bin/eslint src/",
"update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/plex/updateRequestsInPlex.js"
},
"dependencies": {
"axios": "^0.18.0",
"bcrypt-nodejs": "^0.0.3",
"body-parser": "~1.18.2",
"cross-env": "~5.1.4",
"express": "~4.16.0",
"jsonwebtoken": "^8.0.1",
"km-moviedb": "^0.2.13",
"mongoose": "~5.0.11",
"moviedb": "^0.2.10",
"km-moviedb": "^0.2.12",
"node-cache": "^4.1.1",
"python-shell": "^0.5.0",
"request": "^2.85.0",
"request-promise": "^4.2",
"sqlite3": "4.0.0"
"sqlite3": "^4.0.0"
},
"devDependencies": {
"coveralls": "^3.0.0",

View File

@@ -36,6 +36,14 @@ CREATE TABLE IF NOT EXISTS requests(
type CHAR(50) DEFAULT 'movie'
);
CREATE TABLE IF NOT EXISTS request(
id int not null,
title text not null,
year int not null,
type char(10) not null,
date timestamp default (strftime('%s', 'now'))
);
CREATE TABLE IF NOT EXISTS stray_eps(
id TEXT UNIQUE,
@@ -56,3 +64,23 @@ CREATE TABLE IF NOT EXISTS shows(
date_added DATE,
date_modified DATE DEFUALT CURRENT_DATE NOT NULL
);
CREATE TABLE IF NOT EXISTS requested_torrent (
magnet TEXT UNIQUE,
torrent_name TEXT,
tmdb_id TEXT
date_added DATE DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS deluge_torrent (
key TEXT UNIQUE,
name TEXT,
progress TEXT,
eta NUMBER,
save_path TEXT,
state TEXT,
paused BOOLEAN,
finished BOOLEAN,
files TEXT,
is_folder BOOLEAN
)

View File

@@ -1,3 +1,4 @@
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS search_history;
DROP TABLE IF EXISTS requests;
DROP TABLE IF EXISTS request;

View File

@@ -3,6 +3,8 @@ const http = require('http');
const { URL } = require('url');
const PythonShell = require('python-shell');
const establishedDatabase = require('src/database/database');
function getMagnetFromURL(url) {
return new Promise((resolve, reject) => {
const options = new URL(url);
@@ -19,12 +21,12 @@ function getMagnetFromURL(url) {
async function find(searchterm, callback) {
const options = {
pythonPath: '/usr/bin/python3',
// pythonPath: '/Library/Frameworks/Python.framework/Versions/3.6/bin/python3',
args: [searchterm, '-s', 'jackett', '-f', '--print'],
};
pythonPath: '../torrent_search/env/bin/python3.6',
scriptPath: '../torrent_search',
args: [searchterm, '-s', 'jackett', '-f', '--print']
}
PythonShell.run('../torrent_search/torrentSearch/search.py', options, callback);
PythonShell.run('torrentSearch/search.py', options, callback);
// PythonShell does not support return
}
@@ -33,12 +35,12 @@ async function callPythonAddMagnet(url, callback) {
getMagnetFromURL(url)
.then((magnet) => {
const options = {
pythonPath: '/usr/bin/python',
// pythonPath: '/Library/Frameworks/Python.framework/Versions/3.6/bin/python3',
args: [magnet],
pythonPath: '../delugeClient/env/bin/python3.6',
scriptPath: '../delugeClient',
args: ['add', magnet]
};
PythonShell.run('../app/magnet.py', options, callback);
PythonShell.run('deluge_cli.py', options, callback);
})
.catch((err) => {
console.log(err);
@@ -61,15 +63,23 @@ async function SearchPiratebay(query) {
}));
}
async function AddMagnet(magnet) {
async function AddMagnet(magnet, name, tmdb_id) {
return await new Promise((resolve, reject) => callPythonAddMagnet(magnet, (err, results) => {
if (err) {
/* eslint-disable no-console */
console.log(err);
reject(Error('Enable to add torrent', err))
reject(Error('Enable to add torrent', err))
}
/* eslint-disable no-console */
console.log('result/error:', err, results);
database = establishedDatabase;
insert_query = "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \
VALUES (?,?,?)";
let response = database.run(insert_query, [magnet, name, tmdb_id]);
console.log('Response from requsted_torrent insert: ' + response);
resolve({ success: true });
}));
}

View File

@@ -0,0 +1,20 @@
const Episode = require('src/plex/types/episode');
function convertPlexToEpisode(plexEpisode) {
const episode = new Episode(plexEpisode.title, plexEpisode.grandparentTitle, plexEpisode.year);
episode.season = plexEpisode.parentIndex;
episode.episode = plexEpisode.index;
episode.summary = plexEpisode.summary;
episode.rating = plexEpisode.rating;
if (plexEpisode.viewCount !== undefined) {
episode.views = plexEpisode.viewCount;
}
if (plexEpisode.originallyAvailableAt !== undefined) {
episode.airdate = new Date(plexEpisode.originallyAvailableAt)
}
return episode;
}
module.exports = convertPlexToEpisode;

View File

@@ -0,0 +1,15 @@
const Movie = require('src/plex/types/movie');
function convertPlexToMovie(plexMovie) {
const movie = new Movie(plexMovie.title, plexMovie.year);
movie.rating = plexMovie.rating;
movie.tagline = plexMovie.tagline;
if (plexMovie.summary !== undefined) {
movie.summary = plexMovie.summary;
}
return movie;
}
module.exports = convertPlexToMovie;

View File

@@ -0,0 +1,13 @@
const Show = require('src/plex/types/show');
function convertPlexToShow(plexShow) {
const show = new Show(plexShow.title, plexShow.year);
show.summary = plexShow.summary;
show.rating = plexShow.rating;
show.seasons = plexShow.childCount;
show.episodes = plexShow.leafCount;
return show;
}
module.exports = convertPlexToShow;

View File

@@ -0,0 +1,60 @@
const axios = require('axios')
const convertPlexToMovie = require('src/plex/convertPlexToMovie')
const convertPlexToShow = require('src/plex/convertPlexToShow')
const convertPlexToEpisode = require('src/plex/convertPlexToEpisode')
class Plex {
constructor(ip) {
this.plexIP = ip
this.plexPort = 32400
}
existsInPlex(tmdbMovie) {
return Promise.resolve()
.then(() => this.search(tmdbMovie.title))
// TODO handle this when whitelist of local ip is not set in plex
.catch((error) => { console.error('Unable to search plex')})
.then((plexMovies) => {
const matches = plexMovies.some((plexMovie) => {
return tmdbMovie.title === plexMovie.title && tmdbMovie.type === plexMovie.type
})
tmdbMovie.existsInPlex = matches
return tmdbMovie
})
}
search(query) {
const options = {
baseURL: `http://${this.plexIP}:${this.plexPort}`,
url: '/hubs/search',
params: { query: query },
responseType: 'json',
timeout: 3000
}
return Promise.resolve()
.then(() => axios.request(options))
.catch((error) => { throw new Error(`Unable to search plex library`, error) })
.then(response => this.mapResults(response))
}
mapResults(response) {
return response.data.MediaContainer.Hub.reduce((result, hub) => {
if (hub.type === 'movie' && hub.Metadata !== undefined) {
return [...result, ...hub.Metadata.map(convertPlexToMovie)]
}
else if (hub.type === 'show' && hub.Metadata !== undefined) {
return [...result, ...hub.Metadata.map(convertPlexToShow)]
}
else if (hub.type === 'episode' && hub.Metadata !== undefined) {
return [...result, ...hub.Metadata.map(convertPlexToEpisode)]
}
return result
}, [])
}
}
module.exports = Plex;

View File

@@ -3,6 +3,10 @@ const convertPlexToStream = require('src/plex/convertPlexToStream');
const rp = require('request-promise');
class PlexRepository {
constructor(plexIP) {
this.plexIP = plexIP;
}
inPlex(tmdbResult) {
return Promise.resolve()
.then(() => this.search(tmdbResult.title))
@@ -15,8 +19,10 @@ class PlexRepository {
}
search(query) {
const queryUri = encodeURIComponent(query)
const uri = encodeURI(`http://${this.plexIP}:32400/search?query=${queryUri}`)
const options = {
uri: `http://10.0.0.44:32400/search?query=${query}`,
uri: uri,
headers: {
Accept: 'application/json',
},
@@ -25,6 +31,7 @@ class PlexRepository {
return rp(options)
.catch((error) => {
console.log(error)
throw new Error('Unable to search plex.')
})
.then(result => this.mapResults(result))
@@ -38,8 +45,10 @@ class PlexRepository {
tmdb.matchedInPlex = false
}
else {
// console.log('plex and tmdb:', plexResult, '\n', tmdb)
plexResult.results.map((plexItem) => {
if (tmdb.title === plexItem.title && tmdb.year === plexItem.year) { tmdb.matchedInPlex = true; }
if (tmdb.title === plexItem.title && tmdb.year === plexItem.year)
tmdb.matchedInPlex = true;
return tmdb;
});
}
@@ -62,7 +71,7 @@ class PlexRepository {
nowPlaying() {
const options = {
uri: 'http://10.0.0.44:32400/status/sessions',
uri: `http://${this.plexIP}:32400/status/sessions`,
headers: {
Accept: 'application/json',
},
@@ -72,7 +81,7 @@ class PlexRepository {
return rp(options)
.then((result) => {
if (result.MediaContainer.size > 0) {
const playing = result.MediaContainer.Video.map(convertPlexToStream);
const playing = result.MediaContainer.Metadata.map(convertPlexToStream);
return { size: Object.keys(playing).length, video: playing };
}
return { size: 0, video: [] };

View File

@@ -4,7 +4,7 @@ const configuration = require('src/config/configuration').getInstance();
const TMDB = require('src/tmdb/tmdb');
const establishedDatabase = require('src/database/database');
const plexRepository = new PlexRepository();
const plexRepository = new PlexRepository(configuration.get('plex', 'ip'));
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
@@ -18,7 +18,7 @@ class RequestRepository {
fetchRequestedItemsByStatus: 'SELECT * FROM requests WHERE status IS ? AND type LIKE ? ORDER BY date DESC LIMIT 25 OFFSET ?*25-25',
updateRequestedById: 'UPDATE requests SET status = ? WHERE id is ? AND type is ?',
checkIfIdRequested: 'SELECT * FROM requests WHERE id IS ? AND type IS ?',
userRequests: 'SELECT * FROM requests WHERE requested_by IS ?',
userRequests: 'SELECT * FROM requests WHERE requested_by IS ? ORDER BY date DESC',
};
this.cacheTags = {
search: 'se',
@@ -86,7 +86,11 @@ class RequestRepository {
}
throw new Error('Unable to fetch your requests');
})
.then((result) => { return result; });
.then((result) => {
// TODO do a correct mapping before sending, not just a dump of the database
result.map(item => item.poster = item.poster_path)
return result
});
}
updateRequestedById(id, type, status) {

View File

@@ -0,0 +1,16 @@
class Episode {
constructor(title, show, year) {
this.title = title;
this.show = show;
this.year = year;
this.season = null;
this.episode = null;
this.summary = null;
this.rating = null;
this.views = null;
this.aired = null;
this.type = 'episode';
}
}
module.exports = Episode;

View File

@@ -0,0 +1,12 @@
class Movie {
constructor(title, year) {
this.title = title;
this.year = year;
this.summary = null;
this.rating = null;
this.tagline = null;
this.type = 'movie';
}
}
module.exports = Movie;

View File

@@ -0,0 +1,13 @@
class Show {
constructor(title, year) {
this.title = title;
this.year = year;
this.summary = null;
this.rating = null;
this.seasons = null;
this.episodes = null;
this.type = 'show';
}
}
module.exports = Show;

View File

@@ -0,0 +1,157 @@
const assert = require('assert')
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const establishedDatabase = require('src/database/database');
const utils = require('./utils');
class RequestRepository {
constructor(database) {
this.database = database || establishedDatabase;
this.queries = {
add: 'insert into requests (id,title,year,poster_path,background_path,requested_by,ip,user_agent,type) values(?,?,?,?,?,?,?,?,?)',
fetchAll: 'select * from requests where status != "downloaded" order by date desc LIMIT 25 OFFSET ?*25-25',
fetchAllFilteredStatus: 'select * from requests where status = ? order by date desc LIMIT 25 offset ?*25-25',
totalRequests: 'select count(*) as totalRequests from requests where status != "downloaded"',
totalRequestsFilteredStatus: 'select count(*) as totalRequests from requests where status = ?',
fetchAllSort: `select id, type from request order by ? ?`,
fetchAllFilter: `select id, type from request where ? is "?"`,
fetchAllQuery: `select id, type from request where title like "%?%" or year like "%?%"`,
fetchAllFilterAndSort: `select id, type from request where ? is "?" order by ? ?`,
downloaded: '(select status from requests where id is request.id and type is request.type limit 1)',
// deluge: '(select status from deluge_torrent where id is request.id and type is request.type limit 1)',
// fetchAllFilterStatus: 'select * from request where '
read: 'select id, title, year, type, status, requested_by, ip, date, user_agent from requests where id is ? and type is ?'
};
}
sortAndFilterToDbQuery(by, direction, filter, query) {
let dbQuery = undefined;
if (query !== undefined) {
const dbParams = [query, query];
const dbquery = this.queries.fetchAllQuery
dbQuery = dbquery.split('').map((char) => char === '?' ? dbParams.shift() : char).join('')
}
else if (by !== undefined && filter !== undefined) {
const paramToColumnAndValue = {
movie: ['type', 'movie'],
show: ['type', 'show']
}
const dbParams = paramToColumnAndValue[filter].concat([by, direction]);
const query = this.queries.fetchAllFilterAndSort;
dbQuery = query.split('').map((char) => char === '?' ? dbParams.shift() : char).join('')
}
else if (by !== undefined) {
const dbParams = [by, direction];
const query = this.queries.fetchAllSort;
dbQuery = query.split('').map((char) => char === '?' ? dbParams.shift() : char).join('')
}
else if (filter !== undefined) {
const paramToColumnAndValue = {
movie: ['type', 'movie'],
show: ['type', 'show'],
downloaded: [this.queries.downloaded, 'downloaded']
// downloading: [this.database.delugeStatus, 'downloading']
}
const dbParams = paramToColumnAndValue[filter]
const query = this.queries.fetchAllFilter;
dbQuery = query.split('').map((char) => char === '?' ? dbParams.shift() : char).join('')
}
else {
dbQuery = this.queries.fetchAll;
}
return dbQuery
}
mapToTmdbByType(rows) {
return rows.map((row) => {
if (row.type === 'movie')
return tmdb.movieInfo(row.id)
else if (row.type === 'show')
return tmdb.showInfo(row.id)
})
}
/**
* Add tmdb movie|show to requests
* @param {tmdb} tmdb class of movie|show to add
* @returns {Promise}
*/
requestFromTmdb(tmdb, ip, user_agent, user) {
return Promise.resolve()
.then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type]))
.then(row => assert.equal(row, undefined, 'Id has already been requested'))
.then(() => this.database.run(this.queries.add, [tmdb.id, tmdb.title, tmdb.year, tmdb.poster, tmdb.backdrop, user, ip, user_agent, tmdb.type]))
.catch((error) => {
if (error.name === 'AssertionError' || error.message.endsWith('been requested')) {
throw new Error('This id is already requested', error.message);
}
console.log('Error @ request.addTmdb:', error);
throw new Error('Could not add request');
});
}
/**
* Get request item by id
* @param {String} id
* @param {String} type
* @returns {Promise}
*/
getRequestByIdAndType(id, type) {
return Promise.resolve()
.then(() => this.database.get(this.queries.read, [id, type]))
.then(row => {
assert(row, 'Could not find request item with that id and type')
return JSON.stringify(row)
})
}
/**
* Fetch all requests with optional sort and filter params
* @param {String} what we are sorting by
* @param {String} direction that can be either 'asc' or 'desc', default 'asc'.
* @param {String} params to filter by
* @param {String} query param to filter result on. Filters on title and year
* @returns {Promise}
*/
fetchAll(page=1, sort_by=undefined, sort_direction='asc', filter=undefined, query=undefined) {
// TODO implemented sort and filter
let fetchQuery = this.queries.fetchAll
let fetchTotalResults = this.queries.totalRequests
let fetchParams = [page]
if (filter && (filter === 'downloading' || filter === 'downloaded' || filter === 'requested')) {
console.log('tes')
fetchQuery = this.queries.fetchAllFilteredStatus
fetchTotalResults = this.queries.totalRequestsFilteredStatus
fetchParams = [filter, page]
} else {
filter = undefined
}
return Promise.resolve()
.then((dbQuery) => this.database.all(fetchQuery, fetchParams))
.then(async (rows) => {
const sqliteResponse = await this.database.get(fetchTotalResults, filter ? filter : undefined)
const totalRequests = sqliteResponse['totalRequests']
const totalPages = Math.ceil(totalRequests / 26)
return [ rows.map(item => { item.poster = item.poster_path; return item }), totalPages ]
return Promise.all(this.mapToTmdbByType(rows))
})
.then(([result, totalPages]) => Promise.resolve({
results: result, total_results: result.length, page: page, total_pages: totalPages
}))
.catch(error => { console.log(error);throw error })
}
}
module.exports = RequestRepository;

View File

@@ -0,0 +1,34 @@
// TODO : test title and date are valid matches to columns in the database
const validSortParams = ['title', 'date']
const validSortDirs = ['asc', 'desc']
const validFilterParams = ['movie', 'show', 'seeding', 'downloading', 'paused', 'finished', 'downloaded']
function validSort(by, direction) {
return new Promise((resolve, reject) => {
if (by === undefined) {
resolve()
}
if (validSortParams.includes(by) && validSortDirs.includes(direction)) {
resolve()
} else {
reject(new Error(`invalid sort parameter, must be of: ${validSortParams} with optional sort directions: ${validSortDirs} appended with ':'`))
}
});
}
function validFilter(filter_param) {
return new Promise((resolve, reject) => {
if (filter_param === undefined) {
resolve()
}
if (filter_param && validFilterParams.includes(filter_param)) {
resolve()
} else {
reject(new Error(`filter parameteres must be of type: ${validFilterParams}`))
}
});
}
module.exports = { validSort, validFilter }

View File

@@ -43,7 +43,7 @@ class StrayRepository {
assert.notEqual(row, undefined, `Stray '${strayId}' already verified.`);
const options = {
pythonPath: '/usr/bin/python3',
pythonPath: '../app/env/bin/python3',
args: [strayId],
};

View File

@@ -0,0 +1,49 @@
const Movie = require('src/tmdb/types/movie');
const tmdbSwitcher = (tmdbMovie, property) => tmdbMovie[property]
function convertTmdbToMovie(tmdbMovie, credits=undefined) {
const movie = new Movie(tmdbMovie.id, tmdbMovie.title)
movie.overview = tmdbMovie.overview;
movie.rank = tmdbMovie.vote_average;
if (credits) {
movie.credits = credits;
}
if (tmdbMovie.release_date !== undefined) {
movie.release_date = new Date(tmdbMovie.release_date);
movie.year = movie.release_date.getFullYear();
}
if (tmdbMovie.poster_path !== undefined) {
movie.poster = tmdbMovie.poster_path;
}
if (tmdbMovie.backdrop_path !== undefined) {
movie.backdrop = tmdbMovie.backdrop_path;
}
if (tmdbMovie.status !== undefined) {
movie.status = tmdbMovie.status;
}
if (tmdbMovie.genres !== undefined) {
movie.genres = tmdbMovie.genres.map(genre => genre.name);
}
if (tmdbMovie.tagline !== undefined) {
movie.tagline = tmdbMovie.tagline;
}
if (tmdbMovie.runtime !== undefined) {
movie.runtime = tmdbMovie.runtime;
}
if (tmdbMovie.imdb_id !== undefined) {
movie.imdb_id = tmdbMovie.imdb_id;
}
return movie;
}
module.exports = convertTmdbToMovie;

View File

@@ -0,0 +1,30 @@
const Person = require('src/tmdb/types/person');
const convertTmdbToMovie = require('src/tmdb/convertTmdbToMovie');
function convertTmdbToPerson(tmdbPerson, cast=undefined) {
const person = new Person(tmdbPerson.id, tmdbPerson.name);
if (tmdbPerson.profile_path !== undefined) {
person.poster = tmdbPerson.profile_path;
}
if (tmdbPerson.birthday !== undefined) {
person.birthday = new Date(tmdbPerson.birthday);
}
if (tmdbPerson.deathday !== undefined) {
person.deathday = tmdbPerson.deathday;
}
if (tmdbPerson.known_for !== undefined) {
person.known_for = tmdbPerson.known_for.map(convertTmdbToMovie);
}
if (cast) {
person.cast = cast.cast;
}
return person;
}
module.exports = convertTmdbToPerson;

View File

@@ -1,5 +1,5 @@
const TMDB = require('src/media_classes/tmdb');
const Movie = require('src/types/movie');
function translateYear(tmdbReleaseDate) {
return new Date(tmdbReleaseDate).getFullYear();
@@ -14,10 +14,28 @@ function convertType(tmdbType) {
return undefined;
}
function convertTmdbToSeasoned(tmdb, manualType = undefined) {
const title = tmdb.title || tmdb.name;
function convertTmdbToMovie(tmdb) {
const year =
const movie = new Movie();
let seasoned = undefined;
if (tmdb.id && tmdb.title && tmdb.release_date) {
const year = tmdb.release_date.getFullYear();
seasoned = new Movie(tmdb.id, tmdb.title, year);
}
else if (tmdb.id && tmdb.name && tmdb.first_air_date) {
const year = tmdb.first_air_date.getFullYear();
seasoned = new Show(tmdb.id, tmdb.name, year);
seasoned.seasons = tmdb.number_of_season;
seasoned.episodes = tmdb.episodes;
return
}
}
const title = tmdb.title || tmdb.name;
const year = translateYear(tmdb.release_date || tmdb.first_air_date);
const type = manualType || convertType(tmdb.media_type) || 'movie';
const type = manualType || convertType(tmdb.type) || 'movie';
const id = tmdb.id;
const summary = tmdb.overview;

View File

@@ -0,0 +1,41 @@
const Show = require('src/tmdb/types/show');
function convertTmdbToShow(tmdbShow, credits=undefined) {
const show = new Show(tmdbShow.id, tmdbShow.name)
show.seasons = tmdbShow.number_of_seasons;
show.episodes = tmdbShow.number_of_episodes;
show.overview = tmdbShow.overview;
show.rank = tmdbShow.vote_average;
if (credits) {
show.credits = credits
}
if (tmdbShow.genres !== undefined) {
show.genres = tmdbShow.genres.map(genre => genre.name);
}
if (tmdbShow.first_air_date !== undefined) {
show.first_air_date = new Date(tmdbShow.first_air_date);
show.year = show.first_air_date.getFullYear();
}
if (tmdbShow.poster_path !== undefined) {
show.poster = tmdbShow.poster_path;
}
if (tmdbShow.backdrop_path !== undefined) {
show.backdrop = tmdbShow.backdrop_path;
}
if (tmdbShow.status !== undefined) {
show.status = tmdbShow.status;
}
if (tmdbShow.episode_run_time !== undefined) {
show.runtime = tmdbShow.runtime;
}
return show;
}
module.exports = convertTmdbToShow;

View File

@@ -1,30 +1,29 @@
const moviedb = require('moviedb');
const convertTmdbToSeasoned = require('src/tmdb/convertTmdbToSeasoned');
const TMDB_METHODS = {
upcoming: { movie: 'miscUpcomingMovies' },
discover: { movie: 'discoverMovie', show: 'discoverTv' },
popular: { movie: 'miscPopularMovies', show: 'miscPopularTvs' },
nowplaying: { movie: 'miscNowPlayingMovies', show: 'tvOnTheAir' },
similar: { movie: 'movieSimilar', show: 'tvSimilar' },
search: { movie: 'searchMovie', show: 'searchTv', multi: 'searchMulti' },
info: { movie: 'movieInfo', show: 'tvInfo' },
};
const moviedb = require('km-moviedb');
const convertTmdbToMovie = require('src/tmdb/convertTmdbToMovie');
const convertTmdbToShow = require('src/tmdb/convertTmdbToShow');
const convertTmdbToPerson = require('src/tmdb/convertTmdbToPerson');
class TMDB {
constructor(cache, apiKey, tmdbLibrary) {
this.cache = cache;
this.tmdbLibrary = tmdbLibrary || moviedb(apiKey);
this.cacheTags = {
search: 'se',
info: 'i',
upcoming: 'u',
discover: 'd',
popular: 'p',
nowplaying: 'n',
similar: 'si',
};
}
constructor(cache, apiKey, tmdbLibrary) {
this.cache = cache;
this.tmdbLibrary = tmdbLibrary || moviedb(apiKey);
this.cacheTags = {
multiSearch: 'mus',
movieSearch: 'mos',
showSearch: 'ss',
personSearch: 'ps',
movieInfo: 'mi',
showInfo: 'si',
personInfo: 'pi',
miscNowPlayingMovies: 'npm',
miscPopularMovies: 'pm',
miscTopRatedMovies: 'tpm',
miscUpcomingMovies: 'um',
tvOnTheAir: 'toa',
miscPopularTvs: 'pt',
miscTopRatedTvs: 'trt',
};
}
/**
* Retrieve a specific movie by id from TMDB.
@@ -40,7 +39,7 @@ class TMDB {
.catch(() => this.tmdb(TMDB_METHODS['info'][type], query))
.catch(() => { throw new Error('Could not find a movie with that id.'); })
.then(response => this.cache.set(cacheKey, response))
.then((response) => {
.then(response => {
try {
return convertTmdbToSeasoned(response, type);
} catch (parseError) {
@@ -65,9 +64,177 @@ class TMDB {
.catch(() => this.tmdb(TMDB_METHODS['search'][type], query))
.catch(() => { throw new Error('Could not search for movies/shows at tmdb.'); })
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapResults(response))
.then(response => this.mapResults(response));
}
/**
* Retrieve a specific movie by id from TMDB.
* @param {Number} identifier of the movie you want to retrieve
* @param {String} type filter results by type (default movie).
* @returns {Promise} succeeds if movie was found
*/
movieInfo(identifier, credits=false) {
const query = { id: identifier };
const cacheKey = `${this.cacheTags.movieInfo}:${identifier}:${credits}`;
const requests = [this.tmdb('movieInfo', query)]
if (credits) {
requests.push(this.tmdb('movieCredits', query))
}
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => Promise.all(requests))
.catch((error) => { console.log(error); throw new Error('Could not find a movie with that id.'); })
.then(([movies, credits]) => this.cache.set(cacheKey, [movies, credits]))
.then(([movies, credits]) => convertTmdbToMovie(movies, credits))
}
/**
* Retrieve a specific show by id from TMDB.
* @param {Number} identifier of the show you want to retrieve
* @param {String} type filter results by type (default show).
* @returns {Promise} succeeds if show was found
*/
showInfo(identifier, credits=false) {
const query = { id: identifier };
const cacheKey = `${this.cacheTags.showInfo}:${identifier}:${credits}`;
const requests = [this.tmdb('tvInfo', query)]
if (credits) {
requests.push(this.tmdb('tvCredits', query))
}
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => Promise.all(requests))
.catch(() => { throw new Error('Could not find a show with that id.'); })
.then(([shows, credits]) => this.cache.set(cacheKey, [shows, credits]))
.then(([shows, credits]) => convertTmdbToShow(shows, credits))
}
/**
* Retrieve a specific person id from TMDB.
* @param {Number} identifier of the person you want to retrieve
* @param {String} type filter results by type (default person).
* @returns {Promise} succeeds if person was found
*/
personInfo(identifier) {
const query = { id: identifier };
const cacheKey = `${this.cacheTags.personInfo}:${identifier}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => Promise.all([this.tmdb('personInfo', query), this.tmdb('personCombinedCredits', query)]))
.catch(() => { throw new Error('Could not find a person with that id.'); })
.then(([person, cast]) => this.cache.set(cacheKey, [person, cast]))
.then(([person, cast]) => convertTmdbToPerson(person, cast))
.catch(err => new Error('Unable to convert result to person', err))
}
multiSearch(search_query, page=1) {
const query = { query: search_query, page: page };
const cacheKey = `${this.cacheTags.multiSearch}:${page}:${search_query}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb('searchMulti', query))
.catch(() => { throw new Error('Could not complete search to tmdb'); })
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapResults(response));
}
/**
* Retrive movie search results from TMDB.
* @param {String} text query you want to search for
* @param {Number} page representing pagination of results
* @returns {Promise} dict with query results, current page and total_pages
*/
movieSearch(query, page=1) {
const tmdbquery = { query: query, page: page };
const cacheKey = `${this.cacheTags.movieSearch}:${page}:${query}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb('searchMovie', tmdbquery))
.catch(() => { throw new Error('Could not complete movie search to tmdb'); })
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapAndCreateResponse(response, convertTmdbToMovie))
.catch((error) => { console.log(error); throw new Error('Could not parse movie search result') })
}
/**
* Retrive show search results from TMDB.
* @param {String} text query you want to search for
* @param {Number} page representing pagination of results
* @returns {Promise} dict with query results, current page and total_pages
*/
showSearch(query, page=1) {
const tmdbquery = { query: query, page: page };
const cacheKey = `${this.cacheTags.showSearch}:${page}:${query}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb('searchTv', tmdbquery))
.catch(() => { throw new Error('Could not complete show search to tmdb'); })
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapAndCreateResponse(response, convertTmdbToShow))
.catch((error) => { console.log(error); throw new Error('Could not parse show search result') })
}
/**
* Retrive person search results from TMDB.
* @param {String} text query you want to search for
* @param {Number} page representing pagination of results
* @returns {Promise} dict with query results, current page and total_pages
*/
personSearch(query, page=1) {
const tmdbquery = { query: query, page: page };
const cacheKey = `${this.cacheTags.personSearch}:${page}:${query}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb('searchPerson', tmdbquery))
.catch(() => { throw new Error('Could not complete person search to tmdb'); })
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapAndCreateResponse(response, convertTmdbToPerson))
.catch((error) => { console.log(error); throw new Error('Could not parse person search result') })
}
mapAndCreateResponse(response, resultConvertFunction) {
// console.log(response)
return {
results: response.results.map(resultConvertFunction),
page: response.page,
total_results: response.total_results,
total_pages: response.total_pages
}
}
movieList(listname, page = 1) {
const query = { page: page };
const cacheKey = `${this.cacheTags[listname]}:${page}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(listname, query))
.catch(() => { throw new Error('Unable to get movie list from tmdb')})
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapAndCreateResponse(response, convertTmdbToMovie));
}
showList(listname, page = 1) {
const query = { page: page };
const cacheKey = `${this.cacheTags[listname]}:${page}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(listname, query))
.catch(() => { throw new Error('Unable to get show list from tmdb')})
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapAndCreateResponse(response, convertTmdbToShow));
}
/**
* Fetches a given list from tmdb.
* @param {String} listName Name of list
@@ -76,34 +243,52 @@ class TMDB {
* @returns {Promise} dict with query results, current page and total_pages
*/
listSearch(listName, type = 'movie', page = '1') {
const query = { page: page }
console.log(query)
const query = { page: page };
console.log(query);
const cacheKey = `${this.cacheTags[listName]}:${type}:${page}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(TMDB_METHODS[listName][type], query))
.catch(() => { throw new Error('Error fetching list from tmdb.')})
.catch(() => { throw new Error('Error fetching list from tmdb.'); })
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapResults(response, type))
.then(response => this.mapResults(response, type));
}
/**
popular(type='movie', page=1) {
const query = { type: type, page: page };
const cacheKey = `${this.cacheTags.popular}:${type}:${page}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb('miscPopularMovies', query))
.catch((e) => { throw new Error(`Error fetching popular list of type ${type} : ${e}`) })
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapResults(response, type));
}
/**
* Maps our response from tmdb api to a movie/show object.
* @param {String} response from tmdb.
* @param {String} The type declared in listSearch.
* @returns {Promise} dict with tmdb results, mapped as movie/show objects.
*/
mapResults(response, type) {
console.log(response.page)
return Promise.resolve()
.then(() => {
const mappedResults = response.results.filter((element) => {
return (element.media_type === 'movie' || element.media_type === 'tv' || element.media_type === undefined);
}).map((element) => convertTmdbToSeasoned(element, type));
return {results: mappedResults, page: response.page, total_pages: response.total_pages, total_results: response.total_results}
})
.catch((error) => { throw new Error(error); });
}
mapResults(response, _) {
let results = response.results.map((result) => {
if (result.media_type === 'movie') {
return convertTmdbToMovie(result);
} else if (result.media_type === 'tv') {
return convertTmdbToShow(result);
} else if (result.media_type === 'person') {
return convertTmdbToPerson(result);
}
})
return {
results: results,
page: response.page,
total_results: response.total_results,
total_pages: response.total_pages
}
}
/**
* Wraps moviedb library to support Promises.
@@ -117,7 +302,7 @@ class TMDB {
if (error) {
return reject(error);
}
return resolve(reponse);
resolve(reponse);
};
if (!argument) {

View File

@@ -0,0 +1,20 @@
class Movie {
constructor(id, title, year=null, overview=null, poster=null, backdrop=null, rank=null, genres=null, status=null,
tagline=null, runtime=null, imdb_id=null) {
this.id = id;
this.title = title;
this.year = year;
this.overview = overview;
this.poster = poster;
this.backdrop = backdrop;
this.rank = rank;
this.genres = genres;
this.status = status;
this.tagline = tagline;
this.runtime = runtime;
this.imdb_id = imdb_id;
this.type = 'movie';
}
}
module.exports = Movie;

View File

@@ -0,0 +1,13 @@
class Person {
constructor(id, name, poster=null, birthday=null, deathday=null, known_for=null) {
this.id = id;
this.name = name;
this.poster = poster;
this.birthday = birthday;
this.deathday = deathday;
this.known_for = known_for;
this.type = 'person';
}
}
module.exports = Person;

View File

@@ -0,0 +1,20 @@
class Show {
constructor(id, title, year=null, seasons=null, episodes=null, overview=null, rank=null, genres=null,
poster=null, backdrop=null, status=null, runtime=null) {
this.id = id;
this.title = title;
this.year = year;
this.seasons = seasons;
this.episodes = episodes;
this.overview = overview;
this.rank = rank;
this.genres = genres;
this.poster = poster;
this.backdrop = backdrop;
this.status = status;
this.runtime = runtime;
this.type = 'show';
}
}
module.exports = Show;

View File

@@ -41,7 +41,7 @@ class UserRepository {
assert(row, 'The user does not exist.');
return row.password;
})
.catch((err) => console.log('there was a error when getting hash', err));
.catch((err) => { console.log(error); throw new Error('Unable to find your user.'); });
}
/**
@@ -57,7 +57,7 @@ class UserRepository {
checkAdmin(user) {
return this.database.get(this.queries.getAdminStateByUser, user.username).then((row) => {
return row.admin;
})
});
}
}

View File

@@ -6,19 +6,14 @@ const mustBeAuthenticated = require('./middleware/mustBeAuthenticated');
const mustBeAdmin = require('./middleware/mustBeAdmin');
const configuration = require('src/config/configuration').getInstance();
const listController = require('./controllers/list/listController');
// TODO: Have our raven router check if there is a value, if not don't enable raven.
Raven.config(configuration.get('raven', 'DSN')).install();
const app = express(); // define our app using express
app.use(Raven.requestHandler());
// this will let us get the data from a POST
// configure app to use bodyParser()
app.use(bodyParser.json());
// router.use(bodyParser.urlencoded({ extended: true }));
/* Decode the Authorization header if provided */
// router.use(tokenToUser);
const router = express.Router();
const allowedOrigins = ['https://kevinmidboe.com', 'http://localhost:8080'];
@@ -27,19 +22,17 @@ const allowedOrigins = ['https://kevinmidboe.com', 'http://localhost:8080'];
// router.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// This is probably a correct middleware/router setup
/* Translate the user token to a user name */
/* Decode the Authorization header if provided */
router.use(tokenToUser);
// TODO: Should have a separate middleware/router for handling headers.
router.use((req, res, next) => {
// TODO add logging of all incoming
console.log('Request: ', req.originalUrl);
const origin = req.headers.origin;
const origin = req.headers.origin;
if (allowedOrigins.indexOf(origin) > -1) {
console.log('allowed');
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, loggedinuser');
res.header('Access-Control-Allow-Methods', 'POST, GET, PUT');
@@ -72,19 +65,45 @@ router.get('/v1/seasoned/all', require('./controllers/seasoned/readStrays.js'));
router.get('/v1/seasoned/:strayId', require('./controllers/seasoned/strayById.js'));
router.post('/v1/seasoned/verify/:strayId', require('./controllers/seasoned/verifyStray.js'));
router.get('/v2/search/', require('./controllers/search/multiSearch.js'));
router.get('/v2/search/movie', require('./controllers/search/movieSearch.js'));
router.get('/v2/search/show', require('./controllers/search/showSearch.js'));
router.get('/v2/search/person', require('./controllers/search/personSearch.js'));
router.get('/v2/movie/now_playing', listController.nowPlayingMovies);
router.get('/v2/movie/popular', listController.popularMovies);
router.get('/v2/movie/top_rated', listController.topRatedMovies);
router.get('/v2/movie/upcoming', listController.upcomingMovies);
router.get('/v2/show/now_playing', listController.nowPlayingShows);
router.get('/v2/show/popular', listController.popularShows);
router.get('/v2/show/top_rated', listController.topRatedShows);
router.get('/v2/movie/:id', require('./controllers/info/movieInfo.js'));
router.get('/v2/show/:id', require('./controllers/info/showInfo.js'));
router.get('/v2/person/:id', require('./controllers/info/personInfo.js'));
/**
* Plex
*/
router.get('/v2/plex/search', require('./controllers/plex/search'));
/**
* List
*/
router.get('/v1/plex/search', require('./controllers/plex/searchMedia.js'));
router.get('/v1/plex/playing', require('./controllers/plex/plexPlaying.js'));
router.get('/v1/plex/request', require('./controllers/plex/searchRequest.js'));
router.get('/v1/plex/request/:mediaId', require('./controllers/plex/readRequest.js'));
router.post('/v1/plex/request/:mediaId', require('./controllers/plex/submitRequest.js'));
router.get('/v1/plex/hook', require('./controllers/plex/hookDump.js'));
router.post('/v1/plex/hook', require('./controllers/plex/hookDump.js'));
/**
* Requests
*/
router.get('/v2/request', require('./controllers/request/fetchAllRequests.js'));
router.get('/v2/request/:id', require('./controllers/request/getRequest.js'));
router.post('/v2/request', require('./controllers/request/requestTmdbId.js'));
router.get('/v1/plex/requests/all', require('./controllers/plex/fetchRequested.js'));
router.put('/v1/plex/request/:requestId', mustBeAuthenticated, require('./controllers/plex/updateRequested.js'));

View File

@@ -0,0 +1,30 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const Plex = require('src/plex/plex');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const plex = new Plex(configuration.get('plex', 'ip'));
/**
* Controller: Retrieve information for a movie
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
async function movieInfoController(req, res) {
const movieId = req.params.id;
const { credits } = req.query;
const movie = await tmdb.movieInfo(movieId, credits);
plex.existsInPlex(movie)
.catch((error) => { console.log('Error when searching plex'); })
.then(() => {
console.log('movie', movie)
res.send(movie);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = movieInfoController;

View File

@@ -0,0 +1,24 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve information for a person
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function personInfoController(req, res) {
const personId = req.params.id;
tmdb.personInfo(personId)
.then((person) => {
res.send(person);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = personInfoController;

View File

@@ -0,0 +1,31 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const Plex = require('src/plex/plex');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const plex = new Plex(configuration.get('plex', 'ip'));
/**
* Controller: Retrieve information for a show
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
async function showInfoController(req, res) {
const showId = req.params.id;
const { credits } = req.query;
const show = await tmdb.showInfo(showId, credits);
plex.existsInPlex(show)
.catch((error) => { console.log('Error when searching plex'); })
.then(() => {
console.log('show', show)
res.send(show);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = showInfoController;

View File

@@ -0,0 +1,85 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
// there should be a translate function from query params to
// tmdb list that is valid. Should it be a helper function or does it
// belong in tmdb.
// + could also have default value that are sent to the client.
// * have the same class create a getListNames() and a fetchList()
// * dicover list might be overkill_https://tinyurl.com/y7f8ragw
// + trending! https://tinyurl.com/ydywrqox
// by all, mediatype, or person. Can also define time periode to
// get more trending view of what people are checking out.
// + newly created (tv/latest).
// + movie/latest
//
function getTmdbMovieList(res, listname, page) {
Promise.resolve()
.then(() => tmdb.movieList(listname, page))
.then((response) => res.send(response))
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
})
}
function getTmdbShowList(res, listname, page) {
Promise.resolve()
.then(() => tmdb.showList(listname, page))
.then((response) => res.send(response))
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
})
}
exports.nowPlayingMovies = (req, res) => {
const { page } = req.query;
const listname = 'miscNowPlayingMovies'
getTmdbMovieList(res, listname, page);
}
exports.popularMovies = (req, res) => {
const { page } = req.query;
const listname = 'miscPopularMovies'
getTmdbMovieList(res, listname, page);
}
exports.topRatedMovies = (req, res) => {
const { page } = req.query;
const listname = 'miscTopRatedMovies'
getTmdbMovieList(res, listname, page);
}
exports.upcomingMovies = (req, res) => {
const { page } = req.query;
const listname = 'miscUpcomingMovies'
getTmdbMovieList(res, listname, page);
}
exports.nowPlayingShows = (req, res) => {
const { page } = req.query;
const listname = 'tvOnTheAir'
getTmdbShowList(res, listname, page);
}
exports.popularShows = (req, res) => {
const { page } = req.query;
const listname = 'miscPopularTvs'
getTmdbShowList(res, listname, page);
}
exports.topRatedShows = (req, res) => {
const { page } = req.query;
const listname = 'miscTopRatedTvs'
getTmdbShowList(res, listname, page);
}

File diff suppressed because one or more lines are too long

View File

@@ -10,8 +10,10 @@ const PirateRepository = require('src/pirate/pirateRepository');
function addMagnet(req, res) {
const magnet = req.body.magnet;
const name = req.body.name;
const tmdb_id = req.body.tmdb_id;
PirateRepository.AddMagnet(magnet)
PirateRepository.AddMagnet(magnet, name, tmdb_id)
.then((result) => {
res.send(result);
})

View File

@@ -1,6 +1,7 @@
const PlexRepository = require('src/plex/plexRepository');
const configuration = require('src/config/configuration').getInstance();
const plexRepository = new PlexRepository();
const plexRepository = new PlexRepository(configuration.get('plex', 'ip'));
function playingController(req, res) {
plexRepository.nowPlaying()

View File

@@ -0,0 +1,25 @@
const configuration = require('src/config/configuration').getInstance();
const Plex = require('src/plex/plex');
const plex = new Plex(configuration.get('plex', 'ip'));
/**
* Controller: Search plex for movies, shows and episodes by query
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function searchPlexController(req, res) {
const { query, type } = req.query;
plex.search(query, type)
.then((movies) => {
if (movies.length > 0) {
res.send(movies);
} else {
res.status(404).send({ success: false, error: 'Search query did not give any results from plex.'})
}
}).catch((error) => {
res.status(500).send({ success: false, error: error.message });
});
}
module.exports = searchPlexController;

View File

@@ -1,6 +1,7 @@
const PlexRepository = require('src/plex/plexRepository');
const configuration = require('src/config/configuration').getInstance();
const plexRepository = new PlexRepository();
const plexRepository = new PlexRepository(configuration.get('plex', 'ip'));
/**
* Controller: Search for media and check existence

View File

@@ -10,7 +10,7 @@ const searchHistory = new SearchHistory();
function searchRequestController(req, res) {
const user = req.loggedInUser;
const { query, page, type } = req.query;
const username = user == undefined ? undefined : user.username;
const username = user === undefined ? undefined : user.username;
Promise.resolve()
.then(() => searchHistory.create(username, query))

View File

@@ -1,6 +1,19 @@
const RequestRepository = require('src/plex/requestRepository.js');
const configuration = require('src/config/configuration').getInstance()
const RequestRepository = require('src/request/request');
const Cache = require('src/tmdb/cache')
const TMDB = require('src/tmdb/tmdb')
const requestRepository = new RequestRepository();
const cache = new Cache()
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'))
const request = new RequestRepository()
const tmdbMovieInfo = (id) => {
return tmdb.movieInfo(id)
}
const tmdbShowInfo = (id) => {
return tmdb.showInfo(id)
}
/**
* Controller: POST a media id to be donwloaded
@@ -8,22 +21,31 @@ const requestRepository = new RequestRepository();
* @param {Response} res
* @returns {Callback}
*/
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;
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const user_agent = req.headers['user-agent'];
const user = req.loggedInUser;
// This is the id that is the param of the url
const id = req.params.mediaId;
const type = req.query.type ? req.query.type.toLowerCase() : undefined
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const user_agent = req.headers['user-agent'];
const user = req.loggedInUser;
let mediaFunction = undefined
requestRepository.sendRequest(id, type, ip, user_agent, user)
.then(() => {
res.send({ success: true, message: 'Media item sucessfully requested!' });
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
});
if (type === 'movie') {
console.log('movie')
mediaFunction = tmdbMovieInfo
} else if (type === 'show') {
console.log('show')
mediaFunction = tmdbShowInfo
} else {
res.status(422).send({ success: false, error: 'Incorrect type. Allowed types: "movie" or "show"'})
}
if (mediaFunction === undefined) { res.status(200); return }
mediaFunction(id)
.then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user))
.then(() => res.send({ success: true, message: 'Media item successfully requested' }))
.catch(err => res.status(500).send({ success: false, error: err.message }))
}
module.exports = submitRequestController;

View File

@@ -0,0 +1,27 @@
const RequestRepository = require('src/request/request');
const request = new RequestRepository();
/**
* Controller: Fetch all requested items
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function fetchAllRequests(req, res) {
let { page, filter, sort, query } = req.query;
let sort_by = sort;
let sort_direction = undefined;
if (sort !== undefined && sort.includes(':')) {
[sort_by, sort_direction] = sort.split(':')
}
Promise.resolve()
.then(() => request.fetchAll(page, sort_by, sort_direction, filter, query))
.then((result) => res.send(result))
.catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = fetchAllRequests;

View File

@@ -0,0 +1,22 @@
const RequestRepository = require('src/request/request');
const request = new RequestRepository();
/**
* Controller: Get requested item by tmdb id and type
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function fetchAllRequests(req, res) {
const id = req.params.id;
const { type } = req.query;
Promise.resolve()
.then(() => request.getRequestByIdAndType(id, type))
.then((result) => res.send(result))
.catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = fetchAllRequests;

View File

@@ -0,0 +1,52 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const RequestRepository = require('src/request/request');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const request = new RequestRepository();
const tmdbMovieInfo = (id) => {
return tmdb.movieInfo(id)
}
const tmdbShowInfo = (id) => {
return tmdb.showInfo(id)
}
/**
* Controller: Request by id with type param
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function requestTmdbIdController(req, res) {
const { id, type } = req.body
console.log('body', req.body)
console.log('id & type', id, type)
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const user_agent = req.headers['user-agent'];
const user = req.loggedInUser;
let mediaFunction = undefined
if (type === 'movie') {
console.log('movie')
mediaFunction = tmdbMovieInfo
} else if (type === 'show') {
console.log('show')
mediaFunction = tmdbShowInfo
} else {
res.status(422).send({ success: false, error: 'Incorrect type. Allowed types: "movie" or "show"'})
}
mediaFunction(id)
.catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) })
.then((tmdbMedia) => request.requestFromTmdb(tmdbMedia, ip, user_agent, user))
.then(() => res.send({success: true, message: 'Request has been submitted.'}))
.catch((error) => {
res.status(501).send({ success: false, error: error.message });
})
}
module.exports = requestTmdbIdController;

View File

@@ -0,0 +1,35 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const searchHistory = new SearchHistory();
/**
* Controller: Search for movies by query and pagey
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function movieSearchController(req, res) {
const user = req.loggedInUser;
const { query, page } = req.query;
Promise.resolve()
.then(() => {
if (user) {
return searchHistory.create(user, query);
}
return null
})
.then(() => tmdb.movieSearch(query, page))
.then((movies) => {
res.send(movies);
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
});
}
module.exports = movieSearchController;

View File

@@ -0,0 +1,35 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const searchHistory = new SearchHistory();
/**
* Controller: Search for multi (movies, shows and people by query and pagey
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function multiSearchController(req, res) {
const user = req.loggedInUser;
const { query, page } = req.query;
Promise.resolve()
.then(() => {
if (user) {
return searchHistory.create(user, query);
}
return null
})
.then(() => tmdb.multiSearch(query, page))
.then((result) => {
res.send(result);
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
});
}
module.exports = multiSearchController;

View File

@@ -0,0 +1,35 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const searchHistory = new SearchHistory();
/**
* Controller: Search for person by query and pagey
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function personSearchController(req, res) {
const user = req.loggedInUser;
const { query, page } = req.query;
Promise.resolve()
.then(() => {
if (user) {
return searchHistory.create(user, query);
}
return null
})
.then(() => tmdb.personSearch(query, page))
.then((person) => {
res.send(person);
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
});
}
module.exports = personSearchController;

View File

@@ -0,0 +1,35 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const searchHistory = new SearchHistory();
/**
* Controller: Search for shows by query and pagey
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function showSearchController(req, res) {
const user = req.loggedInUser;
const { query, page } = req.query;
Promise.resolve()
.then(() => {
if (user) {
return searchHistory.create(user, query);
}
return null
})
.then(() => tmdb.showSearch(query, page))
.then((shows) => {
res.send(shows);
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
});
}
module.exports = showSearchController;

View File

@@ -10,7 +10,7 @@ const searchHistory = new SearchHistory();
*/
function historyController(req, res) {
const user = req.loggedInUser;
const username = user == undefined ? undefined : user.username;
const username = user === undefined ? undefined : user.username;
searchHistory.read(username)
.then((searchQueries) => {

View File

@@ -22,7 +22,7 @@ function loginController(req, res) {
.then(() => userRepository.checkAdmin(user))
.then((checkAdmin) => {
const token = new Token(user).toString(secret);
const admin_state = checkAdmin == 1 ? true : false;
const admin_state = checkAdmin === 1 ? true : false;
res.send({ success: true, token, admin: admin_state });
})
.catch((error) => {

View File

@@ -22,8 +22,10 @@ function registerController(req, res) {
.then(() => userRepository.checkAdmin(user))
.then((checkAdmin) => {
const token = new Token(user).toString(secret);
const admin_state = checkAdmin == 1 ? true : false;
res.send({ success: true, message: 'Welcome to Seasoned!', token, admin: admin_state });
const admin_state = checkAdmin === 1 ? true : false;
res.send({
success: true, message: 'Welcome to Seasoned!', token, admin: admin_state,
});
})
.catch((error) => {
res.status(401).send({ success: false, error: error.message });

View File

@@ -0,0 +1 @@
{"adult":false,"backdrop_path":"/mVr0UiqyltcfqxbAUcLl9zWL8ah.jpg","belongs_to_collection":{"id":422837,"name":"Blade Runner Collection","poster_path":"/cWESb1o9lW2i2Z3Xllv9u40aNIk.jpg","backdrop_path":"/bSHZIvLoPBWyGLeiAudN1mXdvQX.jpg"},"budget":150000000,"genres":[{"id":9648,"name":"Mystery"},{"id":878,"name":"Science Fiction"},{"id":53,"name":"Thriller"}],"homepage":"http://bladerunnermovie.com/","id":335984,"imdb_id":"tt1856101","original_language":"en","original_title":"Blade Runner 2049","overview":"Thirty years after the events of the first film, a new blade runner, LAPD Officer K, unearths a long-buried secret that has the potential to plunge what's left of society into chaos. K's discovery leads him on a quest to find Rick Deckard, a former LAPD blade runner who has been missing for 30 years.","popularity":30.03,"poster_path":"/gajva2L0rPYkEWjzgFlBXCAVBE5.jpg","production_companies":[{"id":79529,"logo_path":"/gVN3k8emmKy4iV4KREWcCtxusZK.png","name":"Torridon Films","origin_country":"US"},{"id":101829,"logo_path":"/8IOjCvgjq0zTrtP91cWD3kL2jMK.png","name":"16:14 Entertainment","origin_country":"US"},{"id":1645,"logo_path":"/6Ry6uNBaa0IbbSs1XYIgX5DkA9r.png","name":"Scott Free Productions","origin_country":""},{"id":5,"logo_path":"/71BqEFAF4V3qjjMPCpLuyJFB9A.png","name":"Columbia Pictures","origin_country":"US"},{"id":1088,"logo_path":"/9WOE5AQUXbOtLU6GTwfjS8OMF0v.png","name":"Alcon Entertainment","origin_country":"US"},{"id":78028,"logo_path":"/sTFcDFfJaSVT3sv3DoaZDE4SlGB.png","name":"Thunderbird Entertainment","origin_country":"CA"},{"id":174,"logo_path":"/ky0xOc5OrhzkZ1N6KyUxacfQsCk.png","name":"Warner Bros. Pictures","origin_country":"US"}],"production_countries":[{"iso_3166_1":"CA","name":"Canada"},{"iso_3166_1":"US","name":"United States of America"},{"iso_3166_1":"HU","name":"Hungary"},{"iso_3166_1":"GB","name":"United Kingdom"}],"release_date":"2017-10-04","revenue":259239658,"runtime":163,"spoken_languages":[{"iso_639_1":"en","name":"English"},{"iso_639_1":"fi","name":"suomi"}],"status":"Released","tagline":"There's still a page left.","title":"Blade Runner 2049","video":false,"vote_average":7.3,"vote_count":5478}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -7,12 +7,12 @@ const popularMoviesSuccess = require('test/fixtures/popular-movies-success-respo
describe('As a user I want to get popular movies', () => {
before(() => resetDatabase());
before(() => createCacheEntry('p:movie:1', popularMoviesSuccess));
before(() => createCacheEntry('pm:1', popularMoviesSuccess));
it('should return 200 with the information', () =>
request(app)
.get('/api/v1/tmdb/list/popular')
.get('/api/v2/movie/popular')
.expect(200)
.then(response => assert.equal(response.body.results.length, 20))
);
});
});

View File

@@ -7,12 +7,12 @@ const popularShowsSuccess = require('test/fixtures/popular-show-success-response
describe('As a user I want to get popular shows', () => {
before(() => resetDatabase());
before(() => createCacheEntry('p:show:1', popularShowsSuccess));
before(() => createCacheEntry('pt:1', popularShowsSuccess));
it('should return 200 with the information', () =>
request(app)
.get('/api/v1/tmdb/list/popular?type=show')
.get('/api/v2/show/popular')
.expect(200)
.then(response => assert.equal(response.body.results.length, 20))
);
});
});

View File

@@ -4,18 +4,19 @@ const app = require('src/webserver/app');
const request = require('supertest-as-promised');
const createUser = require('test/helpers/createUser');
const createToken = require('test/helpers/createToken');
const infoMovieSuccess = require('test/fixtures/arrival-info-success-response.json');
const infoMovieSuccess = require('test/fixtures/blade_runner_2049-info-success-response.json');
describe('As a user I want to request a movie', () => {
before(() => {
return resetDatabase()
.then(() => createUser('test_user', 'test@gmail.com', 'password'));
})
before(() => createCacheEntry('i:movie:329865', infoMovieSuccess));
before(() => createCacheEntry('mi:335984', infoMovieSuccess));
it('should return 200 when item is requested', () =>
request(app)
.post('/api/v1/plex/request/329865')
.post('/api/v2/request')
.send({ id: 335984, type: 'movie' })
.set('Authorization', createToken('test_user', 'secret'))
.expect(200)
);

View File

@@ -2,15 +2,15 @@ const createCacheEntry = require('test/helpers/createCacheEntry');
const resetDatabase = require('test/helpers/resetDatabase');
const request = require('supertest-as-promised');
const app = require('src/webserver/app');
const interstellarQuerySuccess = require('test/fixtures/interstellar-query-success-response.json');
const interstellarQuerySuccess = require('test/fixtures/interstellar-query-movie-success-response.json');
describe('As an anonymous user I want to search for a movie', () => {
before(() => resetDatabase());
before(() => createCacheEntry('se:1:multi:interstellar', interstellarQuerySuccess));
before(() => createCacheEntry('mos:1:interstellar', interstellarQuerySuccess));
it('should return 200 with the search results even if user is not logged in', () =>
request(app)
.get('/api/v1/tmdb/search?query=interstellar&page=1')
.get('/api/v2/search/movie?query=interstellar&page=1')
.expect(200)
);
});

View File

@@ -0,0 +1,30 @@
const assert = require('assert');
const convertTmdbToMovie = require('src/tmdb/convertTmdbToMovie');
const bladeRunnerQuerySuccess = require('test/fixtures/blade_runner_2049-info-success-response.json')
describe('Convert tmdb movieInfo to movie', () => {
beforeEach(() => this.bladeRunnerTmdbMovie = bladeRunnerQuerySuccess);
it('should translate the tmdb release date to movie year', () => {
const bladeRunner = convertTmdbToMovie(this.bladeRunnerTmdbMovie);
assert.strictEqual(bladeRunner.year, 2017);
});
it('should translate the tmdb release date to instance of Date', () => {
const bladeRunner = convertTmdbToMovie(this.bladeRunnerTmdbMovie);
assert(bladeRunner.release_date instanceof Date);
});
it('should translate the tmdb title to title', () => {
const bladeRunner = convertTmdbToMovie(this.bladeRunnerTmdbMovie);
assert.equal(bladeRunner.title, 'Blade Runner 2049');
});
it('should translate the tmdb vote_average to rank', () => {
const bladeRunner = convertTmdbToMovie(this.bladeRunnerTmdbMovie);
assert.equal(bladeRunner.rank, 7.3);
});
});

View File

@@ -0,0 +1,31 @@
const assert = require('assert');
// const Movie = require('src/movie/movie');
const TMDB = require('src/tmdb/tmdb');
const Cache = require('src/tmdb/cache');
const SqliteDatabase = require('src/database/sqliteDatabase');
const tmdbMock = require('test/helpers/tmdbMock');
const emptyQuerySuccess = require('test/fixtures/empty-query-success-response.json');
const interstellarQuerySuccess = require('test/fixtures/arrival-info-success-response.json');
const popularMovieSuccessResponse = require('test/fixtures/popular-movies-success-response.json');
describe('TMDB', function test() {
beforeEach(() => {
this.mockMoviedb = tmdbMock();
this.database = new SqliteDatabase(':memory:');
return Promise.resolve()
.then(() => this.database.setUp());
});
describe('popular', () => {
it('should return the "Blade Runner 2049" year in the collection of popular movies', () => {
this.mockMoviedb.response = popularMovieSuccessResponse;
const cache = new Cache(this.database);
const tmdb = new TMDB(cache, 'bogus-api-key', this.mockMoviedb);
return tmdb.popular()
.then(movies =>
assert.equal(movies[0].title, "Blade Runner 2049")
);
});
})
});

File diff suppressed because it is too large Load Diff