├── .gitignore ├── public ├── config.inc.php.template ├── style.css ├── query.php ├── index.php ├── functions.inc.php └── app.js ├── LICENSE ├── README.md └── tvshowmanager.sql /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | config.inc.php 3 | -------------------------------------------------------------------------------- /public/config.inc.php.template: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | .navbar-right { margin-right: 0; } 2 | 3 | 4 | /** Animation **/ 5 | .gly-spin { 6 | -webkit-animation: spin 2s infinite linear; 7 | -moz-animation: spin 2s infinite linear; 8 | -o-animation: spin 2s infinite linear; 9 | animation: spin 2s infinite linear; 10 | } 11 | @-moz-keyframes spin { 12 | 0% { 13 | -moz-transform: rotate(0deg); 14 | } 15 | 100% { 16 | -moz-transform: rotate(359deg); 17 | } 18 | } 19 | @-webkit-keyframes spin { 20 | 0% { 21 | -webkit-transform: rotate(0deg); 22 | } 23 | 100% { 24 | -webkit-transform: rotate(359deg); 25 | } 26 | } 27 | @-o-keyframes spin { 28 | 0% { 29 | -o-transform: rotate(0deg); 30 | } 31 | 100% { 32 | -o-transform: rotate(359deg); 33 | } 34 | } 35 | @keyframes spin { 36 | 0% { 37 | -webkit-transform: rotate(0deg); 38 | transform: rotate(0deg); 39 | } 40 | 100% { 41 | -webkit-transform: rotate(359deg); 42 | transform: rotate(359deg); 43 | } 44 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 WaeCo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tv Show Manager 2 | =============== 3 | 4 | **This Project is currently porting to Node.JS.** Please have a look at [node](https://github.com/WaeCo/TVShowManager/tree/node) branch. 5 | 6 | This is a nice and simple Web UI that allows you to keep track of your TV shows. 7 | It is useful if you are watching multiple shows at the same time and cannot remember at which episode you stopped watching. 8 | Also, it shows you if a new episode is available or when the next comes online. 9 | 10 | ![Screenshot](http://i.imgur.com/HfCwj2K.png) 11 | 12 | 13 | 14 | ## Usage 15 | You can either use my site at https://www.waeco-soft.com/TvShowManager or setup your own homepage. 16 | 17 | When using my site you can try it out without creating an account. But keep in mind your shows won't be saved unless you create an account. 18 | 19 | ## Installation 20 | Clone this repository 21 | 22 | #### Database 23 | 1. Install a MySQL server 24 | 2. Create a database `tvshowmanager` 25 | 3. Create a user `tvshowmanager` and set a password 26 | 4. Import `tvshowmanager.sql` into that database 27 | 28 | #### Webserver 29 | 1. Install a Webserver and setup PHP 30 | 2. Copy all files from `public` into a directory in your webroot 31 | 32 | #### Config 33 | 1. Get a API key from http://thetvdb.com/?tab=apiregister 34 | 2. Rename `config.inc.php.template` to `config.inc.php` 35 | 3. Modify the settings to match your MySQL server and your API key 36 | -------------------------------------------------------------------------------- /public/query.php: -------------------------------------------------------------------------------- 1 | error); 8 | 9 | $user = 0; 10 | if(isset($_SESSION['userid'])) 11 | $user = $_SESSION['userid']; 12 | elseif(isset($_COOKIE['usertoken'])) { 13 | $res = login2($_COOKIE['usertoken']); 14 | if($res['msg'] == 'OK') 15 | $user = $_SESSION['userid']; 16 | } 17 | 18 | $postdata = file_get_contents("php://input"); 19 | if(!empty($postdata)) 20 | $_POST = json_decode($postdata,TRUE); 21 | 22 | if(!empty($_GET['search'])) 23 | $res = search(urldecode($_GET['search'])); 24 | 25 | elseif(!empty($_GET['show'])) 26 | $res = getShow($_GET['show'], isset($_GET['force']), isset($_GET['q'])); 27 | 28 | elseif(isset($_GET['usershows'])) 29 | $res = getUserShows(); 30 | 31 | elseif(!empty($_POST['addshow'])) 32 | $res = addShow($_POST['addshow']); 33 | 34 | elseif(!empty($_POST['delshow'])) 35 | $res = delShow($_POST['delshow']); 36 | 37 | elseif(!empty($_POST['updateshow'])) 38 | $res = updateShow($_POST['updateshow']); 39 | 40 | elseif(!empty($_POST['username']) && !empty($_POST['password'])) 41 | $res = login($_POST['username'], $_POST['password'], isset($_POST['stay']) && $_POST['stay']); 42 | 43 | elseif(!empty($_POST['token'])) 44 | $res = login2($_POST['password']); 45 | 46 | elseif(isset($_POST['logout'])) 47 | $res = logout(); 48 | 49 | elseif(!empty($_POST['registername']) && !empty($_POST['password'])) 50 | $res = register($_POST['registername'], $_POST['password']); 51 | 52 | else { 53 | $res = array('msg' => 'Command not set', 'post' => $_POST, 'get' => $_GET); 54 | } 55 | 56 | header("Content-type: application/json"); 57 | echo json_encode($res); 58 | 59 | ?> 60 | -------------------------------------------------------------------------------- /tvshowmanager.sql: -------------------------------------------------------------------------------- 1 | SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; 2 | SET time_zone = "+00:00"; 3 | 4 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 5 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 6 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 7 | /*!40101 SET NAMES utf8 */; 8 | 9 | 10 | CREATE TABLE IF NOT EXISTS `episode` ( 11 | `show_id` int(11) NOT NULL, 12 | `season` smallint(6) NOT NULL, 13 | `episode` smallint(6) NOT NULL, 14 | `title` text NOT NULL, 15 | `airdate` date NOT NULL 16 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 17 | 18 | CREATE TABLE IF NOT EXISTS `show` ( 19 | `show_id` int(11) NOT NULL, 20 | `imdb_id` varchar(12) NOT NULL, 21 | `name` varchar(255) NOT NULL, 22 | `started` date NOT NULL, 23 | `ended` date NOT NULL, 24 | `air_day` varchar(12) NOT NULL, 25 | `air_time` varchar(12) NOT NULL, 26 | `status` varchar(32) NOT NULL, 27 | `image` text NOT NULL, 28 | `seasons` int(11) NOT NULL, 29 | `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' 30 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 31 | 32 | CREATE TABLE IF NOT EXISTS `user` ( 33 | `user_id` int(11) NOT NULL, 34 | `name` varchar(64) NOT NULL, 35 | `password` varchar(64) NOT NULL 36 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 37 | 38 | CREATE TABLE IF NOT EXISTS `user_shows` ( 39 | `user_id` int(11) NOT NULL, 40 | `show_id` int(11) NOT NULL, 41 | `last_season` int(11) NOT NULL DEFAULT '0', 42 | `last_episode` int(11) NOT NULL DEFAULT '0', 43 | `enabled` tinyint(1) NOT NULL DEFAULT '1', 44 | `favourite` tinyint(1) NOT NULL DEFAULT '0' 45 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 46 | 47 | CREATE TABLE IF NOT EXISTS `user_token` ( 48 | `user_id` int(11) NOT NULL, 49 | `token` varchar(32) NOT NULL 50 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 51 | 52 | 53 | ALTER TABLE `episode` 54 | ADD PRIMARY KEY (`show_id`,`episode`,`season`); 55 | 56 | ALTER TABLE `show` 57 | ADD PRIMARY KEY (`show_id`), 58 | ADD UNIQUE KEY `imdb_id` (`imdb_id`), 59 | ADD KEY `name` (`name`); 60 | 61 | ALTER TABLE `user` 62 | ADD PRIMARY KEY (`user_id`), 63 | ADD UNIQUE KEY `name` (`name`); 64 | 65 | ALTER TABLE `user_shows` 66 | ADD PRIMARY KEY (`user_id`,`show_id`) USING BTREE, 67 | ADD KEY `show_id` (`show_id`); 68 | 69 | ALTER TABLE `user_token` 70 | ADD PRIMARY KEY (`user_id`,`token`); 71 | 72 | 73 | ALTER TABLE `user` 74 | MODIFY `user_id` int(11) NOT NULL AUTO_INCREMENT; 75 | 76 | ALTER TABLE `user_shows` 77 | ADD CONSTRAINT `user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE; 78 | 79 | ALTER TABLE `user_token` 80 | ADD CONSTRAINT `user_token_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE; 81 | 82 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 83 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 84 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 85 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | error); 10 | 11 | $user = 0; 12 | $name = ''; 13 | if(isset($_SESSION['userid'])) { 14 | $user = $_SESSION['userid']; 15 | $name = $_SESSION['username']; 16 | } elseif(isset($_COOKIE['usertoken'])) { 17 | $res = login2($_COOKIE['usertoken']); 18 | if($res['msg'] == 'OK') { 19 | $user = $_SESSION['userid']; 20 | $name = $_SESSION['username']; 21 | } 22 | } 23 | 24 | ?> 25 | 26 | 27 | 28 | TV Show Manager 29 | 30 | 31 | 32 | 33 | 34 | 35 | 43 | 44 | 45 |
46 |
47 | 95 | 96 |
97 | '.$res['msg'].'
'; ?> 98 |
×Added {{last_added_show}} to your list
99 | 100 | 125 | 126 |
127 | 128 | 129 | 134 | 138 | 139 | 143 | 144 | 145 | 146 | 147 | 148 | 151 | 152 | 157 | 158 | 159 | 160 | 166 | 167 |
130 | 131 | 132 | 133 | 135 | Name 136 | 137 | Next Episode 140 | Status 141 | 142 | Episode NameDate
149 | 150 | {{show.name}} 153 | {{show.next_ep}} 154 | 155 | 156 | {{show.status}}{{show.next_ep_name}}{{show.next_ep_date}} 161 | 162 | 163 | 164 | 165 |
168 |
169 |
170 | 171 | 175 |
176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /public/functions.inc.php: -------------------------------------------------------------------------------- 1 | 2); 17 | if($data === FALSE) { 18 | # http_response_code(404); 19 | # echo "Nope"; 20 | return FALSE; 21 | } 22 | 23 | $json = substr($data, 5+strlen($str)+2, -1); 24 | $obj = json_decode($json,TRUE); 25 | 26 | #print_r($obj); 27 | 28 | $res = array('show' => array()); 29 | if(isset($obj['d'])) { 30 | foreach ($obj['d'] as $show) { 31 | #echo strtolower($show['l'])."\n"; 32 | if(isset($show['q']) && $show['q'] == "TV series" && startsWith(strtolower($show['l']), strtolower($title))) { 33 | $res['show'][] = array( 34 | 'imdb_id' => $show['id'], 35 | 'name' => $show['l'], 36 | 'year' => $show['y'], 37 | 'img' => isset($show['i']) ? $show['i'][0] : '' 38 | ); 39 | } 40 | } 41 | } 42 | return $res; 43 | } 44 | 45 | //http://thetvdb.com/wiki/index.php?title=Programmers_API 46 | function getShow($sid, $force, $quiet) { 47 | global $db, $user, $apikey; 48 | 49 | $stm = $db->prepare(' 50 | SELECT s.*, us.last_season, us.last_episode, us.enabled, us.favourite 51 | FROM `show` as s 52 | LEFT JOIN user_shows as us 53 | ON s.show_id = us.show_id AND us.user_id = ? 54 | WHERE s.show_id = ? 55 | '); 56 | $stmEpisodes = $db->prepare('SELECT season,episode,title,airdate FROM episode WHERE show_id = ?'); 57 | 58 | if(!$force) { 59 | $res = doOutput($stm, $stmEpisodes, $sid, $user, 'local', $quiet); 60 | if($res) 61 | return $res; 62 | } 63 | 64 | $data = @file_get_contents("http://thetvdb.com/api/$apikey/series/$sid/all"); 65 | if($data === FALSE) { 66 | http_response_code(502); 67 | return "API Error"; 68 | } 69 | 70 | $xml = simplexml_load_string($data); 71 | $json = json_encode($xml); 72 | $obj = json_decode($json,TRUE); 73 | 74 | if(!isset($obj['Series'])) { 75 | http_response_code(404); 76 | return "Show '$sid' not found"; 77 | } 78 | 79 | #print_r($obj); 80 | $series = $obj['Series']; 81 | $episodes = $obj['Episode']; 82 | 83 | #Inster Show 84 | $stmShow = $db->prepare('REPLACE INTO `show` (show_id, imdb_id, name, started, air_day, air_time, status, image, updated_at) VALUES(?, ?, ?, ?, ?, ?, ?, ?, NOW())') or die($db->error); 85 | $stmEp = $db->prepare('INSERT INTO episode (show_id, season, episode, title, airdate) VALUES(?, ?, ?, ?, ?)') or die($db->error); 86 | 87 | $db->query("DELETE FROM episode WHERE show_id = $sid") or die($db->error); 88 | 89 | $img = empty($series['poster']) ? '' : 'http://thetvdb.com/banners/_cache/'.$series['poster']; 90 | $stmShow->bind_param('isssssss', $series['id'], $series['IMDB_ID'], $series['SeriesName'], $series['FirstAired'], $series['Airs_DayOfWeek'], $series['Airs_Time'], $series['Status'], $img); 91 | $stmShow->execute() or die($db->error); 92 | 93 | #Inster Episodes 94 | if(isset($episodes['EpisodeName'])) { 95 | $episode = $episodes; 96 | $episode['FirstAired'] = empty($episode['FirstAired']) ? '0000-00-00' : $episode['FirstAired']; 97 | $episode['EpisodeName'] = empty($episode['EpisodeName']) ? 'TBA' : $episode['EpisodeName']; 98 | $stmEp->bind_param('iiiss', $sid, $episode['SeasonNumber'], $episode['EpisodeNumber'], $episode['EpisodeName'], $episode['FirstAired']); 99 | $stmEp->execute(); 100 | } else { 101 | foreach($episodes as $episode) { 102 | $episode['FirstAired'] = empty($episode['FirstAired']) ? '0000-00-00' : $episode['FirstAired']; 103 | $episode['EpisodeName'] = empty($episode['EpisodeName']) ? 'TBA' : $episode['EpisodeName']; 104 | $stmEp->bind_param('iiiss', $sid, $episode['SeasonNumber'], $episode['EpisodeNumber'], $episode['EpisodeName'], $episode['FirstAired']); 105 | $stmEp->execute(); 106 | } 107 | } 108 | 109 | $res = doOutput($stm, $stmEpisodes, $sid, $user, 'remote', $quiet); 110 | if($res) 111 | return $res; 112 | else { 113 | http_response_code(404); 114 | return "Some Error"; 115 | } 116 | } 117 | 118 | function getUserShows() { 119 | global $db, $user; 120 | 121 | $stm = $db->prepare(' 122 | SELECT 123 | s.*, 124 | us.last_season, us.last_episode, us.enabled, us.favourite 125 | FROM user_shows as us 126 | JOIN `show` as s 127 | USING(show_id) 128 | WHERE us.user_id = ? AND 0 = ? 129 | '); 130 | $stmEpisodes = $db->prepare('SELECT season,episode,title,airdate FROM episode WHERE show_id = ?'); 131 | $res = doOutput($stm, $stmEpisodes, 0, $user, 'local'); 132 | if(isset($res['show_id'])) 133 | $res = array($res); 134 | return $res; 135 | } 136 | 137 | function doOutput($stm, $stmEpisodes, $sid, $user, $type, $quiet = false) { 138 | $stm->bind_param('ii', $user, $sid); 139 | 140 | if($stm->execute()) { 141 | $res = $stm->get_result(); 142 | $result = array(); 143 | while($obj = $res->fetch_assoc()) { 144 | $obj['request_type'] = $type; 145 | $obj['seasons'] = array(); 146 | if($obj['name']) { 147 | $stmEpisodes->bind_param('i', $obj['show_id']); 148 | if($stmEpisodes->execute()) { 149 | $res2 = $stmEpisodes->get_result(); 150 | while ($row = $res2->fetch_assoc()) { 151 | if($row['season'] == 0) continue; 152 | if(isset($obj['seasons'][$row['season']])) 153 | $obj['seasons'][$row['season']][] = $row; 154 | else 155 | $obj['seasons'][$row['season']] = array($row); 156 | } 157 | } 158 | } else 159 | $obj = $obj['show_id']; 160 | if($res->num_rows == 1) 161 | $result = $obj; 162 | else 163 | $result[] = $obj; 164 | } 165 | if($quiet) 166 | return "OK"; 167 | else 168 | return $result; 169 | } 170 | return false; 171 | } 172 | 173 | 174 | function addShow($id) { 175 | global $db, $user; 176 | $id = $db->escape_string($id); 177 | $res = $db->query("SELECT show_id FROM `show` WHERE imdb_id = '$id'"); 178 | if($res->num_rows == 1) { 179 | $show_id = $res->fetch_assoc()['show_id']; 180 | } else { 181 | $data = @file_get_contents("http://thetvdb.com/api/GetSeriesByRemoteID.php?imdbid=$id"); 182 | if($data === FALSE) { 183 | http_response_code(502); 184 | return "API Error"; 185 | } 186 | 187 | $xml = simplexml_load_string($data); 188 | $json = json_encode($xml); 189 | $obj = json_decode($json,TRUE); 190 | 191 | if(!isset($obj['Series'])) { 192 | http_response_code(404); 193 | return "Show not found"; 194 | } 195 | $show_id = $obj['Series']['seriesid']; 196 | getShow($show_id, TRUE, TRUE); 197 | } 198 | $stm = $db->prepare('INSERT INTO user_shows (user_id, show_id) VALUES(?,?)'); 199 | $stm->bind_param('ii', $user, $show_id); 200 | $stm->execute(); 201 | return $show_id; 202 | } 203 | 204 | function delShow($id) { 205 | global $db, $user; 206 | $stm = $db->prepare('DELETE FROM user_shows WHERE user_id = ? AND show_id = ?'); 207 | $stm->bind_param('ii', $user, $id); 208 | return $stm->execute(); 209 | } 210 | 211 | function updateShow($id) { 212 | global $db, $user; 213 | $stm = $db->prepare('UPDATE user_shows SET last_season = ?, last_episode = ?, enabled = ?, favourite = ? WHERE user_id = ? AND show_id = ?'); 214 | $stm->bind_param('iiiiii', $_POST['last_season'], $_POST['last_episode'], $_POST['enabled'], $_POST['favourite'], $user, $id); 215 | return $stm->execute(); 216 | } 217 | 218 | function login($user, $pw, $stay = false) { 219 | global $db; 220 | $user = $db->escape_string($user); 221 | $pw = $db->escape_string($pw); 222 | $res = $db->query("SELECT user_id,name FROM `user` WHERE `name` = '$user' AND `password` = PASSWORD('$pw')") or die($db->error); 223 | if($res->num_rows == 1) { 224 | $row = $res->fetch_assoc(); 225 | $id = $row['user_id']; 226 | $_SESSION['userid'] = $id; 227 | $_SESSION['username'] = $row['name']; 228 | if($stay) { 229 | $token = md5("$id ## $user " + rand() + date('c')); 230 | $db->query("INSERT INTO `user_token` VALUES ($id, '$token')") or die($db->error); 231 | setcookie("usertoken", $token, time()+60*60*24*30, "", "", false, true); 232 | } else { 233 | setcookie("usertoken", NULL); 234 | } 235 | 236 | return array('msg' => 'OK'); 237 | } else 238 | return array('msg' => 'Username or Password invalid'); 239 | } 240 | function login2($usertoken) { 241 | global $db; 242 | $token = $db->escape_string($usertoken); 243 | $res = $db->query("SELECT user_id, name FROM `user` JOIN `user_token` USING(user_id) WHERE `token` = '$token'") or die($db->error); 244 | if($res->num_rows == 1) { 245 | $row = $res->fetch_assoc(); 246 | $_SESSION['userid'] = $row['user_id']; 247 | $_SESSION['username'] = $row['name']; 248 | setcookie("usertoken", $usertoken, time()+60*60*24*30, "", "", false, true); 249 | return array('msg' => 'OK'); 250 | } else 251 | return array('msg' => 'Session expiered'); 252 | } 253 | 254 | function logout() { 255 | global $db; 256 | $id = $_SESSION['userid']; 257 | if($id) 258 | $db->query("DELETE FROM `user_token` WHERE `user_id` = $id"); 259 | unset($_SESSION['userid']); 260 | unset($_SESSION['username']); 261 | setcookie("usertoken", NULL); 262 | } 263 | 264 | function register($user, $pw) { 265 | global $db; 266 | $stm = $db->prepare('INSERT INTO user (name, password) VALUES(?,PASSWORD(?))'); 267 | $stm->bind_param('ss', $user, $pw); 268 | if($stm->execute()) 269 | echo json_encode(array('msg' => 'OK')); 270 | else 271 | echo json_encode(array('msg' => 'Username already exists')); 272 | exit; 273 | } 274 | 275 | function startsWith($haystack, $needle) { 276 | // search backwards starting from haystack length characters from the end 277 | return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false; 278 | } 279 | -------------------------------------------------------------------------------- /public/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('TVShowManager', ['ngRoute', 'ui.bootstrap']); 2 | app.config(['$compileProvider', function ($compileProvider) { 3 | $compileProvider.debugInfoEnabled(true); 4 | }]); 5 | 6 | app.factory('ShowQuery', ['$http', '$timeout', function($http, $timeout) { 7 | return new function() { 8 | this.search = function(name) { 9 | return $http.get("query.php", {params: {search: name}}); 10 | }; 11 | this.showinfo = function(id, force) { 12 | if(force) 13 | return $http.get("query.php", {params: {show: id, force: true}}); 14 | else 15 | return $http.get("query.php", {params: {show: id}}); 16 | }; 17 | 18 | this.user_add_show = function(id) { 19 | return $http.post("query.php", {addshow: id}); 20 | }; 21 | 22 | this.user_del_show = function(show) { 23 | return $http.post("query.php", {delshow: show.id}); 24 | }; 25 | 26 | this.user_del_show = function(show) { 27 | return $http.post("query.php", {delshow: show.id}); 28 | }; 29 | 30 | this.login = function(user, pw, stay) { 31 | return $http.post("query.php", {username: user, password: pw, stay: !!stay}); 32 | }; 33 | 34 | this.login_token = function(user, token) { 35 | return $http.post("query.php", {username: user, token: token}); 36 | }; 37 | 38 | this.logout = function() { 39 | return $http.post("query.php", {logout: true}); 40 | }; 41 | 42 | this.register = function(user, pw) { 43 | return $http.post("query.php", {registername: user, password: pw}); 44 | }; 45 | 46 | var timer; 47 | this.user_show_update = function(show) { 48 | if(timer) 49 | $timeout.cancel(timer); 50 | timer = $timeout(function(){ 51 | $http.post("query.php", { 52 | updateshow: show.id, 53 | last_season: show.last_season, 54 | last_episode: show.last_episode, 55 | enabled: !show.done, 56 | favourite: show.favourite 57 | }, {headers : {'Content-Type': 'application/x-www-form-urlencoded'}}); 58 | }, 500); 59 | }; 60 | }; 61 | }]); 62 | 63 | app.factory('TVShow', ['ShowQuery', function (ShowQuery) { 64 | return function(id, name) { 65 | this.id = id; 66 | this.name = name || 'Unknown Show'; 67 | this.__defineGetter__('class', function() { 68 | switch(this.status) { 69 | case 'Available': 70 | return 'success'; 71 | case 'Unavailable': 72 | return 'danger'; 73 | case 'Disabled': 74 | case 'Ended': 75 | case 'Canceled': 76 | return 'active'; 77 | case 'Undetermined': 78 | case 'Error': 79 | return 'warning'; 80 | default: 81 | return ''; 82 | }; 83 | }); 84 | this.status = '?'; 85 | this.show_status = ''; 86 | this.__defineGetter__('done', function() { 87 | return this.status == 'Disabled' || this.ended; 88 | }); 89 | this.__defineGetter__('disabled', function() { 90 | return this.status == 'Disabled'; 91 | }); 92 | this.__defineGetter__('first', function() { 93 | return this.last_season == 0; 94 | }); 95 | this.__defineGetter__('ended', function() { 96 | return this.status == 'Ended' || this.status == 'Canceled'; 97 | }); 98 | this.__defineGetter__('next_ep', function() { 99 | var next = this.getNext(); 100 | if(next) 101 | return 'S' + pad(next.season, 2) + ' E' + pad(next.episode, 2); 102 | if(this.ended) 103 | return 'End'; 104 | //Fallback 105 | return 'S' + pad(this.last_season, 2) + ' E' + pad(this.last_episode, 2) + "+"; 106 | //return 'Next'; 107 | }); 108 | this.__defineGetter__('next_ep_name', function() { 109 | var next = this.getNext(); 110 | return next ? next.title : this.show_status; 111 | }); 112 | this.__defineGetter__('next_ep_date', function() { 113 | var next = this.getNext(); 114 | return next ? next.airdate : ''; 115 | }); 116 | 117 | this.last_season = 0; 118 | this.last_episode = 0; 119 | this.favourite = false; 120 | 121 | this.loading = false; 122 | this.image = ''; 123 | 124 | this.seasons = {}; 125 | 126 | /* Functions */ 127 | 128 | this.inc = function() { 129 | if(this.loading) return; 130 | this.last_episode++; 131 | if(this.last_season == 0 || this.last_episode > this.seasons[this.last_season].length) { 132 | if(this.last_season < this.seasons.length) { 133 | this.last_season++; 134 | this.last_episode = 1; 135 | } else 136 | this.last_episode--; 137 | } 138 | this.update_status(); 139 | ShowQuery.user_show_update(this); 140 | }; 141 | 142 | this.dec = function() { 143 | if(this.loading) return; 144 | this.last_episode--; 145 | if(this.last_episode == 0) { 146 | this.last_episode = 1; 147 | if(this.last_season > 1) { 148 | this.last_season--; 149 | this.last_episode = this.seasons[this.last_season].length; 150 | } else { 151 | this.last_episode = 0; 152 | this.last_season = 0; 153 | } 154 | } 155 | this.update_status(); 156 | ShowQuery.user_show_update(this); 157 | }; 158 | 159 | this.activate = function() { 160 | if(this.status != 'Disabled') { 161 | this.status = 'Disabled'; 162 | } else 163 | this.update_status(); 164 | ShowQuery.user_show_update(this); 165 | console.log(this); 166 | }; 167 | 168 | this.setFavourite = function() { 169 | this.favourite = !this.favourite; 170 | ShowQuery.user_show_update(this); 171 | } 172 | 173 | this.getNext = function() { 174 | if(this.last_season > 0 && this.seasons[this.last_season].length > this.last_episode) 175 | return this.seasons[this.last_season][this.last_episode]; 176 | if(this.seasons.length >= this.last_season + 1) 177 | return this.seasons[this.last_season + 1][0]; 178 | return null; 179 | } 180 | 181 | this.update_status = function(data) { 182 | if(data) { 183 | this.seasons = data.seasons; 184 | var max = 0; 185 | for(var i in data.seasons) 186 | max = parseInt(i) > max ? parseInt(i) : max; 187 | this.seasons.length = max; 188 | this.image = data.image; 189 | this.name = data.name; 190 | this.show_status = data.status == 'Canceled/Ended' ? 'Ended' : data.status; 191 | if((data.enabled != null && !data.enabled) && !(this.show_status == 'Ended' || this.show_status == 'Canceled')) 192 | this.status = 'Disabled'; 193 | 194 | this.last_season = data.last_season || 0; 195 | this.last_episode = data.last_episode || 0; 196 | this.favourite = !!data.favourite; 197 | 198 | if(this.status == 'Disabled') return; 199 | } 200 | 201 | if(this.seasons.length == this.last_season && this.last_episode >= this.seasons[this.last_season].length) { 202 | if(this.show_status == 'Ended' || this.show_status == 'Canceled') { 203 | this.status = this.show_status; 204 | } else { 205 | this.status = 'Unavailable'; 206 | } 207 | } else if(this.seasons.length >= this.last_season) { 208 | var nextep = {}; 209 | if(this.last_season > 0 && this.last_episode < this.seasons[this.last_season].length) 210 | nextep = this.seasons[this.last_season][this.last_episode]; 211 | else 212 | nextep = this.seasons[this.last_season+1][0]; 213 | 214 | var now = new Date(); 215 | var next = nextep.airdate.split('-'); 216 | var nextdate = new Date(next[0], next[1]-1, next[2]); 217 | if(nextdate.getTime() < 0) { 218 | this.status = 'Undetermined'; 219 | } else if(nextdate <= now) { 220 | this.status = 'Available'; 221 | } else { 222 | this.status = 'Unavailable'; 223 | } 224 | } else { 225 | this.status = 'Error'; 226 | console.error('Last season is greater then total seasons', this); 227 | } 228 | 229 | if(data && (data.enabled != null && !data.enabled) && !(this.status == 'Ended' || this.status == 'Canceled')) { 230 | this.status = 'Disabled'; 231 | } 232 | }; 233 | 234 | this.refresh = function(force) { 235 | //console.log("Refreshing Show ", this.name, this.id); 236 | 237 | this.loading = true; 238 | var me = this; 239 | ShowQuery.showinfo(this.id, force). 240 | success(function(data, status, headers, config) { 241 | //console.log(data); 242 | me.update_status(data); 243 | 244 | me.loading = false; 245 | }).error(function() { 246 | me.status = 'Error'; 247 | me.loading = false; 248 | }); 249 | }; 250 | if(typeof id == 'object') { 251 | this.id = id.show_id; 252 | this.update_status(id); 253 | } else 254 | this.refresh(); 255 | 256 | this.delete = function() { 257 | //console.log("Deleting Show", this.name); 258 | ShowQuery.user_del_show(this); 259 | } 260 | }; 261 | }]); 262 | 263 | app.controller('GlobalController', [ 264 | '$scope', 'ShowQuery', '$timeout', '$interval', 'TVShow','$uibModal', 265 | function($scope, ShowQuery, $timeout, $interval, TVShow, $modal) 266 | { 267 | $scope.show_predicate = 'favourite'; 268 | $scope.show_reverse = true; 269 | $scope.get_show_predicate = function() { 270 | function order_status(elem) { 271 | switch(elem.status) { 272 | case 'Available': 273 | return 100; 274 | case 'Unavailable': 275 | return 90; 276 | case 'Done': 277 | case 'Ended': 278 | case 'Canceled': 279 | return 50; 280 | case 'Error': 281 | return 10; 282 | default: 283 | return 1; 284 | } 285 | }; 286 | return [$scope.show_predicate=='status' ? order_status : $scope.show_predicate, '+favourite', '-name', order_status]; 287 | }; 288 | 289 | $scope.user = { 290 | loggedin: userid ? true : false, 291 | id: userid, 292 | name: username 293 | }; 294 | 295 | 296 | $scope.shows = []; 297 | for(var i = 0; i < usershows.length; i++) 298 | $scope.shows.push(new TVShow(usershows[i])); 299 | 300 | var old_search = ''; 301 | var timer = 0; 302 | $scope.show_search = function(name) { 303 | $scope.new_error = false; 304 | if(!name || name == '') return; 305 | $scope.search.searching = true; 306 | if(timer){ 307 | $timeout.cancel(timer); 308 | } 309 | timer = $timeout(function(){ 310 | if(old_search == name) 311 | $scope.search.open = true; 312 | else { 313 | //$scope.search.results = []; 314 | ShowQuery.search(name) 315 | .success(function(data) { 316 | //console.log("Seach open", data); 317 | old_search = name; 318 | if(data) 319 | $scope.search.results = Array.isArray(data.show) ? data.show : [data.show]; 320 | else { 321 | $scope.search.results = []; 322 | $scope.search.new_error = true; 323 | } 324 | $scope.search.open = true; 325 | $scope.search.searching=false; 326 | }); 327 | } 328 | },500); 329 | }; 330 | 331 | $scope.show_add = function(sid, name) { 332 | var id = 0; 333 | if(sid) 334 | id=sid; 335 | else if($scope.search.results && $scope.search.results.length > 0) 336 | id=$scope.search.results[0].imdb_id; 337 | 338 | if(id) { 339 | if(find_array($scope.shows, function(elem) { return elem.id == id; })) 340 | alert("Show is already in list"); 341 | else { 342 | ShowQuery.user_add_show(id). 343 | success(function(data,status) { 344 | $scope.shows.push(new TVShow(data, name)); 345 | $scope.last_added_show = name; 346 | }). 347 | error(function() { 348 | alert("Something went wrong. Please try again"); 349 | }); 350 | 351 | $scope.new_name = ''; 352 | $scope.search.open = false; 353 | $scope.search.searching=false; 354 | } 355 | } else { 356 | //console.log("No ID set"); 357 | $scope.new_error = true; 358 | } 359 | }; 360 | 361 | $scope.show_delete = function(show) { 362 | for(var i = 0; i < $scope.shows.length; i++) { 363 | if($scope.shows[i].id == show.id) { 364 | $scope.shows.splice(i,1); 365 | break; 366 | } 367 | } 368 | show.delete(); 369 | }; 370 | 371 | var inc_interval, inc_timeout; 372 | document.querySelector("body"). 373 | addEventListener("mouseup", function() { 374 | if(inc_timeout) { 375 | $timeout.cancel(inc_timeout); 376 | inc_timeout = null; 377 | } 378 | if(inc_interval) { 379 | $interval.cancel(inc_interval); 380 | inc_interval = null; 381 | } 382 | }); 383 | $scope.show_start_inc = function(show) { 384 | show.inc(); 385 | if(inc_timeout) 386 | $timeout.cancel(inc_timeout); 387 | inc_timeout = $timeout(function(){ 388 | if(inc_interval) 389 | $interval.cancel(inc_interval); 390 | inc_interval = $interval(function() { 391 | show.inc(); 392 | }, 100); 393 | }, 250); 394 | } 395 | 396 | $scope.show_start_dec = function(show) { 397 | show.dec(); 398 | if(inc_timeout) 399 | $timeout.cancel(inc_timeout); 400 | inc_timeout = $timeout(function(){ 401 | if(inc_interval) 402 | $interval.cancel(inc_interval); 403 | inc_interval = $interval(function() { 404 | show.dec(); 405 | }, 100); 406 | }, 250); 407 | } 408 | 409 | $scope.openLogin = function() { 410 | $modal.open({ 411 | templateUrl: 'loginModal.html', 412 | controller: 'LoginModal' 413 | }); 414 | }; 415 | 416 | $scope.user_logout = function() { 417 | ShowQuery.logout(). 418 | success(function(data) { 419 | window.location.reload(); 420 | }); 421 | }; 422 | }]); 423 | 424 | app.controller('LoginModal', [ 425 | '$scope', 'ShowQuery', '$uibModalInstance', 426 | function($scope, ShowQuery, $modalInstance) 427 | { 428 | $scope.error = null; 429 | $scope.success = null; 430 | 431 | $scope.closeLogin = function(a1) { 432 | $modalInstance.dismiss('close'); 433 | }; 434 | 435 | $scope.user_login = function(user, pw, stay) { 436 | $scope.error = null; 437 | $scope.success = null; 438 | 439 | ShowQuery.login(user, pw, stay). 440 | success(function(data) { 441 | //console.log("Login: ",data); 442 | if(data.msg == "OK") { 443 | $scope.success = "Login Successful"; 444 | window.location.reload(); 445 | } else { 446 | $scope.error = data.msg; 447 | } 448 | }); 449 | return false; 450 | }; 451 | 452 | $scope.user_register = function(user, pw) { 453 | $scope.error = null; 454 | $scope.success = null; 455 | 456 | ShowQuery.register(user, pw). 457 | success(function(data) { 458 | //console.log("Register: ",data); 459 | if(data.msg == "OK") { 460 | $scope.success = "Registration Successful. You can login now"; 461 | } else { 462 | $scope.error = data.msg; 463 | } 464 | }); 465 | return false; 466 | }; 467 | }]); 468 | 469 | function find_array(array, compare) { 470 | if(!Array.isArray(array)) return false; 471 | for(var i = 0; i < array.length; i++) 472 | if(compare(array[i])) return true; 473 | return false; 474 | } 475 | function pad(n, width, z) { 476 | z = z || '0'; 477 | n = n + ''; 478 | return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; 479 | } 480 | --------------------------------------------------------------------------------