Initial suggestion support
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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+']');
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
})
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"; }
|
||||
|
||||
Reference in New Issue
Block a user