Merge branch 'refs/heads/develop'

Conflicts:
	couchpotato/core/helpers/request.py
This commit is contained in:
Ruud committed 2013-07-01 23:34:40 +02:00
commit 47e649643f
25 files changed
+498 -118

No files matched your search

+3 -1
View File
@@ -85,7 +85,9 @@ class Updater(Plugin):
if self.updater.check(): if self.updater.check():
if not self.available_notified and self.conf('notification') and not self.conf('automatic'): 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 self.available_notified = True
return True return True
+2
View File
@@ -32,6 +32,8 @@ def getParams(params):
current = current[item] current = current[item]
else: else:
temp[param] = toUnicode(unquote(value)) temp[param] = toUnicode(unquote(value))
if temp[param].lower() in ['true', 'false']:
temp[param] = temp[param].lower() != 'false'
return dictToList(temp) return dictToList(temp)
+4 -34
View File
@@ -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()
@@ -100,9 +70,9 @@ class Dashboard(Plugin):
coming_soon = False coming_soon = False
# Theater quality # 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 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 coming_soon = True
# Skip if movie is snatched/downloaded/available # Skip if movie is snatched/downloaded/available
@@ -123,8 +93,8 @@ class Dashboard(Plugin):
}) })
# Don't list older movies # Don't list older movies
if ((not late and (not eta.get('dvd') or (eta.get('dvd') and eta.get('dvd') > (now - 2419200)))) or \ 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') and eta.get('dvd') < (now - 2419200))): (late and (eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200))):
movies.append(temp) movies.append(temp)
if len(movies) >= limit: if len(movies) >= limit:
+4 -4
View File
@@ -1,7 +1,6 @@
from couchpotato import get_session from couchpotato import get_session
from couchpotato.core.event import addEvent, fireEventAsync, fireEvent from couchpotato.core.event import addEvent, fireEventAsync, fireEvent
from couchpotato.core.helpers.encoding import toUnicode, simplifyString from couchpotato.core.helpers.encoding import toUnicode, simplifyString
from couchpotato.core.helpers.variable import mergeDicts
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Library, LibraryTitle, File from couchpotato.core.settings.model import Library, LibraryTitle, File
@@ -32,7 +31,8 @@ class LibraryPlugin(Plugin):
identifier = attrs.get('identifier'), identifier = attrs.get('identifier'),
plot = toUnicode(attrs.get('plot')), plot = toUnicode(attrs.get('plot')),
tagline = toUnicode(attrs.get('tagline')), tagline = toUnicode(attrs.get('tagline')),
status_id = status.get('id') status_id = status.get('id'),
info = {},
) )
title = LibraryTitle( title = LibraryTitle(
@@ -87,7 +87,7 @@ class LibraryPlugin(Plugin):
library.tagline = toUnicode(info.get('tagline', '')) library.tagline = toUnicode(info.get('tagline', ''))
library.year = info.get('year', 0) library.year = info.get('year', 0)
library.status_id = done_status.get('id') library.status_id = done_status.get('id')
library.info = info library.info.update(info)
db.commit() db.commit()
# Titles # Titles
@@ -148,7 +148,7 @@ class LibraryPlugin(Plugin):
if dates and dates.get('expires', 0) < time.time() or not dates: if dates and dates.get('expires', 0) < time.time() or not dates:
dates = fireEvent('movie.release_date', identifier = identifier, merge = True) 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.commit()
db.expire_all() db.expire_all()
@@ -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+']');
@@ -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;
} }
@@ -845,7 +845,7 @@
font-family: 'Elusive-Icons'; font-family: 'Elusive-Icons';
content: "\e03e"; content: "\e03e";
position: absolute; position: absolute;
height: 100%; height: 20px;
line-height: 45px; line-height: 45px;
font-size: 12px; font-size: 12px;
margin: 0 0 0 10px; margin: 0 0 0 10px;
@@ -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; }
+26 -11
View File
@@ -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,12 +227,12 @@ 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
}); });
}) })
}, },
alternativeTitle: function(alternative){ alternativeTitle: function(alternative){
@@ -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();
+2 -2
View File
@@ -17,8 +17,8 @@ class QualityPlugin(Plugin):
qualities = [ qualities = [
{'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]}, {'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': '1080p', 'hd': True, 'size': (4000, 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': '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': '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': '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')]}, {'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
@@ -57,6 +57,14 @@ config = [{
'advanced': True, 'advanced': True,
'description': 'Cron settings for the searcher see: <a href="http://packages.python.org/APScheduler/cronschedule.html">APScheduler</a> for details.', 'description': 'Cron settings for the searcher see: <a href="http://packages.python.org/APScheduler/cronschedule.html">APScheduler</a> for details.',
'options': [ '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', 'name': 'cron_day',
'label': 'Day', 'label': 'Day',
+8 -3
View File
@@ -8,6 +8,7 @@ from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
from couchpotato.environment import Env from couchpotato.environment import Env
from datetime import date
from inspect import ismethod, isfunction from inspect import ismethod, isfunction
from sqlalchemy.exc import InterfaceError from sqlalchemy.exc import InterfaceError
import datetime import datetime
@@ -49,6 +50,9 @@ class Searcher(Plugin):
}"""}, }"""},
}) })
if self.conf('run_on_launch'):
addEvent('app.load', self.allMovies)
addEvent('app.load', self.setCrons) addEvent('app.load', self.setCrons)
addEvent('setting.save.searcher.cron_day.after', self.setCrons) addEvent('setting.save.searcher.cron_day.after', self.setCrons)
addEvent('setting.save.searcher.cron_hour.after', self.setCrons) addEvent('setting.save.searcher.cron_hour.after', self.setCrons)
@@ -161,7 +165,7 @@ class Searcher(Plugin):
ret = False ret = False
for quality_type in movie['profile']['types']: 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']) too_early_to_search.append(quality_type['quality']['identifier'])
continue continue
@@ -555,11 +559,12 @@ class Searcher(Plugin):
return False return False
def couldBeReleased(self, is_pre_release, dates): def couldBeReleased(self, is_pre_release, dates, year = None):
now = int(time.time()) 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 return True
else: else:
+78 -6
View File
@@ -1,20 +1,92 @@
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(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])
suggestions = fireEvent('movie.suggest', movies = movies, ignore = list(set(ignored)), single = True)
if suggestions:
new_suggestions.extend(suggestions)
self.setCache(md5(ss('suggestion_cached')), new_suggestions, timeout = 6048000)
return new_suggestions
@@ -0,0 +1,84 @@
.suggestions {
}
.suggestions > h2 {
height: 40px;
}
.suggestions .movie_result {
display: inline-block;
width: 33.333%;
height: 150px;
}
@media all and (max-width: 960px) {
.suggestions .movie_result {
width: 50%;
}
}
@media all and (max-width: 600px) {
.suggestions .movie_result {
width: 100%;
}
}
.suggestions .movie_result .data {
left: 100px;
background: #4e5969;
border: none;
}
.suggestions .movie_result .data .info {
top: 15px;
left: 15px;
right: 15px;
}
.suggestions .movie_result .data .info h2 {
white-space: normal;
max-height: 120px;
font-size: 18px;
line-height: 18px;
}
.suggestions .movie_result .data .info .year {
position: static;
display: block;
margin: 5px 0 0;
padding: 0;
opacity: .6;
}
.suggestions .movie_result .data {
cursor: default;
}
.suggestions .movie_result .options {
left: 100px;
}
.suggestions .movie_result .thumbnail {
width: 100px;
}
.suggestions .movie_result .actions {
position: absolute;
bottom: 10px;
right: 10px;
display: none;
width: 120px;
}
.suggestions .movie_result:hover .actions {
display: block;
}
.suggestions .movie_result .data.open .actions {
display: none;
}
.suggestions .movie_result .actions a {
margin-left: 10px;
vertical-align: middle;
}
@@ -0,0 +1,102 @@
var SuggestList = new Class({
Implements: [Options, Events],
initialize: function(options){
var self = this;
self.setOptions(options);
self.create();
},
create: function(){
var self = this;
self.el = new Element('div.suggestions', {
'events': {
'click:relay(a.delete)': function(e, el){
(e).stop();
$(el).getParent('.movie_result').destroy();
Api.request('suggestion.ignore', {
'data': {
'imdb': el.get('data-ignore')
},
'onComplete': self.fill.bind(self)
});
}
}
}).grab(
new Element('h2', {
'text': 'You might like these'
})
);
self.api_request = Api.request('suggestion.view', {
'onComplete': self.fill.bind(self)
});
},
fill: function(json){
var self = this;
Object.each(json.suggestions, function(movie){
var m = new Block.Search.Item(movie, {
'onAdded': function(){
self.afterAdded(m, movie)
}
});
m.data_container.grab(
new Element('div.actions').adopt(
new Element('a.add.icon2', {
'title': 'Add movie with your default quality',
'data-add': movie.imdb,
'events': {
'click': m.showOptions.bind(m)
}
}),
$(new MA.IMDB(m)),
$(new MA.Trailer(m, {
'height': 150
})),
new Element('a.delete.icon2', {
'title': 'Don\'t suggest this movie again',
'data-ignore': movie.imdb
})
)
);
m.data_container.removeEvents('click');
$(m).inject(self.el);
});
},
afterAdded: function(m, movie){
var self = this;
setTimeout(function(){
$(m).destroy();
Api.request('suggestion.ignore', {
'data': {
'imdb': movie.imdb,
'remove_only': true
},
'onComplete': self.fill.bind(self)
});
}, 3000);
},
toElement: function(){
return this.el;
}
})
@@ -13,7 +13,7 @@ class Automation(Provider):
enabled_option = 'automation_enabled' enabled_option = 'automation_enabled'
http_time_between_calls = 2 http_time_between_calls = 2
interval = 86400 interval = 1800
last_checked = 0 last_checked = 0
def __init__(self): def __init__(self):
@@ -9,6 +9,8 @@ class Goodfilms(Automation):
url = 'http://goodfil.ms/%s/queue?page=%d&without_layout=1' url = 'http://goodfil.ms/%s/queue?page=%d&without_layout=1'
interval = 1800
def getIMDBids(self): def getIMDBids(self):
if not self.conf('automation_username'): if not self.conf('automation_username'):
@@ -12,6 +12,8 @@ class Letterboxd(Automation):
url = 'http://letterboxd.com/%s/watchlist/' url = 'http://letterboxd.com/%s/watchlist/'
pattern = re.compile(r'(.*)\((\d*)\)') pattern = re.compile(r'(.*)\((\d*)\)')
interval = 1800
def getIMDBids(self): def getIMDBids(self):
urls = splitString(self.conf('automation_urls')) urls = splitString(self.conf('automation_urls'))
@@ -28,7 +28,6 @@ class MovieResultModifier(Plugin):
'tagline': '', 'tagline': '',
'imdb': '', 'imdb': '',
'genres': [], 'genres': [],
'release_date': {}
} }
def __init__(self): def __init__(self):
@@ -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
@@ -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),
}) }, headers = self.getRequestHeaders())
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),
@@ -23,7 +23,7 @@ class NzbIndex(NZBProvider, RSS):
def _searchOnTitle(self, title, movie, quality, results): 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({ arguments = tryUrlencode({
'q': q, 'q': q,
'age': Env.setting('retention', 'nzb'), 'age': Env.setting('retention', 'nzb'),
@@ -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': 'Free provider, less accurate. Small HD movies, encoded by <a href="https://yify-torrents.com/">Yify</a>.',
'wizard': False,
'options': [
{
'name': 'enabled',
'type': 'enabler',
'default': 0
},
{
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
'type': 'int',
'default': 0,
'description': 'Starting score for each release found via this provider.',
}
],
}
]
}]
@@ -0,0 +1,53 @@
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.torrent.base import TorrentProvider
import traceback
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):
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.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': title,
'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()))
+32
View File
@@ -3,6 +3,7 @@ from elixir.entity import Entity
from elixir.fields import Field from elixir.fields import Field
from elixir.options import options_defaults, using_options from elixir.options import options_defaults, using_options
from elixir.relationships import ManyToMany, OneToMany, ManyToOne from elixir.relationships import ManyToMany, OneToMany, ManyToOne
from sqlalchemy.ext.mutable import Mutable
from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, String, \ from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, String, \
TypeDecorator TypeDecorator
import json import json
@@ -39,6 +40,37 @@ class JsonType(TypeDecorator):
def process_result_value(self, value, dialect): def process_result_value(self, value, dialect):
return json.loads(value if value else '{}') 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): class Movie(Entity):
"""Movie Resource a movie could have multiple releases """Movie Resource a movie could have multiple releases
+4 -16
View File
@@ -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
View File
@@ -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"; }