From 7e1bdc99eb6d3cac4adea53083bd1c2aea00161c Mon Sep 17 00:00:00 2001 From: Aaron Florey Date: Sun, 23 Jun 2013 00:11:01 +1000 Subject: [PATCH 01/22] Add Yify Torrent Provider --- .../core/providers/torrent/yify/__init__.py | 33 +++++++++++++ .../core/providers/torrent/yify/main.py | 46 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 couchpotato/core/providers/torrent/yify/__init__.py create mode 100644 couchpotato/core/providers/torrent/yify/main.py diff --git a/couchpotato/core/providers/torrent/yify/__init__.py b/couchpotato/core/providers/torrent/yify/__init__.py new file mode 100644 index 00000000..d5080ae0 --- /dev/null +++ b/couchpotato/core/providers/torrent/yify/__init__.py @@ -0,0 +1,33 @@ +from main import Yify + +def start(): + return Yify() + +config = [{ + 'name': 'yify', + 'groups': [ + { + 'tab': 'searcher', + 'subtab': 'providers', + 'list': 'torrent_providers', + 'name': 'Yify', + 'description': 'Small HD movies, encoded by Yify.', + 'wizard': False, + 'options': [ + { + 'name': 'enabled', + 'type': 'enabler', + 'default': True + }, + { + 'name': 'extra_score', + 'advanced': True, + 'label': 'Extra Score', + 'type': 'int', + 'default': 0, + 'description': 'Starting score for each release found via this provider.', + } + ], + } + ] +}] diff --git a/couchpotato/core/providers/torrent/yify/main.py b/couchpotato/core/providers/torrent/yify/main.py new file mode 100644 index 00000000..5c741a5b --- /dev/null +++ b/couchpotato/core/providers/torrent/yify/main.py @@ -0,0 +1,46 @@ +from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode +from couchpotato.core.helpers.variable import tryInt, cleanHost +from couchpotato.core.logger import CPLog +from couchpotato.core.providers.torrent.base import TorrentProvider +from couchpotato.environment import Env +import re +import time +import traceback +from pprint import pprint + +log = CPLog(__name__) + + +class Yify(TorrentProvider): + + urls = { + 'test' : 'https://yify-torrents.com/api', + 'search' : 'https://yify-torrents.com/api/list.json?keywords=%s&quality=%s', + 'detail': 'https://yify-torrents.com/api/movie.json?id=%s' + } + + http_time_between_calls = 1 #seconds + + def _search(self, movie, quality, results): + try: + data = self.getJsonData(self.urls['search'] % (movie['library']['title'], quality['identifier'])) + except: + log.error('Search on Yify (%s) failed (could not decode JSON)', params) + return + + if data: + try: + for result in data: + results.append({ + 'id': result['MovieID'], + 'name': result['MovieTitle'], + 'url': result['TorrentUrl'], + 'detail_url': self.urls['detail'] % result['MovieID'], + 'size': self.parseSize(result['Size']), + 'seeders': tryInt(result['TorrentSeeds']), + 'leechers': tryInt(result['TorrentPeers']) + }) + + except: + log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc())) + From fd1e65507598d11b1abe923f9981b9b54d20e62d Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 23 Jun 2013 19:07:03 +0200 Subject: [PATCH 02/22] Initial suggestion support --- couchpotato/core/plugins/dashboard/main.py | 30 ------ .../plugins/movie/static/movie.actions.js | 46 ++++++-- .../core/plugins/movie/static/movie.css | 8 +- .../core/plugins/movie/static/search.css | 2 +- .../core/plugins/movie/static/search.js | 37 +++++-- couchpotato/core/plugins/suggestion/main.py | 83 ++++++++++++-- .../plugins/suggestion/static/suggest.css | 84 +++++++++++++++ .../core/plugins/suggestion/static/suggest.js | 102 ++++++++++++++++++ .../providers/movie/couchpotatoapi/main.py | 28 +---- couchpotato/static/scripts/page/home.js | 20 +--- couchpotato/static/style/main.css | 1 + 11 files changed, 341 insertions(+), 100 deletions(-) create mode 100644 couchpotato/core/plugins/suggestion/static/suggest.css create mode 100644 couchpotato/core/plugins/suggestion/static/suggest.js diff --git a/couchpotato/core/plugins/dashboard/main.py b/couchpotato/core/plugins/dashboard/main.py index 70c4490d..9836ef9d 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 e0552792..a0f7bad5 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 7d2054d4..04600475 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 82fd13cc..e2aa0a47 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 8fa4fb0c..5530505f 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 a22b6ee6..1dfc5cff 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 00000000..95e12b9e --- /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 00000000..5be7d139 --- /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 2905bea0..9c1266ce 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 04fc7a8e..01344ad8 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 97983130..ae461c43 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"; } From ea3d719b3243013250da5808f1c02d755c1ac594 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 23 Jun 2013 19:09:07 +0200 Subject: [PATCH 03/22] Suggest on wrong dev port --- couchpotato/core/providers/movie/couchpotatoapi/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/providers/movie/couchpotatoapi/main.py b/couchpotato/core/providers/movie/couchpotatoapi/main.py index 9c1266ce..1c12c22b 100644 --- a/couchpotato/core/providers/movie/couchpotatoapi/main.py +++ b/couchpotato/core/providers/movie/couchpotatoapi/main.py @@ -15,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': 'http://api.couchpota.to:3010/suggest/', + 'suggest': 'https://api.couchpota.to/suggest/', 'updater': 'https://api.couchpota.to/updater/?%s', 'messages': 'https://api.couchpota.to/messages/?%s', } From 52ea0215f0b1dc73cbfa216928e237e1c188712c Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 23 Jun 2013 19:14:11 +0200 Subject: [PATCH 04/22] Use done for suggestion also --- couchpotato/core/plugins/suggestion/main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/suggestion/main.py b/couchpotato/core/plugins/suggestion/main.py index 1dfc5cff..e913bb6c 100644 --- a/couchpotato/core/plugins/suggestion/main.py +++ b/couchpotato/core/plugins/suggestion/main.py @@ -75,11 +75,12 @@ class Suggestion(Plugin): if len(new_suggestions) - 1 < limit: db = get_session() - active_movies = db.query(Movie).filter(Movie.status.has(identifier = 'active')).all() + 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 ignored: - # ignored.extend([x.get('imdb') for x in new_suggestions]) + if ignored: + ignored.extend([x.get('imdb') for x in new_suggestions]) suggestions = fireEvent('movie.suggest', movies = movies, ignore = list(set(ignored)), single = True) From 74c984dec3f271c6abd17ed8dfbdc4a51829cb73 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 23 Jun 2013 20:44:11 +0200 Subject: [PATCH 05/22] Send CP headers to suggestion call. fix #1872 --- couchpotato/core/providers/movie/couchpotatoapi/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/providers/movie/couchpotatoapi/main.py b/couchpotato/core/providers/movie/couchpotatoapi/main.py index 1c12c22b..9f76381a 100644 --- a/couchpotato/core/providers/movie/couchpotatoapi/main.py +++ b/couchpotato/core/providers/movie/couchpotatoapi/main.py @@ -84,7 +84,7 @@ class CouchPotatoApi(MovieProvider): suggestions = self.getJsonData(self.urls['suggest'], params = { 'movies': ','.join(movies), 'ignore': ','.join(ignore), - }) + }, headers = self.getRequestHeaders()) log.info('Found suggestions for %s movies, %s ignored', (len(movies), len(ignore))) return suggestions From 374f8ba1de8bf6351da286813a5fed50ab623cbf Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 23 Jun 2013 23:28:13 +0200 Subject: [PATCH 06/22] Allow non trailing slash API calls --- couchpotato/__init__.py | 5 +++-- couchpotato/api.py | 6 ++++-- couchpotato/core/auth.py | 11 ----------- couchpotato/runner.py | 9 ++++----- 4 files changed, 11 insertions(+), 20 deletions(-) diff --git a/couchpotato/__init__.py b/couchpotato/__init__.py index aee03823..8dc691da 100644 --- a/couchpotato/__init__.py +++ b/couchpotato/__init__.py @@ -20,7 +20,8 @@ template_loader = template.Loader(os.path.join(os.path.dirname(__file__), 'templ # Main web handler @requires_auth class WebHandler(RequestHandler): - def get(self, route): + def get(self, route, *args, **kwargs): + route = route.strip('/') if not views.get(route): page_not_found(self) return @@ -55,7 +56,7 @@ addView('docs', apiDocs) # Make non basic auth option to get api key class KeyHandler(RequestHandler): - def get(self): + def get(self, *args, **kwargs): api = None username = Env.setting('username') password = Env.setting('password') diff --git a/couchpotato/api.py b/couchpotato/api.py index 20eaa332..93d8a251 100644 --- a/couchpotato/api.py +++ b/couchpotato/api.py @@ -14,7 +14,8 @@ class NonBlockHandler(RequestHandler): stoppers = [] @asynchronous - def get(self, route): + def get(self, route, *args, **kwargs): + route = route.strip('/') start, stop = api_nonblock[route] self.stoppers.append(stop) @@ -43,7 +44,8 @@ def addNonBlockApiView(route, func_tuple, docs = None, **kwargs): # Blocking API handler class ApiHandler(RequestHandler): - def get(self, route): + def get(self, route, *args, **kwargs): + route = route.strip('/') if not api.get(route): self.write('API call doesn\'t seem to exist') return diff --git a/couchpotato/core/auth.py b/couchpotato/core/auth.py index b987f451..e58016bd 100644 --- a/couchpotato/core/auth.py +++ b/couchpotato/core/auth.py @@ -38,14 +38,3 @@ def requires_auth(handler_class): handler_class._execute = wrap_execute(handler_class._execute) return handler_class - -# @wraps(f) -# def decorated(*args, **kwargs): -# auth = getattr(request, 'authorization') -# if Env.setting('username') and Env.setting('password'): -# if (not auth or not check_auth(auth.username.decode('latin1'), md5(auth.password.decode('latin1').encode(Env.get('encoding'))))): -# return authenticate() -# -# return f(*args, **kwargs) -# -# return decorated diff --git a/couchpotato/runner.py b/couchpotato/runner.py index fd05049a..0c0127fa 100644 --- a/couchpotato/runner.py +++ b/couchpotato/runner.py @@ -230,16 +230,15 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En # Request handlers application.add_handlers(".*$", [ - (r'%snonblock/(.*)/' % api_base, NonBlockHandler), + (r'%snonblock/(.*)(/?)' % api_base, NonBlockHandler), # API handlers - (r'%s(.*)/' % api_base, ApiHandler), # Main API handler - (r'%sgetkey/' % web_base, KeyHandler), # Get API key + (r'%s(.*)(/?)' % api_base, ApiHandler), # Main API handler + (r'%sgetkey(/?)' % web_base, KeyHandler), # Get API key (r'%s' % api_base, RedirectHandler, {"url": web_base + 'docs/'}), # API docs # Catch all webhandlers - (r'%s(.*)/' % web_base, WebHandler), - (r'%s(.*)' % web_base, WebHandler), + (r'%s(.*)(/?)' % web_base, WebHandler), (r'(.*)', WebHandler), ]) From 9eea42b121ea84c71ac24bf2d922176702b027dd Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 24 Jun 2013 00:26:00 +0200 Subject: [PATCH 07/22] Get array arguments as list. fix #1875 --- couchpotato/api.py | 4 +++ couchpotato/core/helpers/request.py | 52 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 couchpotato/core/helpers/request.py diff --git a/couchpotato/api.py b/couchpotato/api.py index 93d8a251..029ebce2 100644 --- a/couchpotato/api.py +++ b/couchpotato/api.py @@ -1,3 +1,4 @@ +from couchpotato.core.helpers.request import getParams from tornado.web import RequestHandler, asynchronous import json import urllib @@ -54,6 +55,9 @@ class ApiHandler(RequestHandler): for x in self.request.arguments: kwargs[x] = urllib.unquote(self.get_argument(x)) + # Split array arguments + kwargs = getParams(kwargs) + # Remove t random string try: del kwargs['t'] except: pass diff --git a/couchpotato/core/helpers/request.py b/couchpotato/core/helpers/request.py new file mode 100644 index 00000000..0ac84ef8 --- /dev/null +++ b/couchpotato/core/helpers/request.py @@ -0,0 +1,52 @@ +from couchpotato.core.helpers.encoding import toUnicode +from couchpotato.core.helpers.variable import natcmp +from urllib import unquote +import re + + +def getParams(params): + + reg = re.compile('^[a-z0-9_\.]+$') + + current = temp = {} + for param, value in sorted(params.iteritems()): + + nest = re.split("([\[\]]+)", param) + if len(nest) > 1: + nested = [] + for key in nest: + if reg.match(key): + nested.append(key) + + current = temp + + for item in nested: + if item is nested[-1]: + current[item] = toUnicode(unquote(value)) + else: + try: + current[item] + except: + current[item] = {} + + current = current[item] + else: + temp[param] = toUnicode(unquote(value)) + + return dictToList(temp) + +def dictToList(params): + + if type(params) is dict: + new = {} + for x, value in params.iteritems(): + try: + new_value = [dictToList(value[k]) for k in sorted(value.iterkeys(), cmp = natcmp)] + except: + new_value = value + + new[x] = new_value + else: + new = params + + return new From 5328f7fe6932e586d0d593335f46d2031542b9fa Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 24 Jun 2013 21:21:49 +0200 Subject: [PATCH 08/22] Allow unknown keywords for all api calls. fix #1881 --- couchpotato/core/_base/_core/main.py | 8 ++++---- couchpotato/core/_base/updater/main.py | 4 ++-- couchpotato/core/notifications/base.py | 2 +- couchpotato/core/notifications/core/main.py | 2 +- couchpotato/core/notifications/nmj/main.py | 2 +- couchpotato/core/notifications/plex/main.py | 2 +- couchpotato/core/notifications/synoindex/main.py | 2 +- couchpotato/core/plugins/file/main.py | 4 ++-- couchpotato/core/plugins/log/main.py | 2 +- couchpotato/core/plugins/manage/main.py | 2 +- couchpotato/core/plugins/movie/main.py | 6 +++--- couchpotato/core/plugins/profile/main.py | 4 ++-- couchpotato/core/plugins/quality/main.py | 2 +- couchpotato/core/plugins/searcher/main.py | 4 ++-- couchpotato/core/plugins/status/main.py | 2 +- couchpotato/core/plugins/userscript/main.py | 4 ++-- couchpotato/core/settings/__init__.py | 2 +- 17 files changed, 27 insertions(+), 27 deletions(-) diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py index 78ffdadf..4ad37d6c 100644 --- a/couchpotato/core/_base/_core/main.py +++ b/couchpotato/core/_base/_core/main.py @@ -67,12 +67,12 @@ class Core(Plugin): return True - def available(self): + def available(self, **kwargs): return { 'success': True } - def shutdown(self): + def shutdown(self, **kwargs): if self.shutdown_started: return False @@ -82,7 +82,7 @@ class Core(Plugin): return 'shutdown' - def restart(self): + def restart(self, **kwargs): if self.shutdown_started: return False @@ -169,7 +169,7 @@ class Core(Plugin): return '%s - %s-%s - v2' % (platf, ver.get('version')['type'], ver.get('version')['hash']) - def versionView(self): + def versionView(self, **kwargs): return { 'version': self.version() } diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py index ca3c87f3..5b61aebe 100644 --- a/couchpotato/core/_base/updater/main.py +++ b/couchpotato/core/_base/updater/main.py @@ -94,13 +94,13 @@ class Updater(Plugin): def info(self, **kwargs): return self.updater.info() - def checkView(self): + def checkView(self, **kwargs): return { 'update_available': self.check(force = True), 'info': self.updater.info() } - def doUpdateView(self): + def doUpdateView(self, **kwargs): self.check() if not self.updater.update_version: diff --git a/couchpotato/core/notifications/base.py b/couchpotato/core/notifications/base.py index 4d8e64f9..7418e1a4 100644 --- a/couchpotato/core/notifications/base.py +++ b/couchpotato/core/notifications/base.py @@ -49,7 +49,7 @@ class Notification(Provider): def notify(self, message = '', data = {}, listener = None): pass - def test(self): + def test(self, **kwargs): test_type = self.testNotifyName() diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index 2bc5e18b..b6c07f58 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -62,7 +62,7 @@ class CoreNotifier(Notification): db.commit() - def markAsRead(self, ids = None): + def markAsRead(self, ids = None, **kwargs): ids = splitString(ids) if ids else None diff --git a/couchpotato/core/notifications/nmj/main.py b/couchpotato/core/notifications/nmj/main.py index e7819383..695f53be 100644 --- a/couchpotato/core/notifications/nmj/main.py +++ b/couchpotato/core/notifications/nmj/main.py @@ -113,7 +113,7 @@ class NMJ(Notification): 'success': False } - def test(self): + def test(self, **kwargs): return { 'success': self.addToLibrary() } diff --git a/couchpotato/core/notifications/plex/main.py b/couchpotato/core/notifications/plex/main.py index df8df3f4..86da9cd5 100644 --- a/couchpotato/core/notifications/plex/main.py +++ b/couchpotato/core/notifications/plex/main.py @@ -72,7 +72,7 @@ class Plex(Notification): log.info('Plex notification to %s successful.', host) return True - def test(self): + def test(self, **kwargs): test_type = self.testNotifyName() diff --git a/couchpotato/core/notifications/synoindex/main.py b/couchpotato/core/notifications/synoindex/main.py index c3653cab..315520ef 100644 --- a/couchpotato/core/notifications/synoindex/main.py +++ b/couchpotato/core/notifications/synoindex/main.py @@ -31,7 +31,7 @@ class Synoindex(Notification): return True - def test(self): + def test(self, **kwargs): return { 'success': os.path.isfile(self.index_path) } diff --git a/couchpotato/core/plugins/file/main.py b/couchpotato/core/plugins/file/main.py index 5ba99f95..cdd67f5c 100644 --- a/couchpotato/core/plugins/file/main.py +++ b/couchpotato/core/plugins/file/main.py @@ -79,7 +79,7 @@ class FileManager(Plugin): except: log.error('Failed removing unused file: %s', traceback.format_exc()) - def showCacheFile(self, route): + def showCacheFile(self, route, **kwargs): Env.get('app').add_handlers(".*$", [('%s%s' % (Env.get('api_base'), route), StaticFileHandler, {'path': Env.get('cache_dir')})]) @@ -150,7 +150,7 @@ class FileManager(Plugin): return types - def getTypesView(self): + def getTypesView(self, **kwargs): return { 'types': self.getTypes() diff --git a/couchpotato/core/plugins/log/main.py b/couchpotato/core/plugins/log/main.py index e9fc0e82..ee88b8d4 100644 --- a/couchpotato/core/plugins/log/main.py +++ b/couchpotato/core/plugins/log/main.py @@ -114,7 +114,7 @@ class Logging(Plugin): 'log': '[0m\n'.join(log_lines), } - def clear(self): + def clear(self, **kwargs): for x in range(0, 50): path = '%s%s' % (Env.get('log_path'), '.%s' % x if x > 0 else '') diff --git a/couchpotato/core/plugins/manage/main.py b/couchpotato/core/plugins/manage/main.py index 056c1f7f..454e765c 100644 --- a/couchpotato/core/plugins/manage/main.py +++ b/couchpotato/core/plugins/manage/main.py @@ -47,7 +47,7 @@ class Manage(Plugin): if not Env.get('dev'): addEvent('app.load', self.updateLibraryQuick) - def getProgress(self): + def getProgress(self, **kwargs): return { 'progress': self.in_progress } diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index 1c829e33..0cc98fd3 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -124,7 +124,7 @@ class MoviePlugin(Plugin): db.expire_all() - def getView(self, id = None): + def getView(self, id = None, **kwargs): movie = self.get(id) if id else None @@ -298,7 +298,7 @@ class MoviePlugin(Plugin): 'chars': chars, } - def refresh(self, id = ''): + def refresh(self, id = '', **kwargs): db = get_session() @@ -320,7 +320,7 @@ class MoviePlugin(Plugin): 'success': True, } - def search(self, q = ''): + def search(self, q = '', **kwargs): cache_key = u'%s/%s' % (__name__, simplifyString(q)) movies = Env.get('cache').get(cache_key) diff --git a/couchpotato/core/plugins/profile/main.py b/couchpotato/core/plugins/profile/main.py index 8309d16d..c70d7c99 100644 --- a/couchpotato/core/plugins/profile/main.py +++ b/couchpotato/core/plugins/profile/main.py @@ -45,7 +45,7 @@ class ProfilePlugin(Plugin): movie.profile_id = default_profile.get('id') db.commit() - def allView(self): + def allView(self, **kwargs): return { 'success': True, @@ -128,7 +128,7 @@ class ProfilePlugin(Plugin): 'success': True } - def delete(self, id = None): + def delete(self, id = None, **kwargs): db = get_session() diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py index 4440e16b..ead8446e 100644 --- a/couchpotato/core/plugins/quality/main.py +++ b/couchpotato/core/plugins/quality/main.py @@ -50,7 +50,7 @@ class QualityPlugin(Plugin): def preReleases(self): return self.pre_releases - def allView(self): + def allView(self, **kwargs): return { 'success': True, diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 008c9218..3d85a565 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -57,7 +57,7 @@ class Searcher(Plugin): def setCrons(self): fireEvent('schedule.cron', 'searcher.all', self.allMovies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) - def allMoviesView(self): + def allMoviesView(self, **kwargs): in_progress = self.in_progress if not in_progress: @@ -70,7 +70,7 @@ class Searcher(Plugin): 'success': not in_progress } - def getProgress(self): + def getProgress(self, **kwargs): return { 'progress': self.in_progress diff --git a/couchpotato/core/plugins/status/main.py b/couchpotato/core/plugins/status/main.py index 42c2d59f..c8c8f666 100644 --- a/couchpotato/core/plugins/status/main.py +++ b/couchpotato/core/plugins/status/main.py @@ -41,7 +41,7 @@ class StatusPlugin(Plugin): }"""} }) - def list(self): + def list(self, **kwargs): return { 'success': True, diff --git a/couchpotato/core/plugins/userscript/main.py b/couchpotato/core/plugins/userscript/main.py index cf481a74..a76cf58c 100644 --- a/couchpotato/core/plugins/userscript/main.py +++ b/couchpotato/core/plugins/userscript/main.py @@ -35,14 +35,14 @@ class Userscript(Plugin): return self.renderTemplate(__file__, 'bookmark.js', **params) - def getIncludes(self): + def getIncludes(self, **kwargs): return { 'includes': fireEvent('userscript.get_includes', merge = True), 'excludes': fireEvent('userscript.get_excludes', merge = True), } - def getUserScript(self, route): + def getUserScript(self, route, **kwargs): klass = self diff --git a/couchpotato/core/settings/__init__.py b/couchpotato/core/settings/__init__.py index 05137104..cdf58aa2 100644 --- a/couchpotato/core/settings/__init__.py +++ b/couchpotato/core/settings/__init__.py @@ -168,7 +168,7 @@ class Settings(object): return self.options - def view(self): + def view(self, **kwargs): return { 'options': self.getOptions(), 'values': self.getValues() From d8f57963a185d38b6bc8120889dbca696c1cb856 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 24 Jun 2013 22:07:21 +0200 Subject: [PATCH 09/22] NZBIndex: Search for year inside brackets. closes #1874 --- couchpotato/core/providers/nzb/nzbindex/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/providers/nzb/nzbindex/main.py b/couchpotato/core/providers/nzb/nzbindex/main.py index 3643f55b..17b87fac 100644 --- a/couchpotato/core/providers/nzb/nzbindex/main.py +++ b/couchpotato/core/providers/nzb/nzbindex/main.py @@ -23,7 +23,7 @@ class NzbIndex(NZBProvider, RSS): def _searchOnTitle(self, title, movie, quality, results): - q = '"%s %s"' % (title, movie['library']['year']) + q = '"%s %s" | "%s (%s)"' % (title, movie['library']['year'], title, movie['library']['year']) arguments = tryUrlencode({ 'q': q, 'age': Env.setting('retention', 'nzb'), From bd56539103121863b811ed43c6afd5fd574547be Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 24 Jun 2013 22:31:50 +0200 Subject: [PATCH 10/22] Yifi cleanup --- .../core/providers/torrent/yify/__init__.py | 2 +- .../core/providers/torrent/yify/main.py | 38 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/couchpotato/core/providers/torrent/yify/__init__.py b/couchpotato/core/providers/torrent/yify/__init__.py index d5080ae0..f7477519 100644 --- a/couchpotato/core/providers/torrent/yify/__init__.py +++ b/couchpotato/core/providers/torrent/yify/__init__.py @@ -11,7 +11,7 @@ config = [{ 'subtab': 'providers', 'list': 'torrent_providers', 'name': 'Yify', - 'description': 'Small HD movies, encoded by Yify.', + 'description': 'Free provider, less accurate. Small HD movies, encoded by Yify.', 'wizard': False, 'options': [ { diff --git a/couchpotato/core/providers/torrent/yify/main.py b/couchpotato/core/providers/torrent/yify/main.py index 5c741a5b..1b8b584b 100644 --- a/couchpotato/core/providers/torrent/yify/main.py +++ b/couchpotato/core/providers/torrent/yify/main.py @@ -1,18 +1,14 @@ from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode -from couchpotato.core.helpers.variable import tryInt, cleanHost +from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog from couchpotato.core.providers.torrent.base import TorrentProvider -from couchpotato.environment import Env -import re -import time import traceback -from pprint import pprint log = CPLog(__name__) class Yify(TorrentProvider): - + urls = { 'test' : 'https://yify-torrents.com/api', 'search' : 'https://yify-torrents.com/api/list.json?keywords=%s&quality=%s', @@ -21,19 +17,31 @@ class Yify(TorrentProvider): http_time_between_calls = 1 #seconds - def _search(self, movie, quality, results): - try: - data = self.getJsonData(self.urls['search'] % (movie['library']['title'], quality['identifier'])) - except: - log.error('Search on Yify (%s) failed (could not decode JSON)', params) - return + def search(self, movie, quality): + + if not quality.get('hd', False): + return [] + + return super(Yify, self).search(movie, quality) + + def _searchOnTitle(self, title, movie, quality, results): + + data = self.getJsonData(self.urls['search'] % (title, quality['identifier'])) if data: try: - for result in data: + for result in data.get('MovieList'): + + try: + title = result['TorrentUrl'].split('/')[-1][:-8].replace('_', '.').strip('._') + title = title.replace('.-.', '-') + title = title.replace('..', '.') + except: + continue + results.append({ 'id': result['MovieID'], - 'name': result['MovieTitle'], + 'name': title, 'url': result['TorrentUrl'], 'detail_url': self.urls['detail'] % result['MovieID'], 'size': self.parseSize(result['Size']), @@ -43,4 +51,4 @@ class Yify(TorrentProvider): except: log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc())) - + From 2e8f670e947e22daa00e29da426041b3d57f6405 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 28 Jun 2013 23:32:38 +0200 Subject: [PATCH 11/22] Remove import --- couchpotato/core/providers/torrent/yify/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/couchpotato/core/providers/torrent/yify/main.py b/couchpotato/core/providers/torrent/yify/main.py index 1b8b584b..ad21362b 100644 --- a/couchpotato/core/providers/torrent/yify/main.py +++ b/couchpotato/core/providers/torrent/yify/main.py @@ -1,4 +1,3 @@ -from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog from couchpotato.core.providers.torrent.base import TorrentProvider From 6fcb4c2058ee166a8dcdb0046c237e9bd7be35cb Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 29 Jun 2013 21:07:07 +0200 Subject: [PATCH 12/22] Change default automation interval --- couchpotato/core/providers/automation/base.py | 2 +- couchpotato/core/providers/automation/goodfilms/main.py | 2 ++ couchpotato/core/providers/automation/letterboxd/main.py | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/providers/automation/base.py b/couchpotato/core/providers/automation/base.py index ea227ce0..e57f5c63 100644 --- a/couchpotato/core/providers/automation/base.py +++ b/couchpotato/core/providers/automation/base.py @@ -13,7 +13,7 @@ class Automation(Provider): enabled_option = 'automation_enabled' http_time_between_calls = 2 - interval = 86400 + interval = 1800 last_checked = 0 def __init__(self): diff --git a/couchpotato/core/providers/automation/goodfilms/main.py b/couchpotato/core/providers/automation/goodfilms/main.py index 266a284c..e1125615 100644 --- a/couchpotato/core/providers/automation/goodfilms/main.py +++ b/couchpotato/core/providers/automation/goodfilms/main.py @@ -9,6 +9,8 @@ class Goodfilms(Automation): url = 'http://goodfil.ms/%s/queue?page=%d&without_layout=1' + interval = 1800 + def getIMDBids(self): if not self.conf('automation_username'): diff --git a/couchpotato/core/providers/automation/letterboxd/main.py b/couchpotato/core/providers/automation/letterboxd/main.py index 7bae2ad5..1f106dd1 100644 --- a/couchpotato/core/providers/automation/letterboxd/main.py +++ b/couchpotato/core/providers/automation/letterboxd/main.py @@ -12,6 +12,8 @@ class Letterboxd(Automation): url = 'http://letterboxd.com/%s/watchlist/' pattern = re.compile(r'(.*)\((\d*)\)') + interval = 1800 + def getIMDBids(self): urls = splitString(self.conf('automation_urls')) From 52b2858ac2734fccec1c3563fbf29697db1fc128 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 29 Jun 2013 21:39:53 +0200 Subject: [PATCH 13/22] Don't enable yifi by default --- couchpotato/core/providers/torrent/yify/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/providers/torrent/yify/__init__.py b/couchpotato/core/providers/torrent/yify/__init__.py index f7477519..70d65687 100644 --- a/couchpotato/core/providers/torrent/yify/__init__.py +++ b/couchpotato/core/providers/torrent/yify/__init__.py @@ -17,7 +17,7 @@ config = [{ { 'name': 'enabled', 'type': 'enabler', - 'default': True + 'default': 0 }, { 'name': 'extra_score', From 3e667ee39ac63b038f9161972b414dfa8a790f22 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 29 Jun 2013 21:56:24 +0200 Subject: [PATCH 14/22] Couldn't press letter in movie filter. fix #1888 --- couchpotato/core/plugins/movie/static/movie.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/plugins/movie/static/movie.css index 04600475..60ab96b2 100644 --- a/couchpotato/core/plugins/movie/static/movie.css +++ b/couchpotato/core/plugins/movie/static/movie.css @@ -845,7 +845,7 @@ font-family: 'Elusive-Icons'; content: "\e03e"; position: absolute; - height: 100%; + height: 20px; line-height: 45px; font-size: 12px; margin: 0 0 0 10px; From 8c77d0d775f51f4c0a4c7e62dc5058ad89ad596b Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 29 Jun 2013 22:03:29 +0200 Subject: [PATCH 15/22] Add advanced option to search on launch. fix #1887 --- couchpotato/core/plugins/searcher/__init__.py | 8 ++++++++ couchpotato/core/plugins/searcher/main.py | 3 +++ 2 files changed, 11 insertions(+) diff --git a/couchpotato/core/plugins/searcher/__init__.py b/couchpotato/core/plugins/searcher/__init__.py index aec419a2..bed90eb2 100644 --- a/couchpotato/core/plugins/searcher/__init__.py +++ b/couchpotato/core/plugins/searcher/__init__.py @@ -57,6 +57,14 @@ config = [{ 'advanced': True, 'description': 'Cron settings for the searcher see: APScheduler for details.', 'options': [ + { + 'name': 'run_on_launch', + 'label': 'Run on launch', + 'advanced': True, + 'default': 0, + 'type': 'bool', + 'description': 'Force run the searcher after (re)start.', + }, { 'name': 'cron_day', 'label': 'Day', diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 3d85a565..71077dbf 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -49,6 +49,9 @@ class Searcher(Plugin): }"""}, }) + if self.conf('run_on_launch'): + addEvent('app.load', self.allMovies) + addEvent('app.load', self.setCrons) addEvent('setting.save.searcher.cron_day.after', self.setCrons) addEvent('setting.save.searcher.cron_hour.after', self.setCrons) From b1942678b425c020d924a245644ed37d2e8ddf3b Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 29 Jun 2013 22:20:35 +0200 Subject: [PATCH 16/22] Add hash and date to update available notification. fix #1883 --- couchpotato/core/_base/updater/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py index 5b61aebe..38b7d36e 100644 --- a/couchpotato/core/_base/updater/main.py +++ b/couchpotato/core/_base/updater/main.py @@ -85,7 +85,9 @@ class Updater(Plugin): if self.updater.check(): if not self.available_notified and self.conf('notification') and not self.conf('automatic'): - fireEvent('updater.available', message = 'A new update is available', data = self.updater.info()) + info = self.updater.info() + version_date = datetime.fromtimestamp(info['update_version']['date']) + fireEvent('updater.available', message = 'A new update with hash "%s" is available, this version is from %s' % (info['update_version']['hash'], version_date), data = info) self.available_notified = True return True From 93346b0c63cf7201c7eec02947742f5b845012ec Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 30 Jun 2013 01:16:13 +0200 Subject: [PATCH 17/22] Properly update release dates --- couchpotato/core/plugins/library/main.py | 4 +-- .../core/providers/movie/_modifier/main.py | 1 - couchpotato/core/settings/model.py | 32 +++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/library/main.py b/couchpotato/core/plugins/library/main.py index 168aead1..b0d34dd7 100644 --- a/couchpotato/core/plugins/library/main.py +++ b/couchpotato/core/plugins/library/main.py @@ -87,7 +87,7 @@ class LibraryPlugin(Plugin): library.tagline = toUnicode(info.get('tagline', '')) library.year = info.get('year', 0) library.status_id = done_status.get('id') - library.info = info + library.info.update(info) db.commit() # Titles @@ -148,7 +148,7 @@ class LibraryPlugin(Plugin): if dates and dates.get('expires', 0) < time.time() or not dates: dates = fireEvent('movie.release_date', identifier = identifier, merge = True) - library.info = mergeDicts(library.info, {'release_date': dates }) + library.info.update({'release_date': dates }) db.commit() db.expire_all() diff --git a/couchpotato/core/providers/movie/_modifier/main.py b/couchpotato/core/providers/movie/_modifier/main.py index 148d9035..e4d70221 100644 --- a/couchpotato/core/providers/movie/_modifier/main.py +++ b/couchpotato/core/providers/movie/_modifier/main.py @@ -28,7 +28,6 @@ class MovieResultModifier(Plugin): 'tagline': '', 'imdb': '', 'genres': [], - 'release_date': {} } def __init__(self): diff --git a/couchpotato/core/settings/model.py b/couchpotato/core/settings/model.py index 6c8deb1e..00ac34e5 100644 --- a/couchpotato/core/settings/model.py +++ b/couchpotato/core/settings/model.py @@ -3,6 +3,7 @@ from elixir.entity import Entity from elixir.fields import Field from elixir.options import options_defaults, using_options from elixir.relationships import ManyToMany, OneToMany, ManyToOne +from sqlalchemy.ext.mutable import Mutable from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, String, \ TypeDecorator import json @@ -39,6 +40,37 @@ class JsonType(TypeDecorator): def process_result_value(self, value, dialect): return json.loads(value if value else '{}') +class MutableDict(Mutable, dict): + + @classmethod + def coerce(cls, key, value): + if not isinstance(value, MutableDict): + if isinstance(value, dict): + return MutableDict(value) + return Mutable.coerce(key, value) + else: + return value + + def __delitem(self, key): + dict.__delitem__(self, key) + self.changed() + + def __setitem__(self, key, value): + dict.__setitem__(self, key, value) + self.changed() + + def __getstate__(self): + return dict(self) + + def __setstate__(self, state): + self.update(self) + + def update(self, *args, **kwargs): + super(MutableDict, self).update(*args, **kwargs) + self.changed() + +MutableDict.associate_with(JsonType) + class Movie(Entity): """Movie Resource a movie could have multiple releases From ad3c24f95027a2f6c60b390d56033fa1d1effd3f Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 30 Jun 2013 13:17:43 +0200 Subject: [PATCH 18/22] Improved "too early to search" calculations --- couchpotato/core/plugins/dashboard/main.py | 4 ++-- couchpotato/core/plugins/searcher/main.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/couchpotato/core/plugins/dashboard/main.py b/couchpotato/core/plugins/dashboard/main.py index 9836ef9d..41d35518 100644 --- a/couchpotato/core/plugins/dashboard/main.py +++ b/couchpotato/core/plugins/dashboard/main.py @@ -70,9 +70,9 @@ class Dashboard(Plugin): coming_soon = False # Theater quality - if pp.get('theater') and fireEvent('searcher.could_be_released', True, eta, single = True): + if pp.get('theater') and fireEvent('searcher.could_be_released', True, eta, movie.library.year, single = True): coming_soon = True - if pp.get('dvd') and fireEvent('searcher.could_be_released', False, eta, single = True): + if pp.get('dvd') and fireEvent('searcher.could_be_released', False, eta, movie.library.year, single = True): coming_soon = True # Skip if movie is snatched/downloaded/available diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 71077dbf..cc2774ac 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -8,6 +8,7 @@ from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin from couchpotato.core.settings.model import Movie, Release, ReleaseInfo from couchpotato.environment import Env +from datetime import date from inspect import ismethod, isfunction from sqlalchemy.exc import InterfaceError import datetime @@ -164,7 +165,7 @@ class Searcher(Plugin): ret = False for quality_type in movie['profile']['types']: - if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates): + if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates, movie['library']['year']): too_early_to_search.append(quality_type['quality']['identifier']) continue @@ -558,11 +559,12 @@ class Searcher(Plugin): return False - def couldBeReleased(self, is_pre_release, dates): + def couldBeReleased(self, is_pre_release, dates, year = None): now = int(time.time()) + now_year = date.today().year - if not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0): + if (year is None or year < now_year - 1) and (not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0)): return True else: From 74bf6bc411dd6a708d6bdc93597675d1842c4bcf Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 30 Jun 2013 13:17:56 +0200 Subject: [PATCH 19/22] Always set info dict on library --- couchpotato/core/plugins/library/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/plugins/library/main.py b/couchpotato/core/plugins/library/main.py index b0d34dd7..b463abfd 100644 --- a/couchpotato/core/plugins/library/main.py +++ b/couchpotato/core/plugins/library/main.py @@ -1,7 +1,6 @@ from couchpotato import get_session from couchpotato.core.event import addEvent, fireEventAsync, fireEvent from couchpotato.core.helpers.encoding import toUnicode, simplifyString -from couchpotato.core.helpers.variable import mergeDicts from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin from couchpotato.core.settings.model import Library, LibraryTitle, File @@ -32,7 +31,8 @@ class LibraryPlugin(Plugin): identifier = attrs.get('identifier'), plot = toUnicode(attrs.get('plot')), tagline = toUnicode(attrs.get('tagline')), - status_id = status.get('id') + status_id = status.get('id'), + info = {}, ) title = LibraryTitle( From 58c446de2d6d7b8cd872a47c98cd445f06357448 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 30 Jun 2013 15:20:02 +0200 Subject: [PATCH 20/22] Make string param boolean --- couchpotato/core/helpers/request.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/couchpotato/core/helpers/request.py b/couchpotato/core/helpers/request.py index 0ac84ef8..c2249796 100644 --- a/couchpotato/core/helpers/request.py +++ b/couchpotato/core/helpers/request.py @@ -32,6 +32,8 @@ def getParams(params): current = current[item] else: temp[param] = toUnicode(unquote(value)) + if temp[param].lower() in ['true', 'false']: + temp[param] = temp[param].lower() != 'false' return dictToList(temp) From 6f42b4c316acf9b986cf7be1c87776db9ff93adb Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 30 Jun 2013 15:21:06 +0200 Subject: [PATCH 21/22] Don't show coming soon when no dvd release is set --- couchpotato/core/plugins/dashboard/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/plugins/dashboard/main.py b/couchpotato/core/plugins/dashboard/main.py index 41d35518..df21fefa 100644 --- a/couchpotato/core/plugins/dashboard/main.py +++ b/couchpotato/core/plugins/dashboard/main.py @@ -93,8 +93,8 @@ class Dashboard(Plugin): }) # Don't list older movies - if ((not late and (not eta.get('dvd') or (eta.get('dvd') and eta.get('dvd') > (now - 2419200)))) or \ - (late and eta.get('dvd') and eta.get('dvd') < (now - 2419200))): + if ((not late and ((not eta.get('dvd') and not eta.get('theater')) or (eta.get('dvd') and eta.get('dvd') > (now - 2419200)))) or \ + (late and (eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200))): movies.append(temp) if len(movies) >= limit: From 931951ff37732631207a203053eb61bc39cad43d Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 30 Jun 2013 15:57:58 +0200 Subject: [PATCH 22/22] Change default min size for 720p and 1080p --- couchpotato/core/plugins/quality/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py index ead8446e..6ac1c6b6 100644 --- a/couchpotato/core/plugins/quality/main.py +++ b/couchpotato/core/plugins/quality/main.py @@ -17,8 +17,8 @@ class QualityPlugin(Plugin): qualities = [ {'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]}, - {'identifier': '1080p', 'hd': True, 'size': (5000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']}, - {'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']}, + {'identifier': '1080p', 'hd': True, 'size': (4000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']}, + {'identifier': '720p', 'hd': True, 'size': (3000, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']}, {'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':['avi']}, {'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']}, {'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},