Browse Source

Merge branch 'refs/heads/develop'

pull/228/head
Ruud 13 years ago
parent
commit
ca87f2c231
  1. 18
      couchpotato/__init__.py
  2. 2
      couchpotato/core/_base/_core/__init__.py
  3. 3
      couchpotato/core/_base/updater/main.py
  4. 2
      couchpotato/core/_base/updater/static/updater.js
  5. 4
      couchpotato/core/notifications/core/main.py
  6. 2
      couchpotato/core/notifications/notifymyandroid/main.py
  7. 2
      couchpotato/core/notifications/notifymywp/main.py
  8. 2
      couchpotato/core/plugins/manage/main.py
  9. 6
      couchpotato/core/plugins/movie/main.py
  10. 5
      couchpotato/core/plugins/movie/static/movie.css
  11. 6
      couchpotato/core/plugins/movie/static/movie.js
  12. 11
      couchpotato/core/plugins/quality/main.py
  13. 6
      couchpotato/core/plugins/renamer/main.py
  14. 7
      couchpotato/core/plugins/scanner/main.py
  15. 2
      couchpotato/core/plugins/score/scores.py
  16. 25
      couchpotato/core/plugins/searcher/main.py
  17. 8
      couchpotato/core/providers/base.py
  18. 22
      couchpotato/core/providers/metadata/xbmc/__init__.py
  19. 6
      couchpotato/core/providers/metadata/xbmc/main.py
  20. 1
      couchpotato/core/providers/movie/couchpotatoapi/main.py
  21. 5
      couchpotato/core/providers/movie/imdbapi/main.py
  22. 2
      couchpotato/core/providers/nzb/newzbin/main.py
  23. 6
      couchpotato/core/providers/nzb/newznab/main.py
  24. 8
      couchpotato/core/providers/nzb/nzbs/main.py
  25. 3
      couchpotato/core/providers/torrent/kickasstorrents/main.py
  26. 6
      couchpotato/templates/api.html

18
couchpotato/__init__.py

@ -1,6 +1,8 @@
from couchpotato.api import api_docs, api_docs_missing
from couchpotato.core.auth import requires_auth
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.request import getParams, jsonified
from couchpotato.core.helpers.variable import md5
from couchpotato.core.logger import CPLog
from couchpotato.environment import Env
from flask.app import Flask
@ -51,6 +53,22 @@ def apiDocs():
del api_docs_missing['']
return render_template('api.html', fireEvent = fireEvent, routes = sorted(routes), api_docs = api_docs, api_docs_missing = sorted(api_docs_missing))
@web.route('getkey/')
def getApiKey():
api = None
params = getParams()
username = Env.setting('username')
password = Env.setting('password')
if (params.get('u') == md5(username) or not username) and (params.get('p') == password or not password):
api = Env.setting('api_key')
return jsonified({
'success': api is not None,
'api_key': api
})
@app.errorhandler(404)
def page_not_found(error):
index_url = url_for('web.index')

2
couchpotato/core/_base/_core/__init__.py

@ -55,7 +55,7 @@ config = [{
'name': 'api_key',
'default': uuid4().hex,
'readonly': 1,
'description': 'Let 3rd party app do stuff. <a target="_self" href="/docs/">Docs</a>',
'description': 'Let 3rd party app do stuff. <a target="_self" href="../../docs/">Docs</a>',
},
{
'name': 'debug',

3
couchpotato/core/_base/updater/main.py

@ -96,7 +96,8 @@ class BaseUpdater(Plugin):
'last_check': self.last_check,
'update_version': self.update_version,
'version': self.getVersion(),
'repo_name': '%s/%s' % (self.repo_user, self.repo_name)
'repo_name': '%s/%s' % (self.repo_user, self.repo_name),
'branch': self.branch,
}
def check(self):

2
couchpotato/core/_base/updater/static/updater.js

@ -79,6 +79,8 @@ var UpdaterBase = new Class({
if(json.success){
App.restart('Please wait while CouchPotato is being updated with more awesome stuff.', 'Updating');
App.checkAvailable.delay(500, App);
if(self.message)
self.message.destroy();
}
}
});

4
couchpotato/core/notifications/core/main.py

@ -56,7 +56,7 @@ class CoreNotifier(Notification):
addEvent('library.update_finish', lambda data: fireEvent('notify.frontend', type = 'library.update', data = data))
def markAsRead(self):
ids = getParam('ids').split(',')
ids = [x.strip() for x in getParam('ids').split(',')]
db = get_session()
@ -78,7 +78,7 @@ class CoreNotifier(Notification):
q = db.query(Notif)
if limit_offset:
splt = limit_offset.split(',')
splt = [x.strip() for x in limit_offset.split(',')]
limit = splt[0]
offset = 0 if len(splt) is 1 else splt[1]
q = q.limit(limit).offset(offset)

2
couchpotato/core/notifications/notifymyandroid/main.py

@ -11,7 +11,7 @@ class NotifyMyAndroid(Notification):
if self.isDisabled(): return
nma = pynma.PyNMA()
keys = self.conf('api_key').split(',')
keys = [x.strip() for x in self.conf('api_key').split(',')]
nma.addkey(keys)
nma.developerkey(self.conf('dev_key'))

2
couchpotato/core/notifications/notifymywp/main.py

@ -10,7 +10,7 @@ class NotifyMyWP(Notification):
def notify(self, message = '', data = {}):
if self.isDisabled(): return
keys = self.conf('api_key').split(',')
keys = [x.strip() for x in self.conf('api_key').split(',')]
p = PyNMWP(keys, self.conf('dev_key'))
response = p.push(application = self.default_title, event = message, description = message, priority = self.conf('priority'), batch_mode = len(keys) > 1)

2
couchpotato/core/plugins/manage/main.py

@ -77,6 +77,6 @@ class Manage(Plugin):
def directories(self):
try:
return self.conf('library', default = '').split('::')
return [x.strip() for x in self.conf('library', default = '').split('::')]
except:
return []

6
couchpotato/core/plugins/movie/main.py

@ -139,7 +139,7 @@ class MoviePlugin(Plugin):
if limit_offset:
splt = limit_offset.split(',')
splt = [x.strip() for x in limit_offset.split(',')]
limit = splt[0]
offset = 0 if len(splt) is 1 else splt[1]
q2 = q2.limit(limit).offset(offset)
@ -324,7 +324,7 @@ class MoviePlugin(Plugin):
available_status = fireEvent('status.get', 'available', single = True)
ids = params.get('id').split(',')
ids = [x.strip() for x in params.get('id').split(',')]
for movie_id in ids:
m = db.query(Movie).filter_by(id = movie_id).first()
@ -356,7 +356,7 @@ class MoviePlugin(Plugin):
params = getParams()
ids = params.get('id').split(',')
ids = [x.strip() for x in params.get('id').split(',')]
for movie_id in ids:
self.delete(movie_id)

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

@ -279,7 +279,8 @@
border: 0;
}
.movies .options .table .provider {
width: 130px;
width: 120px;
text-overflow: ellipsis;
}
.movies .options .table .name {
width: 350px;
@ -290,6 +291,8 @@
.movies .options .table.files .name { width: 605px; }
.movies .options .table .type { width: 130px; }
.movies .options .table .is_available { width: 90px; }
.movies .options .table .age,
.movies .options .table .size { width: 40px; }
.movies .options .table a {
width: 30px !important;

6
couchpotato/core/plugins/movie/static/movie.js

@ -270,8 +270,9 @@ var ReleaseAction = new Class({
// Header
new Element('div.item.head').adopt(
new Element('span.name', {'text': 'Release name'}),
new Element('span.status', {'text': 'Status'}),
new Element('span.quality', {'text': 'Quality'}),
new Element('span.size', {'text': 'Size (MB)'}),
new Element('span.size', {'text': 'Size'}),
new Element('span.age', {'text': 'Age'}),
new Element('span.score', {'text': 'Score'}),
new Element('span.provider', {'text': 'Provider'})
@ -288,9 +289,10 @@ var ReleaseAction = new Class({
} catch(e){}
new Element('div', {
'class': 'item ' + status.identifier
'class': 'item'
}).adopt(
new Element('span.name', {'text': self.get(release, 'name'), 'title': self.get(release, 'name')}),
new Element('span.status', {'text': status.identifier, 'class': 'release_status '+status.identifier}),
new Element('span.quality', {'text': quality.get('label')}),
new Element('span.size', {'text': (self.get(release, 'size') || 'unknown')}),
new Element('span.age', {'text': self.get(release, 'age')}),

11
couchpotato/core/plugins/quality/main.py

@ -18,7 +18,7 @@ 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, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts']},
{'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts']},
{'identifier': '720p', 'hd': True, 'size': (3500, 10000), 'label': '720P', 'width': 1280, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts', 'ts']},
{'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p'], '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', 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
@ -161,26 +161,25 @@ class QualityPlugin(Plugin):
for cur_file in files:
size = (os.path.getsize(cur_file) / 1024 / 1024) if os.path.isfile(cur_file) else 0
words = re.split('\W+', cur_file.lower())
safe_cur_file = toSafeString(cur_file)
for quality in self.all():
# Check tags
if quality['identifier'] in words:
log.debug('Found via identifier "%s" in %s' % (quality['identifier'], safe_cur_file))
log.debug('Found via identifier "%s" in %s' % (quality['identifier'], cur_file))
return self.setCache(hash, quality)
if list(set(quality.get('alternative', [])) & set(words)):
log.debug('Found %s via alt %s in %s' % (quality['identifier'], quality.get('alternative'), safe_cur_file))
log.debug('Found %s via alt %s in %s' % (quality['identifier'], quality.get('alternative'), cur_file))
return self.setCache(hash, quality)
for tag in quality.get('tags', []):
if isinstance(tag, tuple) and '.'.join(tag) in '.'.join(words):
log.debug('Found %s via tag %s in %s' % (quality['identifier'], quality.get('tags'), safe_cur_file))
log.debug('Found %s via tag %s in %s' % (quality['identifier'], quality.get('tags'), cur_file))
return self.setCache(hash, quality)
if list(set(quality.get('tags', [])) & set(words)):
log.debug('Found %s via tag %s in %s' % (quality['identifier'], quality.get('tags'), safe_cur_file))
log.debug('Found %s via tag %s in %s' % (quality['identifier'], quality.get('tags'), cur_file))
return self.setCache(hash, quality)
# Check on unreliable stuff

6
couchpotato/core/plugins/renamer/main.py

@ -328,7 +328,7 @@ class Renamer(Plugin):
try:
os.remove(src)
except:
log.error('Failed removing %s: %s', (src, traceback.format_exc()))
log.error('Failed removing %s: %s' % (src, traceback.format_exc()))
# Remove matching releases
for release in remove_releases:
@ -336,14 +336,14 @@ class Renamer(Plugin):
try:
db.delete(release)
except:
log.error('Failed removing %s: %s', (release.identifier, traceback.format_exc()))
log.error('Failed removing %s: %s' % (release.identifier, traceback.format_exc()))
if group['dirname'] and group['parentdir']:
try:
log.info('Deleting folder: %s' % group['parentdir'])
self.deleteEmptyFolder(group['parentdir'])
except:
log.error('Failed removing %s: %s', (group['parentdir'], traceback.format_exc()))
log.error('Failed removing %s: %s' % (group['parentdir'], traceback.format_exc()))
# Search for trailers etc
fireEventAsync('renamer.after', group)

7
couchpotato/core/plugins/scanner/main.py

@ -31,7 +31,7 @@ class Scanner(Plugin):
ignored_in_path = ['_unpack', '_failed_', '_unknown_', '_exists_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
extensions = {
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf'],
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts'],
'movie_extra': ['mds'],
'dvd': ['vts_*', 'vob'],
'nfo': ['nfo', 'txt', 'tag'],
@ -165,6 +165,9 @@ class Scanner(Plugin):
for file_path in files:
if not os.path.exists(file_path):
continue
# Remove ignored files
if self.isSampleFile(file_path):
leftovers.append(file_path)
@ -263,7 +266,7 @@ class Scanner(Plugin):
file_too_new = tryInt(time.time() - file_time)
break
if file_too_new and not Env.get('dev'):
if file_too_new:
log.info('Files seem to be still unpacking or just unpacked (created on %s), ignoring for now: %s' % (time.ctime(file_time), identifier))
continue

2
couchpotato/core/plugins/score/scores.py

@ -40,7 +40,7 @@ def nameScore(name, year):
# Contains preferred word
nzb_words = re.split('\W+', simplifyString(name))
preferred_words = Env.setting('preferred_words', section = 'searcher').split(',')
preferred_words = [x.strip() for x in Env.setting('preferred_words', section = 'searcher').split(',')]
for word in preferred_words:
if word.strip() and word.strip().lower() in nzb_words:
score = score + 100

25
couchpotato/core/plugins/searcher/main.py

@ -123,7 +123,11 @@ class Searcher(Plugin):
for nzb in sorted_results:
return self.download(data = nzb, movie = movie)
downloaded = self.download(data = nzb, movie = movie)
if downloaded:
return True
else:
break
else:
log.info('Better quality (%s) already available or snatched for %s' % (quality_type['quality']['label'], default_title))
fireEvent('movie.restatus', movie['id'])
@ -190,19 +194,26 @@ class Searcher(Plugin):
log.info('Wrong: Outside retention, age is %s, needs %s or lower: %s' % (nzb['age'], retention, nzb['name']))
return False
nzb_words = re.split('\W+', simplifyString(nzb['name']))
required_words = self.conf('required_words').split(',')
movie_name = simplifyString(nzb['name'])
nzb_words = re.split('\W+', movie_name)
required_words = [x.strip() for x in self.conf('required_words').split(',')]
if self.conf('required_words') and not list(set(nzb_words) & set(required_words)):
log.info("NZB doesn't contain any of the required words.")
return False
ignored_words = self.conf('ignored_words').split(',')
ignored_words = [x.strip() for x in self.conf('ignored_words').split(',')]
blacklisted = list(set(nzb_words) & set(ignored_words))
if self.conf('ignored_words') and blacklisted:
log.info("Wrong: '%s' blacklisted words: %s" % (nzb['name'], ", ".join(blacklisted)))
return False
pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs']
for p_tag in pron_tags:
if p_tag in movie_name:
log.info('Wrong: %s, probably pr0n' % (nzb['name']))
return False
#qualities = fireEvent('quality.all', single = True)
preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True)
@ -313,10 +324,10 @@ class Searcher(Plugin):
check_movie = fireEvent('scanner.name_year', check_name, single = True)
try:
check_words = re.split('\W+', check_movie.get('name', ''))
movie_words = re.split('\W+', simplifyString(movie_name))
check_words = filter(None, re.split('\W+', check_movie.get('name', '')))
movie_words = filter(None, re.split('\W+', simplifyString(movie_name)))
if len(list(set(check_words) - set(movie_words))) == 0:
if len(check_words) > 0 and len(movie_words) > 0 and len(list(set(check_words) - set(movie_words))) == 0:
return True
except:
pass

8
couchpotato/core/providers/base.py

@ -65,9 +65,13 @@ class YarrProvider(Provider):
def belongsTo(self, url, host = None):
try:
hostname = urlparse(url).hostname
download_url = host if host else self.urls['download']
if hostname in download_url:
if host and hostname in host:
return self
else:
for url_type in self.urls:
download_url = self.urls[url_type]
if hostname in download_url:
return self
except:
log.debug('Url % s doesn\'t belong to %s' % (url, self.getName()))

22
couchpotato/core/providers/metadata/xbmc/__init__.py

@ -20,19 +20,41 @@ config = [{
},
{
'name': 'meta_nfo',
'label': 'NFO',
'default': True,
'type': 'bool',
},
{
'name': 'meta_nfo_name',
'label': 'NFO filename',
'default': '%s.nfo',
'advanced': True,
'description': '<strong>%s</strong> is the rootname of the movie. For example "/path/to/movie cd1.mkv" will be "/path/to/movie"'
},
{
'name': 'meta_fanart',
'label': 'Fanart',
'default': True,
'type': 'bool',
},
{
'name': 'meta_fanart_name',
'label': 'Fanart filename',
'default': '%s-fanart.jpg',
'advanced': True,
},
{
'name': 'meta_thumbnail',
'label': 'Thumbnail',
'default': True,
'type': 'bool',
},
{
'name': 'meta_thumbnail_name',
'label': 'Thumbnail filename',
'default': '%s.tbn',
'advanced': True,
},
],
},
],

6
couchpotato/core/providers/metadata/xbmc/main.py

@ -15,13 +15,13 @@ class XBMC(MetaDataBase):
return os.path.join(data['destination_dir'], data['filename'])
def getFanartName(self, root):
return '%s-fanart.jpg' % root
return self.conf('meta_fanart_name') % root
def getThumbnailName(self, root):
return '%s.tbn' % root
return self.conf('meta_thumbnail_name') % root
def getNfoName(self, root):
return '%s.nfo' % root
return self.conf('meta_nfo_name') % root
def getNfo(self, movie_info = {}, data = {}):
nfoxml = Element('movie')

1
couchpotato/core/providers/movie/couchpotatoapi/main.py

@ -27,6 +27,7 @@ class CouchPotatoApi(MovieProvider):
def getReleaseDate(self, identifier = None):
if identifier is None: return {}
try:
headers = {'X-CP-Version': fireEvent('app.version', single = True)}
data = self.urlopen((self.api_url % ('eta')) + (identifier + '/'), headers = headers)

5
couchpotato/core/providers/movie/imdbapi/main.py

@ -27,7 +27,7 @@ class IMDBAPI(MovieProvider):
name_year = fireEvent('scanner.name_year', q, single = True)
if not name_year.get('name'):
if not q or not name_year.get('name'):
return []
cache_key = 'imdbapi.cache.%s' % q
@ -45,6 +45,9 @@ class IMDBAPI(MovieProvider):
def getInfo(self, identifier = None):
if not identifier:
return {}
cache_key = 'imdbapi.cache.%s' % identifier
cached = self.getCache(cache_key, self.urls['info'] % identifier)

2
couchpotato/core/providers/nzb/newzbin/main.py

@ -89,7 +89,7 @@ class Newzbin(NZBProvider, RSS):
title = self.getTextElement(nzb, "title")
if 'error' in title.lower(): continue
REPORT_NS = 'http://www.newzbin.com/DTD/2007/feeds/report/';
REPORT_NS = 'http://www.newzbin2.es/DTD/2007/feeds/report/';
# Add attributes to name
try:

6
couchpotato/core/providers/nzb/newznab/main.py

@ -157,9 +157,9 @@ class Newznab(NZBProvider, RSS):
def getHosts(self):
uses = str(self.conf('use')).split(',')
hosts = self.conf('host').split(',')
api_keys = self.conf('api_key').split(',')
uses = [x.strip() for x in str(self.conf('use')).split(',')]
hosts = [x.strip() for x in self.conf('host').split(',')]
api_keys = [x.strip() for x in self.conf('api_key').split(',')]
list = []
for nr in range(len(hosts)):

8
couchpotato/core/providers/nzb/nzbs/main.py

@ -13,10 +13,10 @@ log = CPLog(__name__)
class Nzbs(NZBProvider, RSS):
urls = {
'download': 'http://nzbs.org/index.php?action=getnzb&nzbid=%s%s',
'nfo': 'http://nzbs.org/index.php?action=view&nzbid=%s&nfo=1',
'detail': 'http://nzbs.org/index.php?action=view&nzbid=%s',
'api': 'http://nzbs.org/rss.php',
'download': 'https://nzbs.org/index.php?action=getnzb&nzbid=%s%s',
'nfo': 'https://nzbs.org/index.php?action=view&nzbid=%s&nfo=1',
'detail': 'https://nzbs.org/index.php?action=view&nzbid=%s',
'api': 'https://nzbs.org/rss.php',
}
cat_ids = [

3
couchpotato/core/providers/torrent/kickasstorrents/main.py

@ -17,6 +17,7 @@ class KickAssTorrents(TorrentProvider):
'test': 'http://www.kat.ph/',
'detail': 'http://www.kat.ph/%s-t%s.html',
'search': 'http://www.kat.ph/%s-i%s/',
'download': 'http://torcache.net/',
}
cat_ids = [
@ -127,7 +128,7 @@ class KickAssTorrents(TorrentProvider):
return tryInt(age)
def download(self, url = '', nzb_id = ''):
compressed_data = super(KickAssTorrents, self).download(url = url, nzb_id = nzb_id)
compressed_data = self.urlopen(url = url, headers = {'Referer': 'http://kat.ph/'})
compressedstream = StringIO.StringIO(compressed_data)
gzipper = gzip.GzipFile(fileobj = compressedstream)

6
couchpotato/templates/api.html

@ -18,6 +18,12 @@
<br />
You can also use the API over another domain using JSONP, the callback function should be in 'callback_func'
<pre><a href="{{ fireEvent('app.api_url', single = True)|safe }}/updater.info/?callback_func=myfunction">{{ fireEvent('app.api_url', single = True)|safe }}/updater.info/?callback_func=myfunction</a></pre>
<br />
<br />
Get the API key:
<pre><a href="/getkey/?p=md5(password)&amp;u=md5(username)">/getkey/?p=md5(password)&amp;u=md5(username)</a></pre>
Will return {"api_key": "XXXXXXXXXX", "success": true}. When username or password is empty you don't need to md5 it.
<br />
</div>
{% for route in routes %}

Loading…
Cancel
Save