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. 37
      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):
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()

46
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+']');

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

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

37
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();

83
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

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.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),

20
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

1
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"; }

Loading…
Cancel
Save