Browse Source

Initial suggestion support

pull/1867/merge
Ruud 12 years ago
parent
commit
fd1e655075
  1. 30
      couchpotato/core/plugins/dashboard/main.py
  2. 46
      couchpotato/core/plugins/movie/static/movie.actions.js
  3. 8
      couchpotato/core/plugins/movie/static/movie.css
  4. 2
      couchpotato/core/plugins/movie/static/search.css
  5. 27
      couchpotato/core/plugins/movie/static/search.js
  6. 83
      couchpotato/core/plugins/suggestion/main.py
  7. 84
      couchpotato/core/plugins/suggestion/static/suggest.css
  8. 102
      couchpotato/core/plugins/suggestion/static/suggest.js
  9. 28
      couchpotato/core/providers/movie/couchpotatoapi/main.py
  10. 20
      couchpotato/static/scripts/page/home.js
  11. 1
      couchpotato/static/style/main.css

30
couchpotato/core/plugins/dashboard/main.py

@ -15,38 +15,8 @@ log = CPLog(__name__)
class Dashboard(Plugin): class Dashboard(Plugin):
def __init__(self): def __init__(self):
addApiView('dashboard.suggestions', self.suggestView)
addApiView('dashboard.soon', self.getSoonView) 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): def getSoonView(self, limit_offset = None, random = False, late = False, **kwargs):
db = get_session() db = get_session()

46
couchpotato/core/plugins/movie/static/movie.actions.js

@ -1,9 +1,13 @@
var MovieAction = new Class({ var MovieAction = new Class({
Implements: [Options],
class_name: 'action icon2', class_name: 'action icon2',
initialize: function(movie){ initialize: function(movie, options){
var self = this; var self = this;
self.setOptions(options);
self.movie = movie; self.movie = movie;
self.create(); self.create();
@ -21,6 +25,32 @@ var MovieAction = new Class({
this.el.removeClass('disable') 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(){ createMask: function(){
var self = this; var self = this;
self.mask = new Element('div.mask', { self.mask = new Element('div.mask', {
@ -62,10 +92,10 @@ MA.IMDB = new Class({
create: function(){ create: function(){
var self = this; 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', { 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+'/', 'href': 'http://www.imdb.com/title/'+self.id+'/',
'target': '_blank' 'target': '_blank'
}); });
@ -83,7 +113,7 @@ MA.Release = new Class({
var self = this; var self = this;
self.el = new Element('a.releases.download', { 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': { 'events': {
'click': self.show.bind(self) 'click': self.show.bind(self)
} }
@ -367,7 +397,7 @@ MA.Trailer = new Class({
var self = this; var self = this;
self.el = new Element('a.trailer', { self.el = new Element('a.trailer', {
'title': 'Watch the trailer of ' + self.movie.getTitle(), 'title': 'Watch the trailer of ' + self.getTitle(),
'events': { 'events': {
'click': self.watch.bind(self) '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 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({ var url = data_url.substitute({
'title': encodeURI(self.movie.getTitle()), 'title': encodeURI(self.getTitle()),
'year': self.movie.get('year'), 'year': self.get('year'),
'offset': offset || 1 'offset': offset || 1
}), }),
size = $(self.movie).getSize(), size = $(self.movie).getSize(),
height = (size.x/16)*9, height = self.options.height || (size.x/16)*9,
id = 'trailer-'+randomString(); id = 'trailer-'+randomString();
self.player_container = new Element('div[id='+id+']'); self.player_container = new Element('div[id='+id+']');

8
couchpotato/core/plugins/movie/static/movie.css

@ -629,7 +629,7 @@
height: auto; height: auto;
} }
.movies .movie .trailer_container { .trailer_container {
width: 100%; width: 100%;
background: #000; background: #000;
text-align: center; text-align: center;
@ -639,11 +639,11 @@
position: absolute; position: absolute;
z-index: 10; z-index: 10;
} }
.movies .movie .trailer_container.hide { .trailer_container.hide {
height: 0 !important; height: 0 !important;
} }
.movies .movie .hide_trailer { .hide_trailer {
position: absolute; position: absolute;
top: 0; top: 0;
left: 50%; left: 50%;
@ -655,7 +655,7 @@
transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s; transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s;
z-index: 11; z-index: 11;
} }
.movies .movie .hide_trailer.hide { .hide_trailer.hide {
top: -30px; top: -30px;
} }

2
couchpotato/core/plugins/movie/static/search.css

@ -193,7 +193,7 @@
transition: all .4s cubic-bezier(0.9,0,0.1,1); transition: all .4s cubic-bezier(0.9,0,0.1,1);
} }
.movie_result .data.open { .movie_result .data.open {
left: 100%; left: 100% !important;
} }
.movie_result:last-child .data { border-bottom: 0; } .movie_result:last-child .data { border-bottom: 0; }

27
couchpotato/core/plugins/movie/static/search.js

@ -185,8 +185,11 @@ Block.Search = new Class({
Block.Search.Item = new Class({ Block.Search.Item = new Class({
Implements: [Options, Events],
initialize: function(info, options){ initialize: function(info, options){
var self = this; var self = this;
self.setOptions(options);
self.info = info; self.info = info;
self.alternative_titles = []; self.alternative_titles = [];
@ -208,17 +211,13 @@ Block.Search.Item = new Class({
}) : null, }) : null,
self.options_el = new Element('div.options.inlay'), self.options_el = new Element('div.options.inlay'),
self.data_container = new Element('div.data', { self.data_container = new Element('div.data', {
'tween': {
duration: 400,
transition: 'quint:in:out'
},
'events': { 'events': {
'click': self.showOptions.bind(self) 'click': self.showOptions.bind(self)
} }
}).adopt( }).adopt(
new Element('div.info').adopt( new Element('div.info').adopt(
self.title = new Element('h2', { self.title = new Element('h2', {
'text': info.titles[0] 'text': info.titles && info.titles.length > 0 ? info.titles[0] : 'Unknown'
}).adopt( }).adopt(
self.year = info.year ? new Element('span.year', { self.year = info.year ? new Element('span.year', {
'text': info.year 'text': info.year
@ -228,7 +227,7 @@ Block.Search.Item = new Class({
) )
) )
if(info.titles)
info.titles.each(function(title){ info.titles.each(function(title){
self.alternativeTitle({ self.alternativeTitle({
'title': title 'title': title
@ -242,6 +241,20 @@ Block.Search.Item = new Class({
self.alternative_titles.include(alternative); 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(){ showOptions: function(){
var self = this; var self = this;
@ -279,6 +292,8 @@ Block.Search.Item = new Class({
}) })
); );
self.mask.fade('out'); self.mask.fade('out');
self.fireEvent('added');
}, },
'onFailure': function(){ 'onFailure': function(){
self.options_el.empty(); self.options_el.empty();

83
couchpotato/core/plugins/suggestion/main.py

@ -1,20 +1,91 @@
from couchpotato import get_session
from couchpotato.api import addApiView from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent 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.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): class Suggestion(Plugin):
def __init__(self): 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 { return {
'success': True, 'success': True,
'empty': len(movies) == 0, 'count': len(suggestions),
'total': total_movies, 'suggestions': suggestions[:limit]
'movies': movies, }
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

84
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;
}

102
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;
}
})

28
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.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import tryUrlencode from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.providers.movie.base import MovieProvider from couchpotato.core.providers.movie.base import MovieProvider
from couchpotato.core.settings.model import Movie
from couchpotato.environment import Env from couchpotato.environment import Env
import time import time
@ -17,7 +15,7 @@ class CouchPotatoApi(MovieProvider):
'info': 'https://api.couchpota.to/info/%s/', 'info': 'https://api.couchpota.to/info/%s/',
'is_movie': 'https://api.couchpota.to/ismovie/%s/', 'is_movie': 'https://api.couchpota.to/ismovie/%s/',
'eta': 'https://api.couchpota.to/eta/%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', 'updater': 'https://api.couchpota.to/updater/?%s',
'messages': 'https://api.couchpota.to/messages/?%s', 'messages': 'https://api.couchpota.to/messages/?%s',
} }
@ -28,7 +26,7 @@ class CouchPotatoApi(MovieProvider):
addEvent('movie.info', self.getInfo, priority = 1) addEvent('movie.info', self.getInfo, priority = 1)
addEvent('movie.search', self.search, priority = 1) addEvent('movie.search', self.search, priority = 1)
addEvent('movie.release_date', self.getReleaseDate) addEvent('movie.release_date', self.getReleaseDate)
addEvent('movie.suggest', self.suggest) addEvent('movie.suggest', self.getSuggestions)
addEvent('movie.is_movie', self.isMovie) addEvent('movie.is_movie', self.isMovie)
addEvent('cp.source_url', self.getSourceUrl) addEvent('cp.source_url', self.getSourceUrl)
@ -82,33 +80,15 @@ class CouchPotatoApi(MovieProvider):
return dates return dates
def suggest(self, movies = [], ignore = []): def getSuggestions(self, movies = [], ignore = []):
suggestions = self.getJsonData(self.urls['suggest'], params = { suggestions = self.getJsonData(self.urls['suggest'], params = {
'movies': ','.join(movies), 'movies': ','.join(movies),
'ignore': ','.join(ignore), '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 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): def getRequestHeaders(self):
return { return {
'X-CP-Version': fireEvent('app.version', single = True), 'X-CP-Version': fireEvent('app.version', single = True),

20
couchpotato/static/scripts/page/home.js

@ -101,6 +101,9 @@ Page.Home = new Class({
}); });
}); });
// Suggest
self.suggestion_list = new SuggestList();
// Still not available // Still not available
self.late_list = new MovieList({ self.late_list = new MovieList({
'navigation': false, 'navigation': false,
@ -121,25 +124,10 @@ Page.Home = new Class({
self.el.adopt( self.el.adopt(
$(self.available_list), $(self.available_list),
$(self.soon_list), $(self.soon_list),
$(self.suggestion_list),
$(self.late_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 // Recent
// Snatched // Snatched
// Renamed // Renamed

1
couchpotato/static/style/main.css

@ -168,6 +168,7 @@ body > .spinner, .mask{
color: #FFF; color: #FFF;
} }
.icon2.add:before { content: "\e05a"; color: #c2fac5; }
.icon2.cog:before { content: "\e109"; } .icon2.cog:before { content: "\e109"; }
.icon2.eye-open:before { content: "\e09d"; } .icon2.eye-open:before { content: "\e09d"; }
.icon2.search:before { content: "\e03e"; } .icon2.search:before { content: "\e03e"; }

Loading…
Cancel
Save