diff --git a/couchpotato/core/plugins/dashboard/main.py b/couchpotato/core/plugins/dashboard/main.py index 70c4490..9836ef9 100644 --- a/couchpotato/core/plugins/dashboard/main.py +++ b/couchpotato/core/plugins/dashboard/main.py @@ -15,38 +15,8 @@ log = CPLog(__name__) class Dashboard(Plugin): def __init__(self): - - addApiView('dashboard.suggestions', self.suggestView) addApiView('dashboard.soon', self.getSoonView) - def newSuggestions(self): - - movies = fireEvent('movie.list', status = ['active', 'done'], limit_offset = (20, 0), single = True) - movie_identifiers = [m['library']['identifier'] for m in movies[1]] - - ignored_movies = fireEvent('movie.list', status = ['ignored', 'deleted'], limit_offset = (100, 0), single = True) - ignored_identifiers = [m['library']['identifier'] for m in ignored_movies[1]] - - suggestions = fireEvent('movie.suggest', movies = movie_identifiers, ignore = ignored_identifiers, single = True) - suggest_status = fireEvent('status.get', 'suggest', single = True) - - for suggestion in suggestions: - fireEvent('movie.add', params = {'identifier': suggestion}, force_readd = False, search_after = False, status_id = suggest_status.get('id')) - - def suggestView(self): - - db = get_session() - - movies = db.query(Movie).limit(20).all() - identifiers = [m.library.identifier for m in movies] - - suggestions = fireEvent('movie.suggest', movies = identifiers, single = True) - - return { - 'result': True, - 'suggestions': suggestions - } - def getSoonView(self, limit_offset = None, random = False, late = False, **kwargs): db = get_session() diff --git a/couchpotato/core/plugins/movie/static/movie.actions.js b/couchpotato/core/plugins/movie/static/movie.actions.js index e055279..a0f7bad 100644 --- a/couchpotato/core/plugins/movie/static/movie.actions.js +++ b/couchpotato/core/plugins/movie/static/movie.actions.js @@ -1,9 +1,13 @@ var MovieAction = new Class({ + + Implements: [Options], class_name: 'action icon2', - initialize: function(movie){ + initialize: function(movie, options){ var self = this; + self.setOptions(options); + self.movie = movie; self.create(); @@ -21,6 +25,32 @@ var MovieAction = new Class({ this.el.removeClass('disable') }, + getTitle: function(){ + var self = this; + + try { + return self.movie.getTitle(); + } + catch(e){ + try { + return self.movie.original_title ? self.movie.original_title : self.movie.titles[0]; + } + catch(e){ + return 'Unknown'; + } + } + }, + + get: function(key){ + var self = this; + try { + return self.movie.get(key) + } + catch(e){ + return self.movie[key] + } + }, + createMask: function(){ var self = this; self.mask = new Element('div.mask', { @@ -62,10 +92,10 @@ MA.IMDB = new Class({ create: function(){ var self = this; - self.id = self.movie.get('identifier'); + self.id = self.movie.get('imdb') || self.movie.get('identifier'); self.el = new Element('a.imdb', { - 'title': 'Go to the IMDB page of ' + self.movie.getTitle(), + 'title': 'Go to the IMDB page of ' + self.getTitle(), 'href': 'http://www.imdb.com/title/'+self.id+'/', 'target': '_blank' }); @@ -83,7 +113,7 @@ MA.Release = new Class({ var self = this; self.el = new Element('a.releases.download', { - 'title': 'Show the releases that are available for ' + self.movie.getTitle(), + 'title': 'Show the releases that are available for ' + self.getTitle(), 'events': { 'click': self.show.bind(self) } @@ -367,7 +397,7 @@ MA.Trailer = new Class({ var self = this; self.el = new Element('a.trailer', { - 'title': 'Watch the trailer of ' + self.movie.getTitle(), + 'title': 'Watch the trailer of ' + self.getTitle(), 'events': { 'click': self.watch.bind(self) } @@ -380,12 +410,12 @@ MA.Trailer = new Class({ var data_url = 'http://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18' var url = data_url.substitute({ - 'title': encodeURI(self.movie.getTitle()), - 'year': self.movie.get('year'), + 'title': encodeURI(self.getTitle()), + 'year': self.get('year'), 'offset': offset || 1 }), size = $(self.movie).getSize(), - height = (size.x/16)*9, + height = self.options.height || (size.x/16)*9, id = 'trailer-'+randomString(); self.player_container = new Element('div[id='+id+']'); diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/plugins/movie/static/movie.css index 7d2054d..0460047 100644 --- a/couchpotato/core/plugins/movie/static/movie.css +++ b/couchpotato/core/plugins/movie/static/movie.css @@ -629,7 +629,7 @@ height: auto; } - .movies .movie .trailer_container { + .trailer_container { width: 100%; background: #000; text-align: center; @@ -639,11 +639,11 @@ position: absolute; z-index: 10; } - .movies .movie .trailer_container.hide { + .trailer_container.hide { height: 0 !important; } - .movies .movie .hide_trailer { + .hide_trailer { position: absolute; top: 0; left: 50%; @@ -655,7 +655,7 @@ transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s; z-index: 11; } - .movies .movie .hide_trailer.hide { + .hide_trailer.hide { top: -30px; } diff --git a/couchpotato/core/plugins/movie/static/search.css b/couchpotato/core/plugins/movie/static/search.css index 82fd13c..e2aa0a4 100644 --- a/couchpotato/core/plugins/movie/static/search.css +++ b/couchpotato/core/plugins/movie/static/search.css @@ -193,7 +193,7 @@ transition: all .4s cubic-bezier(0.9,0,0.1,1); } .movie_result .data.open { - left: 100%; + left: 100% !important; } .movie_result:last-child .data { border-bottom: 0; } diff --git a/couchpotato/core/plugins/movie/static/search.js b/couchpotato/core/plugins/movie/static/search.js index 8fa4fb0..5530505 100644 --- a/couchpotato/core/plugins/movie/static/search.js +++ b/couchpotato/core/plugins/movie/static/search.js @@ -185,8 +185,11 @@ Block.Search = new Class({ Block.Search.Item = new Class({ + Implements: [Options, Events], + initialize: function(info, options){ var self = this; + self.setOptions(options); self.info = info; self.alternative_titles = []; @@ -208,17 +211,13 @@ Block.Search.Item = new Class({ }) : null, self.options_el = new Element('div.options.inlay'), self.data_container = new Element('div.data', { - 'tween': { - duration: 400, - transition: 'quint:in:out' - }, 'events': { 'click': self.showOptions.bind(self) } }).adopt( new Element('div.info').adopt( self.title = new Element('h2', { - 'text': info.titles[0] + 'text': info.titles && info.titles.length > 0 ? info.titles[0] : 'Unknown' }).adopt( self.year = info.year ? new Element('span.year', { 'text': info.year @@ -228,12 +227,12 @@ Block.Search.Item = new Class({ ) ) - - info.titles.each(function(title){ - self.alternativeTitle({ - 'title': title - }); - }) + if(info.titles) + info.titles.each(function(title){ + self.alternativeTitle({ + 'title': title + }); + }) }, alternativeTitle: function(alternative){ @@ -242,6 +241,20 @@ Block.Search.Item = new Class({ self.alternative_titles.include(alternative); }, + getTitle: function(){ + var self = this; + try { + return self.info.original_title ? self.info.original_title : self.info.titles[0]; + } + catch(e){ + return 'Unknown'; + } + }, + + get: function(key){ + return this.info[key] + }, + showOptions: function(){ var self = this; @@ -279,6 +292,8 @@ Block.Search.Item = new Class({ }) ); self.mask.fade('out'); + + self.fireEvent('added'); }, 'onFailure': function(){ self.options_el.empty(); diff --git a/couchpotato/core/plugins/suggestion/main.py b/couchpotato/core/plugins/suggestion/main.py index a22b6ee..1dfc5cf 100644 --- a/couchpotato/core/plugins/suggestion/main.py +++ b/couchpotato/core/plugins/suggestion/main.py @@ -1,20 +1,91 @@ +from couchpotato import get_session from couchpotato.api import addApiView from couchpotato.core.event import fireEvent +from couchpotato.core.helpers.encoding import ss +from couchpotato.core.helpers.variable import splitString, md5 from couchpotato.core.plugins.base import Plugin +from couchpotato.core.settings.model import Movie +from couchpotato.environment import Env +from sqlalchemy.sql.expression import or_ class Suggestion(Plugin): def __init__(self): - addApiView('suggestion.view', self.getView) + addApiView('suggestion.view', self.suggestView) + addApiView('suggestion.ignore', self.ignoreView) - def getView(self, limit_offset = None, **kwargs): + def suggestView(self, **kwargs): - total_movies, movies = fireEvent('movie.list', status = 'suggest', limit_offset = limit_offset, single = True) + movies = splitString(kwargs.get('movies', '')) + ignored = splitString(kwargs.get('ignored', '')) + limit = kwargs.get('limit', 6) + + if not movies or len(movies) == 0: + db = get_session() + active_movies = db.query(Movie) \ + .filter(or_(*[Movie.status.has(identifier = s) for s in ['active', 'done']])).all() + movies = [x.library.identifier for x in active_movies] + + if not ignored or len(ignored) == 0: + ignored = splitString(Env.prop('suggest_ignore', default = '')) + + cached_suggestion = self.getCache('suggestion_cached') + if cached_suggestion: + suggestions = cached_suggestion + else: + suggestions = fireEvent('movie.suggest', movies = movies, ignore = ignored, single = True) + self.setCache(md5(ss('suggestion_cached')), suggestions, timeout = 6048000) # Cache for 10 weeks return { 'success': True, - 'empty': len(movies) == 0, - 'total': total_movies, - 'movies': movies, + 'count': len(suggestions), + 'suggestions': suggestions[:limit] + } + + def ignoreView(self, imdb = None, limit = 6, remove_only = False, **kwargs): + + ignored = splitString(Env.prop('suggest_ignore', default = '')) + + if imdb: + if not remove_only: + ignored.append(imdb) + Env.prop('suggest_ignore', ','.join(set(ignored))) + + new_suggestions = self.updateSuggestionCache(ignore_imdb = imdb, limit = limit, ignored = ignored) + + return { + 'result': True, + 'ignore_count': len(ignored), + 'suggestions': new_suggestions[limit - 1:limit] } + + def updateSuggestionCache(self, ignore_imdb = None, limit = 6, ignored = None): + + # Combine with previous suggestion_cache + cached_suggestion = self.getCache('suggestion_cached') + new_suggestions = [] + + if ignore_imdb: + for cs in cached_suggestion: + if cs.get('imdb') != ignore_imdb: + new_suggestions.append(cs) + + # Get new results and add them + if len(new_suggestions) - 1 < limit: + + db = get_session() + active_movies = db.query(Movie).filter(Movie.status.has(identifier = 'active')).all() + movies = [x.library.identifier for x in active_movies] + + #if ignored: + # ignored.extend([x.get('imdb') for x in new_suggestions]) + + suggestions = fireEvent('movie.suggest', movies = movies, ignore = list(set(ignored)), single = True) + + if suggestions: + new_suggestions.extend(suggestions) + + self.setCache(md5(ss('suggestion_cached')), new_suggestions, timeout = 6048000) + + return new_suggestions diff --git a/couchpotato/core/plugins/suggestion/static/suggest.css b/couchpotato/core/plugins/suggestion/static/suggest.css new file mode 100644 index 0000000..95e12b9 --- /dev/null +++ b/couchpotato/core/plugins/suggestion/static/suggest.css @@ -0,0 +1,84 @@ +.suggestions { +} + + .suggestions > h2 { + height: 40px; + } + +.suggestions .movie_result { + display: inline-block; + width: 33.333%; + height: 150px; +} + + @media all and (max-width: 960px) { + .suggestions .movie_result { + width: 50%; + } + } + + @media all and (max-width: 600px) { + .suggestions .movie_result { + width: 100%; + } + } + + .suggestions .movie_result .data { + left: 100px; + background: #4e5969; + border: none; + } + + .suggestions .movie_result .data .info { + top: 15px; + left: 15px; + right: 15px; + } + + .suggestions .movie_result .data .info h2 { + white-space: normal; + max-height: 120px; + font-size: 18px; + line-height: 18px; + } + + .suggestions .movie_result .data .info .year { + position: static; + display: block; + margin: 5px 0 0; + padding: 0; + opacity: .6; + } + + .suggestions .movie_result .data { + cursor: default; + } + + .suggestions .movie_result .options { + left: 100px; + } + + .suggestions .movie_result .thumbnail { + width: 100px; + } + + .suggestions .movie_result .actions { + position: absolute; + bottom: 10px; + right: 10px; + display: none; + width: 120px; + } + .suggestions .movie_result:hover .actions { + display: block; + } + .suggestions .movie_result .data.open .actions { + display: none; + } + + .suggestions .movie_result .actions a { + margin-left: 10px; + vertical-align: middle; + } + + \ No newline at end of file diff --git a/couchpotato/core/plugins/suggestion/static/suggest.js b/couchpotato/core/plugins/suggestion/static/suggest.js new file mode 100644 index 0000000..5be7d13 --- /dev/null +++ b/couchpotato/core/plugins/suggestion/static/suggest.js @@ -0,0 +1,102 @@ +var SuggestList = new Class({ + + Implements: [Options, Events], + + initialize: function(options){ + var self = this; + self.setOptions(options); + + self.create(); + }, + + create: function(){ + var self = this; + + self.el = new Element('div.suggestions', { + 'events': { + 'click:relay(a.delete)': function(e, el){ + (e).stop(); + + $(el).getParent('.movie_result').destroy(); + + Api.request('suggestion.ignore', { + 'data': { + 'imdb': el.get('data-ignore') + }, + 'onComplete': self.fill.bind(self) + }); + + } + } + }).grab( + new Element('h2', { + 'text': 'You might like these' + }) + ); + + self.api_request = Api.request('suggestion.view', { + 'onComplete': self.fill.bind(self) + }); + + }, + + fill: function(json){ + + var self = this; + + Object.each(json.suggestions, function(movie){ + + var m = new Block.Search.Item(movie, { + 'onAdded': function(){ + self.afterAdded(m, movie) + } + }); + m.data_container.grab( + new Element('div.actions').adopt( + new Element('a.add.icon2', { + 'title': 'Add movie with your default quality', + 'data-add': movie.imdb, + 'events': { + 'click': m.showOptions.bind(m) + } + }), + $(new MA.IMDB(m)), + $(new MA.Trailer(m, { + 'height': 150 + })), + new Element('a.delete.icon2', { + 'title': 'Don\'t suggest this movie again', + 'data-ignore': movie.imdb + }) + ) + ); + m.data_container.removeEvents('click'); + $(m).inject(self.el); + + }); + + }, + + afterAdded: function(m, movie){ + var self = this; + + setTimeout(function(){ + $(m).destroy(); + + Api.request('suggestion.ignore', { + 'data': { + 'imdb': movie.imdb, + 'remove_only': true + }, + 'onComplete': self.fill.bind(self) + }); + + }, 3000); + + }, + + toElement: function(){ + return this.el; + } + +}) diff --git a/couchpotato/core/providers/movie/couchpotatoapi/main.py b/couchpotato/core/providers/movie/couchpotatoapi/main.py index 2905bea..9c1266c 100644 --- a/couchpotato/core/providers/movie/couchpotatoapi/main.py +++ b/couchpotato/core/providers/movie/couchpotatoapi/main.py @@ -1,9 +1,7 @@ -from couchpotato import get_session from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.helpers.encoding import tryUrlencode from couchpotato.core.logger import CPLog from couchpotato.core.providers.movie.base import MovieProvider -from couchpotato.core.settings.model import Movie from couchpotato.environment import Env import time @@ -17,7 +15,7 @@ class CouchPotatoApi(MovieProvider): 'info': 'https://api.couchpota.to/info/%s/', 'is_movie': 'https://api.couchpota.to/ismovie/%s/', 'eta': 'https://api.couchpota.to/eta/%s/', - 'suggest': 'https://api.couchpota.to/suggest/', + 'suggest': 'http://api.couchpota.to:3010/suggest/', 'updater': 'https://api.couchpota.to/updater/?%s', 'messages': 'https://api.couchpota.to/messages/?%s', } @@ -28,7 +26,7 @@ class CouchPotatoApi(MovieProvider): addEvent('movie.info', self.getInfo, priority = 1) addEvent('movie.search', self.search, priority = 1) addEvent('movie.release_date', self.getReleaseDate) - addEvent('movie.suggest', self.suggest) + addEvent('movie.suggest', self.getSuggestions) addEvent('movie.is_movie', self.isMovie) addEvent('cp.source_url', self.getSourceUrl) @@ -82,33 +80,15 @@ class CouchPotatoApi(MovieProvider): return dates - def suggest(self, movies = [], ignore = []): + def getSuggestions(self, movies = [], ignore = []): suggestions = self.getJsonData(self.urls['suggest'], params = { 'movies': ','.join(movies), 'ignore': ','.join(ignore), }) - log.info('Found Suggestions for %s', (suggestions)) + log.info('Found suggestions for %s movies, %s ignored', (len(movies), len(ignore))) return suggestions - def suggestView(self, **kwargs): - - movies = kwargs.get('movies') - ignore = kwargs.get('ignore', []) - - if not movies: - db = get_session() - active_movies = db.query(Movie).filter(Movie.status.has(identifier = 'active')).all() - movies = [x.library.identifier for x in active_movies] - - suggestions = self.suggest(movies, ignore) - - return { - 'success': True, - 'count': len(suggestions), - 'suggestions': suggestions - } - def getRequestHeaders(self): return { 'X-CP-Version': fireEvent('app.version', single = True), diff --git a/couchpotato/static/scripts/page/home.js b/couchpotato/static/scripts/page/home.js index 04fc7a8..01344ad 100644 --- a/couchpotato/static/scripts/page/home.js +++ b/couchpotato/static/scripts/page/home.js @@ -101,6 +101,9 @@ Page.Home = new Class({ }); }); + // Suggest + self.suggestion_list = new SuggestList(); + // Still not available self.late_list = new MovieList({ 'navigation': false, @@ -121,25 +124,10 @@ Page.Home = new Class({ self.el.adopt( $(self.available_list), $(self.soon_list), + $(self.suggestion_list), $(self.late_list) ); - // Suggest - // self.suggestion_list = new MovieList({ - // 'navigation': false, - // 'identifier': 'suggestions', - // 'limit': 6, - // 'load_more': false, - // 'view': 'thumbs', - // 'api_call': 'suggestion.suggest' - // }); - // self.el.adopt( - // new Element('h2', { - // 'text': 'You might like' - // }), - // $(self.suggestion_list) - // ); - // Recent // Snatched // Renamed diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css index 9798313..ae461c4 100644 --- a/couchpotato/static/style/main.css +++ b/couchpotato/static/style/main.css @@ -168,6 +168,7 @@ body > .spinner, .mask{ color: #FFF; } +.icon2.add:before { content: "\e05a"; color: #c2fac5; } .icon2.cog:before { content: "\e109"; } .icon2.eye-open:before { content: "\e09d"; } .icon2.search:before { content: "\e03e"; }