Merge branch 'refs/heads/develop'

Conflicts:
	couchpotato/core/helpers/request.py
This commit is contained in:
Ruud
2013-07-01 23:34:40 +02:00
25 changed files with 498 additions and 118 deletions
+3 -1
View File
@@ -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
+2
View File
@@ -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)
+4 -34
View File
@@ -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()
@@ -100,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
@@ -123,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:
+4 -4
View File
@@ -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(
@@ -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()
@@ -1,9 +1,13 @@
var MovieAction = new Class({
Implements: [Options],
class_name: 'action icon2',
initialize: function(movie){
initialize: function(movie, options){
var self = this;
self.setOptions(options);
self.movie = movie;
self.create();
@@ -21,6 +25,32 @@ var MovieAction = new Class({
this.el.removeClass('disable')
},
getTitle: function(){
var self = this;
try {
return self.movie.getTitle();
}
catch(e){
try {
return self.movie.original_title ? self.movie.original_title : self.movie.titles[0];
}
catch(e){
return 'Unknown';
}
}
},
get: function(key){
var self = this;
try {
return self.movie.get(key)
}
catch(e){
return self.movie[key]
}
},
createMask: function(){
var self = this;
self.mask = new Element('div.mask', {
@@ -62,10 +92,10 @@ MA.IMDB = new Class({
create: function(){
var self = this;
self.id = self.movie.get('identifier');
self.id = self.movie.get('imdb') || self.movie.get('identifier');
self.el = new Element('a.imdb', {
'title': 'Go to the IMDB page of ' + self.movie.getTitle(),
'title': 'Go to the IMDB page of ' + self.getTitle(),
'href': 'http://www.imdb.com/title/'+self.id+'/',
'target': '_blank'
});
@@ -83,7 +113,7 @@ MA.Release = new Class({
var self = this;
self.el = new Element('a.releases.download', {
'title': 'Show the releases that are available for ' + self.movie.getTitle(),
'title': 'Show the releases that are available for ' + self.getTitle(),
'events': {
'click': self.show.bind(self)
}
@@ -367,7 +397,7 @@ MA.Trailer = new Class({
var self = this;
self.el = new Element('a.trailer', {
'title': 'Watch the trailer of ' + self.movie.getTitle(),
'title': 'Watch the trailer of ' + self.getTitle(),
'events': {
'click': self.watch.bind(self)
}
@@ -380,12 +410,12 @@ MA.Trailer = new Class({
var data_url = 'http://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18'
var url = data_url.substitute({
'title': encodeURI(self.movie.getTitle()),
'year': self.movie.get('year'),
'title': encodeURI(self.getTitle()),
'year': self.get('year'),
'offset': offset || 1
}),
size = $(self.movie).getSize(),
height = (size.x/16)*9,
height = self.options.height || (size.x/16)*9,
id = 'trailer-'+randomString();
self.player_container = new Element('div[id='+id+']');
@@ -629,7 +629,7 @@
height: auto;
}
.movies .movie .trailer_container {
.trailer_container {
width: 100%;
background: #000;
text-align: center;
@@ -639,11 +639,11 @@
position: absolute;
z-index: 10;
}
.movies .movie .trailer_container.hide {
.trailer_container.hide {
height: 0 !important;
}
.movies .movie .hide_trailer {
.hide_trailer {
position: absolute;
top: 0;
left: 50%;
@@ -655,7 +655,7 @@
transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s;
z-index: 11;
}
.movies .movie .hide_trailer.hide {
.hide_trailer.hide {
top: -30px;
}
@@ -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;
@@ -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; }
+26 -11
View File
@@ -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();
+2 -2
View File
@@ -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')]},
@@ -57,6 +57,14 @@ config = [{
'advanced': True,
'description': 'Cron settings for the searcher see: <a href="http://packages.python.org/APScheduler/cronschedule.html">APScheduler</a> 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',
+8 -3
View File
@@ -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
@@ -49,6 +50,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)
@@ -161,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
@@ -555,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:
+78 -6
View File
@@ -1,20 +1,92 @@
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(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'
http_time_between_calls = 2
interval = 86400
interval = 1800
last_checked = 0
def __init__(self):
@@ -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'):
@@ -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'))
@@ -28,7 +28,6 @@ class MovieResultModifier(Plugin):
'tagline': '',
'imdb': '',
'genres': [],
'release_date': {}
}
def __init__(self):
@@ -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
@@ -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))
}, headers = self.getRequestHeaders())
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),
@@ -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'),
@@ -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.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
+4 -16
View File
@@ -101,6 +101,9 @@ Page.Home = new Class({
});
});
// Suggest
self.suggestion_list = new SuggestList();
// Still not available
self.late_list = new MovieList({
'navigation': false,
@@ -121,25 +124,10 @@ Page.Home = new Class({
self.el.adopt(
$(self.available_list),
$(self.soon_list),
$(self.suggestion_list),
$(self.late_list)
);
// Suggest
// self.suggestion_list = new MovieList({
// 'navigation': false,
// 'identifier': 'suggestions',
// 'limit': 6,
// 'load_more': false,
// 'view': 'thumbs',
// 'api_call': 'suggestion.suggest'
// });
// self.el.adopt(
// new Element('h2', {
// 'text': 'You might like'
// }),
// $(self.suggestion_list)
// );
// Recent
// Snatched
// Renamed
+1
View File
@@ -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"; }