Compare commits
205
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51e747049d | ||
|
|
0582f7d694 | ||
|
|
fa7cac7538 | ||
|
|
ec857a9b3d | ||
|
|
4d32b0b16d | ||
|
|
ca08287cff | ||
|
|
36fee69843 | ||
|
|
c5cae5ab9b | ||
|
|
9bd5688fb9 | ||
|
|
1993c2b6cb | ||
|
|
acc8ed2092 | ||
|
|
7b4924dd7a | ||
|
|
3a2861f72a | ||
|
|
4779265b43 | ||
|
|
f8a46ebe6d | ||
|
|
383ec7e6f5 | ||
|
|
dd9118292d | ||
|
|
4d0f8eb4ac | ||
|
|
637b21cc68 | ||
|
|
da429f0cb8 | ||
|
|
41c2845328 | ||
|
|
c2453bb070 | ||
|
|
a3a2c8da8e | ||
|
|
a1d4bab793 | ||
|
|
d314a9b5b3 | ||
|
|
9a60f6001a | ||
|
|
96a39dbf60 | ||
|
|
015675750c | ||
|
|
bf4dc62f54 | ||
|
|
c2382ade05 | ||
|
|
2f65545086 | ||
|
|
3aea2cd968 | ||
|
|
f30cb9185c | ||
|
|
615468e8e6 | ||
|
|
0cbee01024 | ||
|
|
c29cb39797 | ||
|
|
580ff38136 | ||
|
|
6b8bca5491 | ||
|
|
e92b5d95ca | ||
|
|
611a32d110 | ||
|
|
74e4b015a9 | ||
|
|
1e0267cdb5 | ||
|
|
041a206fb4 | ||
|
|
12a4d6a995 | ||
|
|
b14a6c1e63 | ||
|
|
7fa08ef9b6 | ||
|
|
9a314cfbc4 | ||
|
|
5941d0bf77 | ||
|
|
d326c1c25c | ||
|
|
7e6234298d | ||
|
|
5cf4b8b4d3 | ||
|
|
6e56072250 | ||
|
|
917c5552a4 | ||
|
|
73c5b90232 | ||
|
|
fd53ba0637 | ||
|
|
0ef3906b3d | ||
|
|
5ab0d7a97b | ||
|
|
dbbbbb2f84 | ||
|
|
1bfe948a45 | ||
|
|
0d2dcff7f0 | ||
|
|
d4da206f93 | ||
|
|
439cda8b63 | ||
|
|
bbe8362b08 | ||
|
|
985a168724 | ||
|
|
5e6aea97f7 | ||
|
|
6c7c4c7aba | ||
|
|
e2f59f5ff4 | ||
|
|
b225980ce7 | ||
|
|
b8e86b378f | ||
|
|
031a186d71 | ||
|
|
3c04eed218 | ||
|
|
17e01689d9 | ||
|
|
173c6194ed | ||
|
|
95c2e992b0 | ||
|
|
4bffb299af | ||
|
|
a2c4119508 | ||
|
|
4e9472f8ee | ||
|
|
f7911fe9f3 | ||
|
|
8ffa6a8392 | ||
|
|
382d49f895 | ||
|
|
570b79a67e | ||
|
|
e7aafc406f | ||
|
|
2dcc1e096e | ||
|
|
9f0746a668 | ||
|
|
d9c437bd7f | ||
|
|
7079647f87 | ||
|
|
65570ba479 | ||
|
|
a57ba9026d | ||
|
|
63246256ee | ||
|
|
1ac0dc3bbf | ||
|
|
bcd23ad10c | ||
|
|
342d31b48a | ||
|
|
ea7904ed9a | ||
|
|
ca37c2f018 | ||
|
|
5aa2146614 | ||
|
|
0fd49a2c67 | ||
|
|
b680d84cba | ||
|
|
898e6f487d | ||
|
|
96472a9a8f | ||
|
|
27252561e2 | ||
|
|
24b341005e | ||
|
|
749cf550ec | ||
|
|
650177803b | ||
|
|
bb7b4cbbed | ||
|
|
6618c3927c | ||
|
|
003db92c9b | ||
|
|
b2b396bf17 | ||
|
|
f1a1db8d5b | ||
|
|
f515cd2477 | ||
|
|
65bb1bec27 | ||
|
|
cc84532824 | ||
|
|
5530fbf792 | ||
|
|
5658a85f61 | ||
|
|
0c5206f01b | ||
|
|
4bffce637e | ||
|
|
9f2941a45c | ||
|
|
f452106bfc | ||
|
|
da3055be30 | ||
|
|
f9b65e7216 | ||
|
|
07e2c56095 | ||
|
|
9a6cfe3a21 | ||
|
|
802338a934 | ||
|
|
f0a3358561 | ||
|
|
1c4c69211b | ||
|
|
c9e732651f | ||
|
|
7849e7170d | ||
|
|
087894eb4e | ||
|
|
4b58b40226 | ||
|
|
77d57f5a09 | ||
|
|
618845a021 | ||
|
|
3aabcbf8f1 | ||
|
|
929c6fe3f9 | ||
|
|
c852949591 | ||
|
|
e36c8ec3ab | ||
|
|
afea12c7c0 | ||
|
|
c29a8b47d6 | ||
|
|
fdd0826b4f | ||
|
|
81b7ebaf51 | ||
|
|
eafc3db74d | ||
|
|
3464435a5c | ||
|
|
9f19902221 | ||
|
|
2ed72c9098 | ||
|
|
723f720280 | ||
|
|
daaa2154e5 | ||
|
|
95c5db2d17 | ||
|
|
e53a9ed30a | ||
|
|
2b49a4b5d6 | ||
|
|
4224a25e54 | ||
|
|
3635da1f59 | ||
|
|
71cca6b87f | ||
|
|
68c0496f8e | ||
|
|
6dc3c8d69d | ||
|
|
3ecc826629 | ||
|
|
b03012e4aa | ||
|
|
5a1f05df8e | ||
|
|
62a5909856 | ||
|
|
813c078db0 | ||
|
|
904d1ea4f7 | ||
|
|
20b773bc3b | ||
|
|
be56b96bd0 | ||
|
|
655e847aeb | ||
|
|
f3fd0afb42 | ||
|
|
3782ad7f98 | ||
|
|
6f5031fa7c | ||
|
|
93604a45e5 | ||
|
|
28f4169e44 | ||
|
|
2361057e4c | ||
|
|
5caa40bd81 | ||
|
|
a22bd4abd4 | ||
|
|
a32ba7a763 | ||
|
|
5fe645cc11 | ||
|
|
f333d85907 | ||
|
|
3ec2df5780 | ||
|
|
25f1b8c7a7 | ||
|
|
e71da1f14d | ||
|
|
212d64143c | ||
|
|
51f9b5c673 | ||
|
|
2215c000b7 | ||
|
|
14797249ff | ||
|
|
49e2607f5d | ||
|
|
938b14ba18 | ||
|
|
c893d5bbb8 | ||
|
|
c4adab69cb | ||
|
|
2c9af74f7f | ||
|
|
7eb15c1a53 | ||
|
|
a02257a906 | ||
|
|
667075a006 | ||
|
|
b0f6f9b2ea | ||
|
|
c0900cfe94 | ||
|
|
24a4810919 | ||
|
|
70b15a5696 | ||
|
|
32fe3796e4 | ||
|
|
359d1aaafa | ||
|
|
fb5d336351 | ||
|
|
eb30dff986 | ||
|
|
9312336962 | ||
|
|
ade4338ea6 | ||
|
|
55b20324c0 | ||
|
|
c0fb28301d | ||
|
|
f9c2503f81 | ||
|
|
5b4cdf05b1 | ||
|
|
6f25a6bdfd | ||
|
|
23427e95f7 | ||
|
|
90a09e573b | ||
|
|
e1d7440b9d |
+1
-1
@@ -100,7 +100,7 @@ class Loader(object):
|
|||||||
logging.shutdown()
|
logging.shutdown()
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
args = [sys.executable] + [os.path.join(base_path, __file__)] + sys.argv[1:]
|
args = [sys.executable] + [os.path.join(base_path, os.path.basename(__file__))] + sys.argv[1:]
|
||||||
subprocess.Popen(args)
|
subprocess.Popen(args)
|
||||||
except:
|
except:
|
||||||
self.log.critical(traceback.format_exc())
|
self.log.critical(traceback.format_exc())
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
from esky.util import appdir_from_executable #@UnresolvedImport
|
from esky.util import appdir_from_executable #@UnresolvedImport
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
from version import VERSION
|
||||||
from wx.lib.softwareupdate import SoftwareUpdate
|
from wx.lib.softwareupdate import SoftwareUpdate
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -165,7 +166,7 @@ class CouchPotatoApp(wx.App, SoftwareUpdate):
|
|||||||
def OnInit(self):
|
def OnInit(self):
|
||||||
|
|
||||||
# Updater
|
# Updater
|
||||||
base_url = 'http://couchpota.to/updates/'
|
base_url = 'http://couchpota.to/updates/%s/' % VERSION
|
||||||
self.InitUpdates(base_url, base_url + 'changelog.html',
|
self.InitUpdates(base_url, base_url + 'changelog.html',
|
||||||
icon = wx.Icon('icon.png'))
|
icon = wx.Icon('icon.png'))
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ Windows, see [the CP forum](http://couchpota.to/forum/showthread.php?tid=14) for
|
|||||||
* Open up `Git Bash` (or CMD) and go to the folder you want to install CP. Something like Program Files.
|
* Open up `Git Bash` (or CMD) and go to the folder you want to install CP. Something like Program Files.
|
||||||
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`.
|
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`.
|
||||||
* You can now start CP via `CouchPotatoServer\CouchPotato.py` to start
|
* You can now start CP via `CouchPotatoServer\CouchPotato.py` to start
|
||||||
|
* Your browser should open up, but if it doesn't go to: `http://localhost:5050/`
|
||||||
|
|
||||||
OSx:
|
OSx:
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ OSx:
|
|||||||
* Go to your App folder `cd /Applications`
|
* Go to your App folder `cd /Applications`
|
||||||
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`
|
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`
|
||||||
* Then do `python CouchPotatoServer/CouchPotato.py`
|
* Then do `python CouchPotatoServer/CouchPotato.py`
|
||||||
|
* Your browser should open up, but if it doesn't go to: `http://localhost:5050/`
|
||||||
|
|
||||||
Linux (ubuntu / debian):
|
Linux (ubuntu / debian):
|
||||||
|
|
||||||
@@ -37,3 +39,4 @@ Linux (ubuntu / debian):
|
|||||||
* Change the paths inside the init script. `sudo nano /etc/init.d/couchpotato`
|
* Change the paths inside the init script. `sudo nano /etc/init.d/couchpotato`
|
||||||
* Make it executable. `sudo chmod +x /etc/init.d/couchpotato`
|
* Make it executable. `sudo chmod +x /etc/init.d/couchpotato`
|
||||||
* Add it to defaults. `sudo update-rc.d couchpotato defaults`
|
* Add it to defaults. `sudo update-rc.d couchpotato defaults`
|
||||||
|
* Open your browser and go to: `http://localhost:5050/`
|
||||||
|
|||||||
+5
-2
@@ -1,6 +1,5 @@
|
|||||||
from flask.blueprints import Blueprint
|
from flask.blueprints import Blueprint
|
||||||
from flask.helpers import url_for
|
from flask.helpers import url_for
|
||||||
from tornado.ioloop import IOLoop
|
|
||||||
from tornado.web import RequestHandler, asynchronous
|
from tornado.web import RequestHandler, asynchronous
|
||||||
from werkzeug.utils import redirect
|
from werkzeug.utils import redirect
|
||||||
|
|
||||||
@@ -11,7 +10,11 @@ api_nonblock = {}
|
|||||||
|
|
||||||
|
|
||||||
class NonBlockHandler(RequestHandler):
|
class NonBlockHandler(RequestHandler):
|
||||||
stoppers = []
|
|
||||||
|
def __init__(self, application, request, **kwargs):
|
||||||
|
cls = NonBlockHandler
|
||||||
|
cls.stoppers = []
|
||||||
|
super(NonBlockHandler, self).__init__(application, request, **kwargs)
|
||||||
|
|
||||||
@asynchronous
|
@asynchronous
|
||||||
def get(self, route):
|
def get(self, route):
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from tornado.ioloop import IOLoop
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
import signal
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import webbrowser
|
import webbrowser
|
||||||
@@ -51,6 +52,9 @@ class Core(Plugin):
|
|||||||
addEvent('setting.save.core.password', self.md5Password)
|
addEvent('setting.save.core.password', self.md5Password)
|
||||||
addEvent('setting.save.core.api_key', self.checkApikey)
|
addEvent('setting.save.core.api_key', self.checkApikey)
|
||||||
|
|
||||||
|
# Make sure we can close-down with ctrl+c properly
|
||||||
|
if not Env.get('desktop'):
|
||||||
|
self.signalHandler()
|
||||||
|
|
||||||
def md5Password(self, value):
|
def md5Password(self, value):
|
||||||
return md5(value.encode(Env.get('encoding'))) if value else ''
|
return md5(value.encode(Env.get('encoding'))) if value else ''
|
||||||
@@ -66,7 +70,7 @@ class Core(Plugin):
|
|||||||
|
|
||||||
def available(self):
|
def available(self):
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'succes': True
|
'success': True
|
||||||
})
|
})
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
@@ -98,7 +102,7 @@ class Core(Plugin):
|
|||||||
|
|
||||||
self.shutdown_started = True
|
self.shutdown_started = True
|
||||||
|
|
||||||
fireEvent('app.shutdown')
|
fireEvent('app.do_shutdown')
|
||||||
log.debug('Every plugin got shutdown event')
|
log.debug('Every plugin got shutdown event')
|
||||||
|
|
||||||
loop = True
|
loop = True
|
||||||
@@ -170,3 +174,10 @@ class Core(Plugin):
|
|||||||
return jsonified({
|
return jsonified({
|
||||||
'version': self.version()
|
'version': self.version()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def signalHandler(self):
|
||||||
|
|
||||||
|
def signal_handler(signal, frame):
|
||||||
|
fireEvent('app.do_shutdown')
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
|||||||
@@ -8,11 +8,9 @@ from couchpotato.environment import Env
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
from git.repository import LocalRepository
|
from git.repository import LocalRepository
|
||||||
import atexit
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
|
||||||
import tarfile
|
import tarfile
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|||||||
@@ -90,17 +90,18 @@ var UpdaterBase = new Class({
|
|||||||
doUpdate: function(){
|
doUpdate: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
App.blockPage('Please wait while CouchPotato is being updated with more awesome stuff.', 'Updating');
|
||||||
Api.request('updater.update', {
|
Api.request('updater.update', {
|
||||||
'onComplete': function(json){
|
'onComplete': function(json){
|
||||||
if(json.success){
|
if(json.success)
|
||||||
self.updating();
|
self.updating();
|
||||||
}
|
else
|
||||||
|
App.unBlockPage()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updating: function(){
|
updating: function(){
|
||||||
App.blockPage('Please wait while CouchPotato is being updated with more awesome stuff.', 'Updating');
|
|
||||||
App.checkAvailable.delay(500, App, [1000, function(){
|
App.checkAvailable.delay(500, App, [1000, function(){
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}]);
|
}]);
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
from base64 import b32decode, b16encode
|
from base64 import b32decode, b16encode
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.providers.base import Provider
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Downloader(Plugin):
|
class Downloader(Provider):
|
||||||
|
|
||||||
type = []
|
type = []
|
||||||
|
http_time_between_calls = 0
|
||||||
|
|
||||||
torrent_sources = [
|
torrent_sources = [
|
||||||
'http://torrage.com/torrent/%s.torrent',
|
'http://torrage.com/torrent/%s.torrent',
|
||||||
'http://torrage.ws/torrent/%s.torrent',
|
|
||||||
'http://torcache.net/torrent/%s.torrent',
|
'http://torcache.net/torrent/%s.torrent',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
torrent_trackers = [
|
||||||
|
'http://tracker.publicbt.com/announce',
|
||||||
|
'udp://tracker.istole.it:80/announce',
|
||||||
|
'udp://fr33domtracker.h33t.com:3310/announce',
|
||||||
|
'http://tracker.istole.it/announce',
|
||||||
|
'http://tracker.ccc.de/announce',
|
||||||
|
'udp://tracker.publicbt.com:80/announce',
|
||||||
|
'udp://tracker.ccc.de:80/announce',
|
||||||
|
'http://exodus.desync.com/announce',
|
||||||
|
'http://exodus.desync.com:6969/announce',
|
||||||
|
'http://tracker.publichd.eu/announce',
|
||||||
|
'http://tracker.openbittorrent.com/announce',
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('download', self.download)
|
addEvent('download', self.download)
|
||||||
addEvent('download.status', self.getAllDownloadStatus)
|
addEvent('download.status', self.getAllDownloadStatus)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from .main import Blackhole
|
from .main import Blackhole
|
||||||
|
from couchpotato.core.helpers.variable import getDownloadDir
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
return Blackhole()
|
return Blackhole()
|
||||||
@@ -16,7 +17,7 @@ config = [{
|
|||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
'default': 0,
|
'default': True,
|
||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
'radio_group': 'nzb,torrent',
|
'radio_group': 'nzb,torrent',
|
||||||
},
|
},
|
||||||
@@ -24,6 +25,7 @@ config = [{
|
|||||||
'name': 'directory',
|
'name': 'directory',
|
||||||
'type': 'directory',
|
'type': 'directory',
|
||||||
'description': 'Directory where the .nzb (or .torrent) file is saved to.',
|
'description': 'Directory where the .nzb (or .torrent) file is saved to.',
|
||||||
|
'default': getDownloadDir()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'use_for',
|
'name': 'use_for',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ config = [{
|
|||||||
'tab': 'downloaders',
|
'tab': 'downloaders',
|
||||||
'name': 'nzbget',
|
'name': 'nzbget',
|
||||||
'label': 'NZBGet',
|
'label': 'NZBGet',
|
||||||
'description': 'Send NZBs to your NZBGet installation.',
|
'description': 'Use <a href="http://nzbget.sourceforge.net/Main_Page" target="_blank">NZBGet</a> to download NZBs.',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
@@ -25,6 +25,7 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'password',
|
'name': 'password',
|
||||||
|
'type': 'password',
|
||||||
'description': 'Default NZBGet password is <i>tegbzn6789</i>',
|
'description': 'Default NZBGet password is <i>tegbzn6789</i>',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
from .main import NZBVortex
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return NZBVortex()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'nzbvortex',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'downloaders',
|
||||||
|
'name': 'nzbvortex',
|
||||||
|
'label': 'NZBVortex',
|
||||||
|
'description': 'Use <a href="http://www.nzbvortex.com/landing/" target="_blank">NZBVortex</a> to download NZBs.',
|
||||||
|
'wizard': True,
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'enabled',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'enabler',
|
||||||
|
'radio_group': 'nzb',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'host',
|
||||||
|
'default': 'https://localhost:4321',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'api_key',
|
||||||
|
'label': 'Api Key',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'manual',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool',
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'delete_failed',
|
||||||
|
'default': True,
|
||||||
|
'type': 'bool',
|
||||||
|
'description': 'Delete a release after the download has failed.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
from base64 import b64encode
|
||||||
|
from couchpotato.core.downloaders.base import Downloader
|
||||||
|
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
||||||
|
from couchpotato.core.helpers.variable import cleanHost
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from urllib2 import URLError
|
||||||
|
from uuid import uuid4
|
||||||
|
import hashlib
|
||||||
|
import httplib
|
||||||
|
import json
|
||||||
|
import socket
|
||||||
|
import ssl
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
class NZBVortex(Downloader):
|
||||||
|
|
||||||
|
type = ['nzb']
|
||||||
|
api_level = None
|
||||||
|
session_id = None
|
||||||
|
|
||||||
|
def download(self, data = {}, movie = {}, manual = False, filedata = None):
|
||||||
|
|
||||||
|
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')) or not self.getApiLevel():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Send the nzb
|
||||||
|
try:
|
||||||
|
nzb_filename = self.createFileName(data, filedata, movie)
|
||||||
|
self.call('nzb/add', params = {'file': (ss(nzb_filename), filedata)}, multipart = True)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
log.error('Something went wrong sending the NZB file: %s', traceback.format_exc())
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getAllDownloadStatus(self):
|
||||||
|
|
||||||
|
if self.isDisabled(manual = True):
|
||||||
|
return False
|
||||||
|
|
||||||
|
raw_statuses = self.call('nzb')
|
||||||
|
|
||||||
|
statuses = []
|
||||||
|
for item in raw_statuses.get('nzbs', []):
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
status = 'busy'
|
||||||
|
if item['state'] == 20:
|
||||||
|
status = 'completed'
|
||||||
|
elif item['state'] in [21, 22, 24]:
|
||||||
|
status = 'failed'
|
||||||
|
|
||||||
|
statuses.append({
|
||||||
|
'id': item['id'],
|
||||||
|
'name': item['uiTitle'],
|
||||||
|
'status': status,
|
||||||
|
'original_status': item['state'],
|
||||||
|
'timeleft':-1,
|
||||||
|
})
|
||||||
|
|
||||||
|
return statuses
|
||||||
|
|
||||||
|
def removeFailed(self, item):
|
||||||
|
|
||||||
|
if not self.conf('delete_failed', default = True):
|
||||||
|
return False
|
||||||
|
|
||||||
|
log.info('%s failed downloading, deleting...', item['name'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.call('nzb/%s/cancel' % item['id'])
|
||||||
|
except:
|
||||||
|
log.error('Failed deleting: %s', traceback.format_exc(0))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
|
||||||
|
nonce = self.call('auth/nonce', auth = False).get('authNonce')
|
||||||
|
cnonce = uuid4().hex
|
||||||
|
hashed = b64encode(hashlib.sha256('%s:%s:%s' % (nonce, cnonce, self.conf('api_key'))).digest())
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'nonce': nonce,
|
||||||
|
'cnonce': cnonce,
|
||||||
|
'hash': hashed
|
||||||
|
}
|
||||||
|
|
||||||
|
login_data = self.call('auth/login', parameters = params, auth = False)
|
||||||
|
|
||||||
|
# Save for later
|
||||||
|
if login_data.get('loginResult') == 'successful':
|
||||||
|
self.session_id = login_data.get('sessionID')
|
||||||
|
return True
|
||||||
|
|
||||||
|
log.error('Login failed, please check you api-key')
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def call(self, call, parameters = {}, repeat = False, auth = True, *args, **kwargs):
|
||||||
|
|
||||||
|
# Login first
|
||||||
|
if not self.session_id and auth:
|
||||||
|
self.login()
|
||||||
|
|
||||||
|
# Always add session id to request
|
||||||
|
if self.session_id:
|
||||||
|
parameters['sessionid'] = self.session_id
|
||||||
|
|
||||||
|
params = tryUrlencode(parameters)
|
||||||
|
|
||||||
|
url = cleanHost(self.conf('host')) + 'api/' + call
|
||||||
|
url_opener = urllib2.build_opener(HTTPSHandler())
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = self.urlopen('%s?%s' % (url, params), opener = url_opener, *args, **kwargs)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
return json.loads(data)
|
||||||
|
except URLError, e:
|
||||||
|
if hasattr(e, 'code') and e.code == 403:
|
||||||
|
# Try login and do again
|
||||||
|
if not repeat:
|
||||||
|
self.login()
|
||||||
|
return self.call(call, parameters = parameters, repeat = True, *args, **kwargs)
|
||||||
|
|
||||||
|
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
except:
|
||||||
|
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def getApiLevel(self):
|
||||||
|
|
||||||
|
if not self.api_level:
|
||||||
|
|
||||||
|
url = cleanHost(self.conf('host')) + 'api/app/apilevel'
|
||||||
|
url_opener = urllib2.build_opener(HTTPSHandler())
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = self.urlopen(url, opener = url_opener, show_error = False)
|
||||||
|
self.api_level = float(json.loads(data).get('apilevel'))
|
||||||
|
except URLError, e:
|
||||||
|
if hasattr(e, 'code') and e.code == 403:
|
||||||
|
log.error('This version of NZBVortex isn\'t supported. Please update to 2.8.6 or higher')
|
||||||
|
else:
|
||||||
|
log.error('NZBVortex doesn\'t seem to be running or maybe the remote option isn\'t enabled yet: %s', traceback.format_exc(1))
|
||||||
|
|
||||||
|
return self.api_level
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPSConnection(httplib.HTTPSConnection):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
httplib.HTTPSConnection.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
sock = socket.create_connection((self.host, self.port), self.timeout)
|
||||||
|
if sys.version_info < (2, 6, 7):
|
||||||
|
if hasattr(self, '_tunnel_host'):
|
||||||
|
self.sock = sock
|
||||||
|
self._tunnel()
|
||||||
|
else:
|
||||||
|
if self._tunnel_host:
|
||||||
|
self.sock = sock
|
||||||
|
self._tunnel()
|
||||||
|
|
||||||
|
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version = ssl.PROTOCOL_TLSv1)
|
||||||
|
|
||||||
|
class HTTPSHandler(urllib2.HTTPSHandler):
|
||||||
|
def https_open(self, req):
|
||||||
|
return self.do_open(HTTPSConnection, req)
|
||||||
@@ -11,7 +11,7 @@ config = [{
|
|||||||
'tab': 'downloaders',
|
'tab': 'downloaders',
|
||||||
'name': 'pneumatic',
|
'name': 'pneumatic',
|
||||||
'label': 'Pneumatic',
|
'label': 'Pneumatic',
|
||||||
'description': 'Download the .strm file to a specific folder.',
|
'description': 'Use <a href="http://forum.xbmc.org/showthread.php?tid=97657" target="_blank">Pneumatic</a> to download .strm files.',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ config = [{
|
|||||||
'tab': 'downloaders',
|
'tab': 'downloaders',
|
||||||
'name': 'sabnzbd',
|
'name': 'sabnzbd',
|
||||||
'label': 'Sabnzbd',
|
'label': 'Sabnzbd',
|
||||||
'description': 'Send NZBs to your Sabnzbd installation.',
|
'description': 'Use <a href="http://sabnzbd.org/" target="_blank">SABnzbd</a> to download NZBs.',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from couchpotato.core.downloaders.base import Downloader
|
from couchpotato.core.downloaders.base import Downloader
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
||||||
from couchpotato.core.helpers.variable import cleanHost, mergeDicts
|
from couchpotato.core.helpers.variable import cleanHost, mergeDicts
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from urllib2 import URLError
|
from urllib2 import URLError
|
||||||
@@ -28,7 +28,7 @@ class Sabnzbd(Downloader):
|
|||||||
|
|
||||||
if filedata:
|
if filedata:
|
||||||
if len(filedata) < 50:
|
if len(filedata) < 50:
|
||||||
log.error('No proper nzb available!')
|
log.error('No proper nzb available: %s', (filedata))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If it's a .rar, it adds the .rar extension, otherwise it stays .nzb
|
# If it's a .rar, it adds the .rar extension, otherwise it stays .nzb
|
||||||
@@ -41,7 +41,7 @@ class Sabnzbd(Downloader):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if params.get('mode') is 'addfile':
|
if params.get('mode') is 'addfile':
|
||||||
sab = self.urlopen(url, timeout = 60, params = {'nzbfile': (nzb_filename, filedata)}, multipart = True, show_error = False)
|
sab = self.urlopen(url, timeout = 60, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True, show_error = False)
|
||||||
else:
|
else:
|
||||||
sab = self.urlopen(url, timeout = 60, show_error = False)
|
sab = self.urlopen(url, timeout = 60, show_error = False)
|
||||||
except URLError:
|
except URLError:
|
||||||
@@ -65,7 +65,7 @@ class Sabnzbd(Downloader):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def getAllDownloadStatus(self):
|
def getAllDownloadStatus(self):
|
||||||
if self.isDisabled(manual = False):
|
if self.isDisabled(manual = True):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
log.debug('Checking SABnzbd download status.')
|
log.debug('Checking SABnzbd download status.')
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
from .main import Synology
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Synology()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'synology',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'downloaders',
|
||||||
|
'name': 'synology',
|
||||||
|
'label': 'Synology',
|
||||||
|
'description': 'Use <a href="http://www.synology.com/dsm/home_home_applications_download_station.php" target="_blank">Synology Download Station</a> to download.',
|
||||||
|
'wizard': True,
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'enabled',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'enabler',
|
||||||
|
'radio_group': 'torrent',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'host',
|
||||||
|
'default': 'localhost:5000',
|
||||||
|
'description': 'Hostname with port. Usually <strong>localhost:5000</strong>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'username',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'password',
|
||||||
|
'type': 'password',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'manual',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'bool',
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
from couchpotato.core.downloaders.base import Downloader
|
||||||
|
from couchpotato.core.helpers.encoding import isInt
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
import httplib
|
||||||
|
import json
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
class Synology(Downloader):
|
||||||
|
|
||||||
|
type = ['torrent_magnet']
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
def download(self, data, movie, manual = False, filedata = None):
|
||||||
|
|
||||||
|
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
||||||
|
return
|
||||||
|
|
||||||
|
log.error('Sending "%s" (%s) to Synology.', (data.get('name'), data.get('type')))
|
||||||
|
|
||||||
|
# Load host from config and split out port.
|
||||||
|
host = self.conf('host').split(':')
|
||||||
|
if not isInt(host[1]):
|
||||||
|
log.error('Config properties are not filled in correctly, port is missing.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
if data.get('type') == 'torrent':
|
||||||
|
log.error('Can\'t add binary torrent file')
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Send request to Transmission
|
||||||
|
srpc = SynologyRPC(host[0], host[1], self.conf('username'), self.conf('password'))
|
||||||
|
remote_torrent = srpc.add_torrent_uri(data.get('url'))
|
||||||
|
log.info('Response: %s', remote_torrent)
|
||||||
|
return remote_torrent['success']
|
||||||
|
except Exception, err:
|
||||||
|
log.error('Exception while adding torrent: %s', err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class SynologyRPC(object):
|
||||||
|
|
||||||
|
'''SynologyRPC lite library'''
|
||||||
|
|
||||||
|
def __init__(self, host = 'localhost', port = 5000, username = None, password = None):
|
||||||
|
|
||||||
|
super(SynologyRPC, self).__init__()
|
||||||
|
|
||||||
|
self.download_url = 'http://%s:%s/webapi/DownloadStation/task.cgi' % (host, port)
|
||||||
|
self.auth_url = 'http://%s:%s/webapi/auth.cgi' % (host, port)
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.session_name = 'DownloadStation'
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
if self.username and self.password:
|
||||||
|
args = {'api': 'SYNO.API.Auth', 'account': self.username, 'passwd': self.password, 'version': 2,
|
||||||
|
'method': 'login', 'session': self.session_name, 'format': 'sid'}
|
||||||
|
response = self._req(self.auth_url, args)
|
||||||
|
if response['success'] == True:
|
||||||
|
self.sid = response['data']['sid']
|
||||||
|
log.debug('Sid=%s', self.sid)
|
||||||
|
return response
|
||||||
|
elif self.username or self.password:
|
||||||
|
log.error('User or password missing, not using authentication.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _logout(self):
|
||||||
|
args = {'api':'SYNO.API.Auth', 'version':1, 'method':'logout', 'session':self.session_name, '_sid':self.sid}
|
||||||
|
return self._req(self.auth_url, args)
|
||||||
|
|
||||||
|
def _req(self, url, args):
|
||||||
|
req_url = url + '?' + urllib.urlencode(args)
|
||||||
|
try:
|
||||||
|
req_open = urllib2.urlopen(req_url)
|
||||||
|
response = json.loads(req_open.read())
|
||||||
|
if response['success'] == True:
|
||||||
|
log.info('Synology action successfull')
|
||||||
|
return response
|
||||||
|
except httplib.InvalidURL, err:
|
||||||
|
log.error('Invalid Transmission host, check your config %s', err)
|
||||||
|
return False
|
||||||
|
except urllib2.HTTPError, err:
|
||||||
|
log.error('SynologyRPC HTTPError: %s', err)
|
||||||
|
return False
|
||||||
|
except urllib2.URLError, err:
|
||||||
|
log.error('Unable to connect to Synology %s', err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add_torrent_uri(self, torrent):
|
||||||
|
log.info('Adding torrent URL %s', torrent)
|
||||||
|
response = {}
|
||||||
|
# login
|
||||||
|
login = self._login()
|
||||||
|
if len(login) > 0 and login['success'] == True:
|
||||||
|
log.info('Login success, adding torrent')
|
||||||
|
args = {'api':'SYNO.DownloadStation.Task', 'version':1, 'method':'create', 'uri':torrent, '_sid':self.sid}
|
||||||
|
response = self._req(self.download_url, args)
|
||||||
|
self._logout()
|
||||||
|
else:
|
||||||
|
log.error('Couldn\'t login to Synology, %s', login)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ config = [{
|
|||||||
'tab': 'downloaders',
|
'tab': 'downloaders',
|
||||||
'name': 'transmission',
|
'name': 'transmission',
|
||||||
'label': 'Transmission',
|
'label': 'Transmission',
|
||||||
'description': 'Send torrents to Transmission.',
|
'description': 'Use <a href="http://www.transmissionbt.com/" target="_blank">Transmission</a> to download torrents.',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,9 +30,15 @@ class Transmission(Downloader):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Set parameters for Transmission
|
# Set parameters for Transmission
|
||||||
|
folder_name = self.createFileName(data, filedata, movie)[:-len(data.get('type')) - 1]
|
||||||
|
folder_path = os.path.join(self.conf('directory', default = ''), folder_name).rstrip(os.path.sep)
|
||||||
|
|
||||||
|
# Create the empty folder to download too
|
||||||
|
self.makeDir(folder_path)
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'paused': self.conf('paused', default = 0),
|
'paused': self.conf('paused', default = 0),
|
||||||
'download-dir': self.conf('directory', default = '').rstrip(os.path.sep)
|
'download-dir': folder_path
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent_params = {
|
torrent_params = {
|
||||||
@@ -49,6 +55,7 @@ class Transmission(Downloader):
|
|||||||
trpc = TransmissionRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
|
trpc = TransmissionRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
|
||||||
if data.get('type') == 'torrent_magnet':
|
if data.get('type') == 'torrent_magnet':
|
||||||
remote_torrent = trpc.add_torrent_uri(data.get('url'), arguments = params)
|
remote_torrent = trpc.add_torrent_uri(data.get('url'), arguments = params)
|
||||||
|
torrent_params['trackerAdd'] = self.torrent_trackers
|
||||||
else:
|
else:
|
||||||
remote_torrent = trpc.add_torrent_file(b64encode(filedata), arguments = params)
|
remote_torrent = trpc.add_torrent_file(b64encode(filedata), arguments = params)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
from .main import uTorrent
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return uTorrent()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'utorrent',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'downloaders',
|
||||||
|
'name': 'utorrent',
|
||||||
|
'label': 'uTorrent',
|
||||||
|
'description': 'Use <a href="http://www.utorrent.com/" target="_blank">uTorrent</a> to download torrents.',
|
||||||
|
'wizard': True,
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'enabled',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'enabler',
|
||||||
|
'radio_group': 'torrent',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'host',
|
||||||
|
'default': 'localhost:8000',
|
||||||
|
'description': 'Hostname with port. Usually <strong>localhost:8000</strong>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'username',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'password',
|
||||||
|
'type': 'password',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'label',
|
||||||
|
'description': 'Label to add torrent as.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'paused',
|
||||||
|
'type': 'bool',
|
||||||
|
'default': False,
|
||||||
|
'description': 'Add the torrent paused.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'manual',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'bool',
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
from bencode import bencode, bdecode
|
||||||
|
from couchpotato.core.downloaders.base import Downloader
|
||||||
|
from couchpotato.core.helpers.encoding import isInt
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from hashlib import sha1
|
||||||
|
from multipartpost import MultipartPostHandler
|
||||||
|
import cookielib
|
||||||
|
import httplib
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class uTorrent(Downloader):
|
||||||
|
|
||||||
|
type = ['torrent', 'torrent_magnet']
|
||||||
|
utorrent_api = None
|
||||||
|
|
||||||
|
def download(self, data, movie, manual = False, filedata = None):
|
||||||
|
|
||||||
|
if self.isDisabled(manual) or not self.isCorrectType(data.get('type')):
|
||||||
|
return
|
||||||
|
|
||||||
|
log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('type')))
|
||||||
|
|
||||||
|
# Load host from config and split out port.
|
||||||
|
host = self.conf('host').split(':')
|
||||||
|
if not isInt(host[1]):
|
||||||
|
log.error('Config properties are not filled in correctly, port is missing.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
torrent_params = {}
|
||||||
|
if self.conf('label'):
|
||||||
|
torrent_params['label'] = self.conf('label')
|
||||||
|
|
||||||
|
if not filedata and data.get('type') == 'torrent':
|
||||||
|
log.error('Failed sending torrent, no data')
|
||||||
|
return False
|
||||||
|
if data.get('type') == 'torrent_magnet':
|
||||||
|
torrent_hash = re.findall('urn:btih:([\w]{32,40})', data.get('url'))[0].upper()
|
||||||
|
torrent_params['trackers'] = '%0D%0A%0D%0A'.join(self.torrent_trackers)
|
||||||
|
else:
|
||||||
|
info = bdecode(filedata)["info"]
|
||||||
|
torrent_hash = sha1(bencode(info)).hexdigest().upper()
|
||||||
|
torrent_filename = self.createFileName(data, filedata, movie)
|
||||||
|
|
||||||
|
# Send request to uTorrent
|
||||||
|
try:
|
||||||
|
if not self.utorrent_api:
|
||||||
|
self.utorrent_api = uTorrentAPI(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
|
||||||
|
|
||||||
|
if data.get('type') == 'torrent_magnet':
|
||||||
|
self.utorrent_api.add_torrent_uri(data.get('url'))
|
||||||
|
else:
|
||||||
|
self.utorrent_api.add_torrent_file(torrent_filename, filedata)
|
||||||
|
|
||||||
|
# Change settings of added torrents
|
||||||
|
self.utorrent_api.set_torrent(torrent_hash, torrent_params)
|
||||||
|
if self.conf('paused', default = 0):
|
||||||
|
self.utorrent_api.pause_torrent(torrent_hash)
|
||||||
|
return True
|
||||||
|
except Exception, err:
|
||||||
|
log.error('Failed to send torrent to uTorrent: %s', err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class uTorrentAPI(object):
|
||||||
|
|
||||||
|
def __init__(self, host = 'localhost', port = 8000, username = None, password = None):
|
||||||
|
|
||||||
|
super(uTorrentAPI, self).__init__()
|
||||||
|
|
||||||
|
self.url = 'http://' + str(host) + ':' + str(port) + '/gui/'
|
||||||
|
self.token = ''
|
||||||
|
self.last_time = time.time()
|
||||||
|
cookies = cookielib.CookieJar()
|
||||||
|
self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler)
|
||||||
|
self.opener.addheaders = [('User-agent', 'couchpotato-utorrent-client/1.0')]
|
||||||
|
if username and password:
|
||||||
|
password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||||
|
password_manager.add_password(realm = None, uri = self.url, user = username, passwd = password)
|
||||||
|
self.opener.add_handler(urllib2.HTTPBasicAuthHandler(password_manager))
|
||||||
|
self.opener.add_handler(urllib2.HTTPDigestAuthHandler(password_manager))
|
||||||
|
elif username or password:
|
||||||
|
log.debug('User or password missing, not using authentication.')
|
||||||
|
self.token = self.get_token()
|
||||||
|
|
||||||
|
def _request(self, action, data = None):
|
||||||
|
if time.time() > self.last_time + 1800:
|
||||||
|
self.last_time = time.time()
|
||||||
|
self.token = self.get_token()
|
||||||
|
request = urllib2.Request(self.url + "?token=" + self.token + "&" + action, data)
|
||||||
|
try:
|
||||||
|
open_request = self.opener.open(request)
|
||||||
|
response = open_request.read()
|
||||||
|
log.debug('response: %s', response)
|
||||||
|
if response:
|
||||||
|
log.debug('uTorrent action successfull')
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
log.debug('Unknown failure sending command to uTorrent. Return text is: %s', response)
|
||||||
|
except httplib.InvalidURL, err:
|
||||||
|
log.error('Invalid uTorrent host, check your config %s', err)
|
||||||
|
except urllib2.HTTPError, err:
|
||||||
|
if err.code == 401:
|
||||||
|
log.error('Invalid uTorrent Username or Password, check your config')
|
||||||
|
else:
|
||||||
|
log.error('uTorrent HTTPError: %s', err)
|
||||||
|
except urllib2.URLError, err:
|
||||||
|
log.error('Unable to connect to uTorrent %s', err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_token(self):
|
||||||
|
request = self.opener.open(self.url + "token.html")
|
||||||
|
token = re.findall("<div.*?>(.*?)</", request.read())[0]
|
||||||
|
return token
|
||||||
|
|
||||||
|
def add_torrent_uri(self, torrent):
|
||||||
|
action = "action=add-url&s=%s" % urllib.quote(torrent)
|
||||||
|
return self._request(action)
|
||||||
|
|
||||||
|
def add_torrent_file(self, filename, filedata):
|
||||||
|
action = "action=add-file"
|
||||||
|
return self._request(action, {"torrent_file": (filename, filedata)})
|
||||||
|
|
||||||
|
def set_torrent(self, hash, params):
|
||||||
|
action = "action=setprops&hash=%s" % hash
|
||||||
|
for k, v in params.iteritems():
|
||||||
|
action += "&s=%s&v=%s" % (k, v)
|
||||||
|
return self._request(action)
|
||||||
|
|
||||||
|
def pause_torrent(self, hash):
|
||||||
|
action = "action=pause&hash=%s" % hash
|
||||||
|
return self._request(action)
|
||||||
@@ -12,7 +12,7 @@ def runHandler(name, handler, *args, **kwargs):
|
|||||||
return handler(*args, **kwargs)
|
return handler(*args, **kwargs)
|
||||||
except:
|
except:
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
log.error('Error in event "%s", that wasn\'t caught: %s%s', (name, traceback.format_exc(), Env.all()))
|
log.error('Error in event "%s", that wasn\'t caught: %s%s', (name, traceback.format_exc(), Env.all() if not Env.get('dev') else ''))
|
||||||
|
|
||||||
def addEvent(name, handler, priority = 100):
|
def addEvent(name, handler, priority = 100):
|
||||||
|
|
||||||
@@ -63,11 +63,21 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
e = events[name]
|
e = events[name]
|
||||||
if not options['in_order']: e.lock.acquire()
|
|
||||||
|
# Lock this event
|
||||||
|
e.lock.acquire()
|
||||||
|
|
||||||
e.asynchronous = False
|
e.asynchronous = False
|
||||||
e.in_order = options['in_order']
|
|
||||||
|
# Make sure only 1 event is fired at a time when order is wanted
|
||||||
|
kwargs['event_order_lock'] = threading.RLock() if options['in_order'] or options['single'] else None
|
||||||
|
kwargs['event_return_on_result'] = options['single']
|
||||||
|
|
||||||
|
# Fire
|
||||||
result = e(*args, **kwargs)
|
result = e(*args, **kwargs)
|
||||||
if not options['in_order']: e.lock.release()
|
|
||||||
|
# Release lock for this event
|
||||||
|
e.lock.release()
|
||||||
|
|
||||||
if options['single'] and not options['merge']:
|
if options['single'] and not options['merge']:
|
||||||
results = None
|
results = None
|
||||||
@@ -95,14 +105,14 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
# Merge
|
# Merge
|
||||||
if options['merge'] and len(results) > 0:
|
if options['merge'] and len(results) > 0:
|
||||||
# Dict
|
# Dict
|
||||||
if type(results[0]) == dict:
|
if isinstance(results[0], dict):
|
||||||
merged = {}
|
merged = {}
|
||||||
for result in results:
|
for result in results:
|
||||||
merged = mergeDicts(merged, result)
|
merged = mergeDicts(merged, result)
|
||||||
|
|
||||||
results = merged
|
results = merged
|
||||||
# Lists
|
# Lists
|
||||||
elif type(results[0]) == list:
|
elif isinstance(results[0], list):
|
||||||
merged = []
|
merged = []
|
||||||
for result in results:
|
for result in results:
|
||||||
merged += result
|
merged += result
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ def tryUrlencode(s):
|
|||||||
|
|
||||||
return new[1:]
|
return new[1:]
|
||||||
else:
|
else:
|
||||||
for letter in toUnicode(s):
|
for letter in ss(s):
|
||||||
try:
|
try:
|
||||||
new += quote_plus(letter)
|
new += quote_plus(letter)
|
||||||
except:
|
except:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from couchpotato.core.helpers.encoding import simplifyString, toSafeString
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
import hashlib
|
import hashlib
|
||||||
import os.path
|
import os.path
|
||||||
@@ -9,15 +10,34 @@ import sys
|
|||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
def getUserDir():
|
||||||
|
try:
|
||||||
|
import pwd
|
||||||
|
os.environ['HOME'] = pwd.getpwuid(os.geteuid()).pw_dir
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return os.path.expanduser('~')
|
||||||
|
|
||||||
|
def getDownloadDir():
|
||||||
|
user_dir = getUserDir()
|
||||||
|
|
||||||
|
# OSX
|
||||||
|
if 'darwin' in platform.platform().lower():
|
||||||
|
return os.path.join(user_dir, 'Downloads')
|
||||||
|
|
||||||
|
if os.name == 'nt':
|
||||||
|
return os.path.join(user_dir, 'Downloads')
|
||||||
|
|
||||||
|
return user_dir
|
||||||
|
|
||||||
def getDataDir():
|
def getDataDir():
|
||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
return os.path.join(os.environ['APPDATA'], 'CouchPotato')
|
return os.path.join(os.environ['APPDATA'], 'CouchPotato')
|
||||||
|
|
||||||
import pwd
|
user_dir = getUserDir()
|
||||||
os.environ['HOME'] = pwd.getpwuid(os.geteuid()).pw_dir
|
|
||||||
user_dir = os.path.expanduser('~')
|
|
||||||
|
|
||||||
# OSX
|
# OSX
|
||||||
if 'darwin' in platform.platform().lower():
|
if 'darwin' in platform.platform().lower():
|
||||||
@@ -84,7 +104,7 @@ def cleanHost(host):
|
|||||||
|
|
||||||
return host
|
return host
|
||||||
|
|
||||||
def getImdb(txt, check_inside = True):
|
def getImdb(txt, check_inside = True, multiple = False):
|
||||||
|
|
||||||
if check_inside and os.path.isfile(txt):
|
if check_inside and os.path.isfile(txt):
|
||||||
output = open(txt, 'r')
|
output = open(txt, 'r')
|
||||||
@@ -92,8 +112,10 @@ def getImdb(txt, check_inside = True):
|
|||||||
output.close()
|
output.close()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
id = re.findall('(tt\d{7})', txt)[0]
|
ids = re.findall('(tt\d{7})', txt)
|
||||||
return id
|
if multiple:
|
||||||
|
return ids if len(ids) > 0 else []
|
||||||
|
return ids[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -132,6 +154,16 @@ def getTitle(library_dict):
|
|||||||
log.error('Could not get title for library item: %s', library_dict)
|
log.error('Could not get title for library item: %s', library_dict)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def possibleTitles(raw_title):
|
||||||
|
|
||||||
|
titles = []
|
||||||
|
|
||||||
|
titles.append(toSafeString(raw_title).lower())
|
||||||
|
titles.append(raw_title.lower())
|
||||||
|
titles.append(simplifyString(raw_title))
|
||||||
|
|
||||||
|
return list(set(titles))
|
||||||
|
|
||||||
def randomString(size = 8, chars = string.ascii_uppercase + string.digits):
|
def randomString(size = 8, chars = string.ascii_uppercase + string.digits):
|
||||||
return ''.join(random.choice(chars) for x in range(size))
|
return ''.join(random.choice(chars) for x in range(size))
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ class CPLog(object):
|
|||||||
def info(self, msg, replace_tuple = ()):
|
def info(self, msg, replace_tuple = ()):
|
||||||
self.logger.info(self.addContext(msg, replace_tuple))
|
self.logger.info(self.addContext(msg, replace_tuple))
|
||||||
|
|
||||||
|
def info2(self, msg, replace_tuple = ()):
|
||||||
|
self.logger.log(19, self.addContext(msg, replace_tuple))
|
||||||
|
|
||||||
def debug(self, msg, replace_tuple = ()):
|
def debug(self, msg, replace_tuple = ()):
|
||||||
self.logger.debug(self.addContext(msg, replace_tuple))
|
self.logger.debug(self.addContext(msg, replace_tuple))
|
||||||
|
|
||||||
@@ -53,7 +56,8 @@ class CPLog(object):
|
|||||||
if not Env.get('dev'):
|
if not Env.get('dev'):
|
||||||
|
|
||||||
for replace in self.replace_private:
|
for replace in self.replace_private:
|
||||||
msg = re.sub('(%s=)[^\&]+' % replace, '%s=xxx' % replace, msg)
|
msg = re.sub('(\?%s=)[^\&]+' % replace, '?%s=xxx' % replace, msg)
|
||||||
|
msg = re.sub('(&%s=)[^\&]+' % replace, '&%s=xxx' % replace, msg)
|
||||||
|
|
||||||
# Replace api key
|
# Replace api key
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -37,8 +37,11 @@ class Growl(Notification):
|
|||||||
)
|
)
|
||||||
self.growl.register()
|
self.growl.register()
|
||||||
self.registered = True
|
self.registered = True
|
||||||
except:
|
except Exception, e:
|
||||||
log.error('Failed register of growl: %s', traceback.format_exc())
|
if 'timed out' in str(e):
|
||||||
|
self.registered = True
|
||||||
|
else:
|
||||||
|
log.error('Failed register of growl: %s', traceback.format_exc())
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
from .main import Toasty
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Toasty()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'toasty',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'notifications',
|
||||||
|
'name': 'toasty',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'enabled',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'api_key',
|
||||||
|
'label': 'Device ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'on_snatch',
|
||||||
|
'default': 0,
|
||||||
|
'type': 'bool',
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Also send message when movie is snatched.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.notifications.base import Notification
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
class Toasty(Notification):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'api': 'http://api.supertoasty.com/notify/%s?%s'
|
||||||
|
}
|
||||||
|
|
||||||
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
|
if self.isDisabled(): return
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'title': self.default_title,
|
||||||
|
'text': toUnicode(message),
|
||||||
|
'sender': toUnicode("CouchPotato"),
|
||||||
|
'image': 'https://raw.github.com/RuudBurger/CouchPotatoServer/master/couchpotato/static/images/homescreen.png',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.urlopen(self.urls['api'] % (self.conf('api_key'), tryUrlencode(data)), show_error = False)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
log.error('Toasty failed: %s', traceback.format_exc())
|
||||||
|
|
||||||
|
return False
|
||||||
@@ -10,6 +10,7 @@ config = [{
|
|||||||
'tab': 'notifications',
|
'tab': 'notifications',
|
||||||
'name': 'xbmc',
|
'name': 'xbmc',
|
||||||
'label': 'XBMC',
|
'label': 'XBMC',
|
||||||
|
'description': 'v11 (Eden) and v12 (Frodo)',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ from couchpotato.core.logger import CPLog
|
|||||||
from couchpotato.core.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
from flask.helpers import json
|
from flask.helpers import json
|
||||||
import base64
|
import base64
|
||||||
|
import traceback
|
||||||
|
import urllib
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -10,24 +12,147 @@ log = CPLog(__name__)
|
|||||||
class XBMC(Notification):
|
class XBMC(Notification):
|
||||||
|
|
||||||
listen_to = ['renamer.after']
|
listen_to = ['renamer.after']
|
||||||
|
use_json_notifications = {}
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|
||||||
hosts = splitString(self.conf('host'))
|
hosts = splitString(self.conf('host'))
|
||||||
|
|
||||||
successful = 0
|
successful = 0
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
response = self.request(host, [
|
|
||||||
('GUI.ShowNotification', {"title":"CouchPotato", "message":message}),
|
|
||||||
('VideoLibrary.Scan', {}),
|
|
||||||
])
|
|
||||||
|
|
||||||
for result in response:
|
if self.use_json_notifications.get(host) is None:
|
||||||
if result['result'] == "OK":
|
self.getXBMCJSONversion(host, message = message)
|
||||||
successful += 1
|
|
||||||
|
if self.use_json_notifications.get(host):
|
||||||
|
response = self.request(host, [
|
||||||
|
('GUI.ShowNotification', {'title':self.default_title, 'message':message}),
|
||||||
|
('VideoLibrary.Scan', {}),
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
response = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message})
|
||||||
|
response += self.request(host, [('VideoLibrary.Scan', {})])
|
||||||
|
|
||||||
|
try:
|
||||||
|
for result in response:
|
||||||
|
if (result.get('result') and result['result'] == 'OK'):
|
||||||
|
successful += 1
|
||||||
|
elif (result.get('error')):
|
||||||
|
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed parsing results: %s', traceback.format_exc())
|
||||||
|
|
||||||
return successful == len(hosts) * 2
|
return successful == len(hosts) * 2
|
||||||
|
|
||||||
|
def getXBMCJSONversion(self, host, message = ''):
|
||||||
|
|
||||||
|
success = False
|
||||||
|
|
||||||
|
# XBMC JSON-RPC version request
|
||||||
|
response = self.request(host, [
|
||||||
|
('JSONRPC.Version', {})
|
||||||
|
])
|
||||||
|
for result in response:
|
||||||
|
if (result.get('result') and type(result['result']['version']).__name__ == 'int'):
|
||||||
|
# only v2 and v4 return an int object
|
||||||
|
# v6 (as of XBMC v12(Frodo)) is required to send notifications
|
||||||
|
xbmc_rpc_version = str(result['result']['version'])
|
||||||
|
|
||||||
|
log.debug('XBMC JSON-RPC Version: %s ; Notifications by JSON-RPC only supported for v6 [as of XBMC v12(Frodo)]', xbmc_rpc_version)
|
||||||
|
|
||||||
|
# disable JSON use
|
||||||
|
self.use_json_notifications[host] = False
|
||||||
|
|
||||||
|
# send the text message
|
||||||
|
resp = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message})
|
||||||
|
for result in resp:
|
||||||
|
if (result.get('result') and result['result'] == 'OK'):
|
||||||
|
log.debug('Message delivered successfully!')
|
||||||
|
success = True
|
||||||
|
break
|
||||||
|
elif (result.get('error')):
|
||||||
|
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
||||||
|
break
|
||||||
|
|
||||||
|
elif (result.get('result') and type(result['result']['version']).__name__ == 'dict'):
|
||||||
|
# XBMC JSON-RPC v6 returns an array object containing
|
||||||
|
# major, minor and patch number
|
||||||
|
xbmc_rpc_version = str(result['result']['version']['major'])
|
||||||
|
xbmc_rpc_version += '.' + str(result['result']['version']['minor'])
|
||||||
|
xbmc_rpc_version += '.' + str(result['result']['version']['patch'])
|
||||||
|
|
||||||
|
log.debug('XBMC JSON-RPC Version: %s', xbmc_rpc_version)
|
||||||
|
|
||||||
|
# ok, XBMC version is supported
|
||||||
|
self.use_json_notifications[host] = True
|
||||||
|
|
||||||
|
# send the text message
|
||||||
|
resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message})])
|
||||||
|
for result in resp:
|
||||||
|
if (result.get('result') and result['result'] == 'OK'):
|
||||||
|
log.debug('Message delivered successfully!')
|
||||||
|
success = True
|
||||||
|
break
|
||||||
|
elif (result.get('error')):
|
||||||
|
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
||||||
|
break
|
||||||
|
|
||||||
|
# error getting version info (we do have contact with XBMC though)
|
||||||
|
elif (result.get('error')):
|
||||||
|
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
|
||||||
|
|
||||||
|
log.debug('Use JSON notifications: %s ', self.use_json_notifications)
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
def notifyXBMCnoJSON(self, host, data):
|
||||||
|
|
||||||
|
server = 'http://%s/xbmcCmds/' % host
|
||||||
|
|
||||||
|
# title, message [, timeout , image #can be added!]
|
||||||
|
cmd = "xbmcHttp?command=ExecBuiltIn(Notification('%s','%s'))" % (urllib.quote(data['title']), urllib.quote(data['message']))
|
||||||
|
server += cmd
|
||||||
|
|
||||||
|
# I have no idea what to set to, just tried text/plain and seems to be working :)
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
}
|
||||||
|
|
||||||
|
# authentication support
|
||||||
|
if self.conf('password'):
|
||||||
|
base64string = base64.encodestring('%s:%s' % (self.conf('username'), self.conf('password'))).replace('\n', '')
|
||||||
|
headers['Authorization'] = 'Basic %s' % base64string
|
||||||
|
|
||||||
|
try:
|
||||||
|
log.debug('Sending non-JSON-type request to %s: %s', (host, data))
|
||||||
|
|
||||||
|
# response wil either be 'OK':
|
||||||
|
# <html>
|
||||||
|
# <li>OK
|
||||||
|
# </html>
|
||||||
|
#
|
||||||
|
# or 'Error':
|
||||||
|
# <html>
|
||||||
|
# <li>Error:<message>
|
||||||
|
# </html>
|
||||||
|
#
|
||||||
|
response = self.urlopen(server, headers = headers)
|
||||||
|
|
||||||
|
if 'OK' in response:
|
||||||
|
log.debug('Returned from non-JSON-type request %s: %s', (host, response))
|
||||||
|
# manually fake expected response array
|
||||||
|
return [{'result': 'OK'}]
|
||||||
|
else:
|
||||||
|
log.error('Returned from non-JSON-type request %s: %s', (host, response))
|
||||||
|
# manually fake expected response array
|
||||||
|
return [{'result': 'Error'}]
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed sending non-JSON-type request to XBMC: %s', traceback.format_exc())
|
||||||
|
return [{'result': 'Error'}]
|
||||||
|
|
||||||
def request(self, host, requests):
|
def request(self, host, requests):
|
||||||
server = 'http://%s/jsonrpc' % host
|
server = 'http://%s/jsonrpc' % host
|
||||||
|
|
||||||
@@ -50,10 +175,14 @@ class XBMC(Notification):
|
|||||||
base64string = base64.encodestring('%s:%s' % (self.conf('username'), self.conf('password'))).replace('\n', '')
|
base64string = base64.encodestring('%s:%s' % (self.conf('username'), self.conf('password'))).replace('\n', '')
|
||||||
headers['Authorization'] = 'Basic %s' % base64string
|
headers['Authorization'] = 'Basic %s' % base64string
|
||||||
|
|
||||||
log.debug('Sending request to %s: %s', (host, data))
|
try:
|
||||||
rdata = self.urlopen(server, headers = headers, params = data, multipart = True)
|
log.debug('Sending request to %s: %s', (host, data))
|
||||||
response = json.loads(rdata)
|
rdata = self.urlopen(server, headers = headers, params = data, multipart = True)
|
||||||
log.debug('Returned from request %s: %s', (host, response))
|
response = json.loads(rdata)
|
||||||
|
log.debug('Returned from request %s: %s', (host, response))
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
except:
|
||||||
|
log.error('Failed sending request to XBMC: %s', traceback.format_exc())
|
||||||
|
return []
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class Automation(Plugin):
|
|||||||
|
|
||||||
fireEvent('schedule.interval', 'automation.add_movies', self.addMovies, hours = self.conf('hour', default = 12))
|
fireEvent('schedule.interval', 'automation.add_movies', self.addMovies, hours = self.conf('hour', default = 12))
|
||||||
|
|
||||||
if not Env.get('dev'):
|
if Env.get('dev'):
|
||||||
addEvent('app.load', self.addMovies)
|
addEvent('app.load', self.addMovies)
|
||||||
|
|
||||||
def addMovies(self):
|
def addMovies(self):
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from couchpotato import addView
|
from couchpotato import addView
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, simplifyString, ss, \
|
from couchpotato.core.helpers.encoding import tryUrlencode, ss, toSafeString
|
||||||
toSafeString
|
from couchpotato.core.helpers.variable import getExt, md5
|
||||||
from couchpotato.core.helpers.variable import getExt
|
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from flask.templating import render_template_string
|
from flask.templating import render_template_string
|
||||||
@@ -35,7 +34,7 @@ class Plugin(object):
|
|||||||
http_failed_disabled = {}
|
http_failed_disabled = {}
|
||||||
|
|
||||||
def registerPlugin(self):
|
def registerPlugin(self):
|
||||||
addEvent('app.shutdown', self.doShutdown)
|
addEvent('app.do_shutdown', self.doShutdown)
|
||||||
addEvent('plugin.running', self.isRunning)
|
addEvent('plugin.running', self.isRunning)
|
||||||
|
|
||||||
def conf(self, attr, value = None, default = None):
|
def conf(self, attr, value = None, default = None):
|
||||||
@@ -99,6 +98,7 @@ class Plugin(object):
|
|||||||
|
|
||||||
# http request
|
# http request
|
||||||
def urlopen(self, url, timeout = 30, params = None, headers = None, opener = None, multipart = False, show_error = True):
|
def urlopen(self, url, timeout = 30, params = None, headers = None, opener = None, multipart = False, show_error = True):
|
||||||
|
url = ss(url)
|
||||||
|
|
||||||
if not headers: headers = {}
|
if not headers: headers = {}
|
||||||
if not params: params = {}
|
if not params: params = {}
|
||||||
@@ -114,8 +114,11 @@ class Plugin(object):
|
|||||||
# Don't try for failed requests
|
# Don't try for failed requests
|
||||||
if self.http_failed_disabled.get(host, 0) > 0:
|
if self.http_failed_disabled.get(host, 0) > 0:
|
||||||
if self.http_failed_disabled[host] > (time.time() - 900):
|
if self.http_failed_disabled[host] > (time.time() - 900):
|
||||||
log.info('Disabled calls to %s for 15 minutes because so many failed requests.', host)
|
log.info2('Disabled calls to %s for 15 minutes because so many failed requests.', host)
|
||||||
raise Exception
|
if not show_error:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
else:
|
else:
|
||||||
del self.http_failed_request[host]
|
del self.http_failed_request[host]
|
||||||
del self.http_failed_disabled[host]
|
del self.http_failed_disabled[host]
|
||||||
@@ -127,8 +130,11 @@ class Plugin(object):
|
|||||||
log.info('Opening multipart url: %s, params: %s', (url, [x for x in params.iterkeys()] if isinstance(params, dict) else 'with data'))
|
log.info('Opening multipart url: %s, params: %s', (url, [x for x in params.iterkeys()] if isinstance(params, dict) else 'with data'))
|
||||||
request = urllib2.Request(url, params, headers)
|
request = urllib2.Request(url, params, headers)
|
||||||
|
|
||||||
cookies = cookielib.CookieJar()
|
if opener:
|
||||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler)
|
opener.add_handler(MultipartPostHandler())
|
||||||
|
else:
|
||||||
|
cookies = cookielib.CookieJar()
|
||||||
|
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler)
|
||||||
|
|
||||||
response = opener.open(request, timeout = timeout)
|
response = opener.open(request, timeout = timeout)
|
||||||
else:
|
else:
|
||||||
@@ -219,7 +225,7 @@ class Plugin(object):
|
|||||||
|
|
||||||
|
|
||||||
def getCache(self, cache_key, url = None, **kwargs):
|
def getCache(self, cache_key, url = None, **kwargs):
|
||||||
cache_key = simplifyString(cache_key)
|
cache_key = md5(ss(cache_key))
|
||||||
cache = Env.get('cache').get(cache_key)
|
cache = Env.get('cache').get(cache_key)
|
||||||
if cache:
|
if cache:
|
||||||
if not Env.get('dev'): log.debug('Getting cache %s', cache_key)
|
if not Env.get('dev'): log.debug('Getting cache %s', cache_key)
|
||||||
@@ -239,9 +245,11 @@ class Plugin(object):
|
|||||||
self.setCache(cache_key, data, timeout = cache_timeout)
|
self.setCache(cache_key, data, timeout = cache_timeout)
|
||||||
return data
|
return data
|
||||||
except:
|
except:
|
||||||
if not kwargs.get('show_error'):
|
if not kwargs.get('show_error', True):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
def setCache(self, cache_key, value, timeout = 300):
|
def setCache(self, cache_key, value, timeout = 300):
|
||||||
log.debug('Setting cache %s', cache_key)
|
log.debug('Setting cache %s', cache_key)
|
||||||
Env.get('cache').set(cache_key, value, timeout)
|
Env.get('cache').set(cache_key, value, timeout)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.helpers.request import getParam, jsonified
|
from couchpotato.core.helpers.request import getParam, jsonified
|
||||||
|
from couchpotato.core.helpers.variable import getUserDir
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
import ctypes
|
import ctypes
|
||||||
import os
|
import os
|
||||||
@@ -65,15 +66,7 @@ class FileBrowser(Plugin):
|
|||||||
def view(self):
|
def view(self):
|
||||||
|
|
||||||
path = getParam('path', '/')
|
path = getParam('path', '/')
|
||||||
|
home = getUserDir()
|
||||||
# Set proper home dir for some systems
|
|
||||||
try:
|
|
||||||
import pwd
|
|
||||||
os.environ['HOME'] = pwd.getpwuid(os.geteuid()).pw_dir
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
home = os.path.expanduser('~')
|
|
||||||
|
|
||||||
if not path:
|
if not path:
|
||||||
path = home
|
path = home
|
||||||
|
|||||||
@@ -66,10 +66,12 @@ class FileManager(Plugin):
|
|||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
log.debug('Cleaning up unused files')
|
log.debug('Cleaning up unused files')
|
||||||
|
|
||||||
|
python_cache = Env.get('cache')._path
|
||||||
try:
|
try:
|
||||||
db = get_session()
|
db = get_session()
|
||||||
for root, dirs, walk_files in os.walk(Env.get('cache_dir')):
|
for root, dirs, walk_files in os.walk(Env.get('cache_dir')):
|
||||||
for filename in walk_files:
|
for filename in walk_files:
|
||||||
|
if root == python_cache: continue
|
||||||
file_path = os.path.join(root, filename)
|
file_path = os.path.join(root, filename)
|
||||||
f = db.query(File).filter(File.path == toUnicode(file_path)).first()
|
f = db.query(File).filter(File.path == toUnicode(file_path)).first()
|
||||||
if not f:
|
if not f:
|
||||||
|
|||||||
@@ -115,12 +115,35 @@ class Manage(Plugin):
|
|||||||
if done_movie['library']['identifier'] not in added_identifiers:
|
if done_movie['library']['identifier'] not in added_identifiers:
|
||||||
fireEvent('movie.delete', movie_id = done_movie['id'], delete_from = 'all')
|
fireEvent('movie.delete', movie_id = done_movie['id'], delete_from = 'all')
|
||||||
else:
|
else:
|
||||||
|
|
||||||
for release in done_movie.get('releases', []):
|
for release in done_movie.get('releases', []):
|
||||||
for release_file in release.get('files', []):
|
if len(release.get('files', [])) == 0:
|
||||||
# Remove release not available anymore
|
fireEvent('release.delete', release['id'])
|
||||||
if not os.path.isfile(ss(release_file['path'])):
|
else:
|
||||||
fireEvent('release.clean', release['id'])
|
for release_file in release.get('files', []):
|
||||||
break
|
# Remove release not available anymore
|
||||||
|
if not os.path.isfile(ss(release_file['path'])):
|
||||||
|
fireEvent('release.clean', release['id'])
|
||||||
|
break
|
||||||
|
|
||||||
|
# Check if there are duplicate releases (different quality) use the last one, delete the rest
|
||||||
|
if len(done_movie.get('releases', [])) > 1:
|
||||||
|
used_files = {}
|
||||||
|
for release in done_movie.get('releases', []):
|
||||||
|
|
||||||
|
for release_file in release.get('files', []):
|
||||||
|
already_used = used_files.get(release_file['path'])
|
||||||
|
|
||||||
|
if already_used:
|
||||||
|
print already_used, release['id']
|
||||||
|
if already_used < release['id']:
|
||||||
|
fireEvent('release.delete', release['id'], single = True) # delete this one
|
||||||
|
else:
|
||||||
|
fireEvent('release.delete', already_used, single = True) # delete previous one
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
used_files[release_file['path']] = release.get('id')
|
||||||
|
del used_files
|
||||||
|
|
||||||
Env.prop('manage.last_update', time.time())
|
Env.prop('manage.last_update', time.time())
|
||||||
except:
|
except:
|
||||||
@@ -153,7 +176,7 @@ class Manage(Plugin):
|
|||||||
'to_go': total_found,
|
'to_go': total_found,
|
||||||
}
|
}
|
||||||
|
|
||||||
if group['library']:
|
if group['library'] and group['library'].get('identifier'):
|
||||||
identifier = group['library'].get('identifier')
|
identifier = group['library'].get('identifier')
|
||||||
added_identifiers.append(identifier)
|
added_identifiers.append(identifier)
|
||||||
|
|
||||||
@@ -187,5 +210,5 @@ class Manage(Plugin):
|
|||||||
groups = fireEvent('scanner.scan', folder = folder, files = files, single = True)
|
groups = fireEvent('scanner.scan', folder = folder, files = files, single = True)
|
||||||
|
|
||||||
for group in groups.itervalues():
|
for group in groups.itervalues():
|
||||||
if group['library']:
|
if group['library'] and group['library'].get('identifier'):
|
||||||
fireEvent('release.add', group = group)
|
fireEvent('release.add', group = group)
|
||||||
|
|||||||
@@ -300,10 +300,11 @@ var MovieList = new Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
deleteSelected: function(){
|
deleteSelected: function(){
|
||||||
var self = this;
|
var self = this,
|
||||||
var ids = self.getSelectedMovies()
|
ids = self.getSelectedMovies(),
|
||||||
|
help_msg = self.identifier == 'wanted' ? 'If you do, you won\'t be able to watch them, as they won\'t get downloaded!' : 'Your files will be safe, this will only delete the reference from the CouchPotato manage list';
|
||||||
|
|
||||||
var qObj = new Question('Are you sure you want to delete '+ids.length+' movie'+ (ids.length != 1 ? 's' : '') +'?', 'If you do, you won\'t be able to watch them, as they won\'t get downloaded!', [{
|
var qObj = new Question('Are you sure you want to delete '+ids.length+' movie'+ (ids.length != 1 ? 's' : '') +'?', help_msg, [{
|
||||||
'text': 'Yes, delete '+(ids.length != 1 ? 'them' : 'it'),
|
'text': 'Yes, delete '+(ids.length != 1 ? 'them' : 'it'),
|
||||||
'class': 'delete',
|
'class': 'delete',
|
||||||
'events': {
|
'events': {
|
||||||
|
|||||||
@@ -479,29 +479,32 @@ var ReleaseAction = new Class({
|
|||||||
self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release');
|
self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release');
|
||||||
}
|
}
|
||||||
|
|
||||||
self.trynext_container.adopt(
|
if(self.next_release || self.last_release){
|
||||||
new Element('span.or', {
|
|
||||||
'text': 'This movie is snatched, if anything went wrong, download'
|
self.trynext_container.adopt(
|
||||||
}),
|
new Element('span.or', {
|
||||||
self.last_release ? new Element('a.button.orange', {
|
'text': 'This movie is snatched, if anything went wrong, download'
|
||||||
'text': 'the same release again',
|
}),
|
||||||
'events': {
|
self.last_release ? new Element('a.button.orange', {
|
||||||
'click': self.trySameRelease.bind(self)
|
'text': 'the same release again',
|
||||||
}
|
'events': {
|
||||||
}) : null,
|
'click': self.trySameRelease.bind(self)
|
||||||
self.next_release && self.last_release ? new Element('span.or', {
|
}
|
||||||
'text': ','
|
}) : null,
|
||||||
}) : null,
|
self.next_release && self.last_release ? new Element('span.or', {
|
||||||
self.next_release ? [new Element('a.button.green', {
|
'text': ','
|
||||||
'text': self.last_release ? 'another release' : 'the best release',
|
}) : null,
|
||||||
'events': {
|
self.next_release ? [new Element('a.button.green', {
|
||||||
'click': self.tryNextRelease.bind(self)
|
'text': self.last_release ? 'another release' : 'the best release',
|
||||||
}
|
'events': {
|
||||||
}),
|
'click': self.tryNextRelease.bind(self)
|
||||||
new Element('span.or', {
|
}
|
||||||
'text': 'or pick one below'
|
}),
|
||||||
})] : null
|
new Element('span.or', {
|
||||||
)
|
'text': 'or pick one below'
|
||||||
|
})] : null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,10 +91,15 @@
|
|||||||
.movie_result {
|
.movie_result {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 140px;
|
height: 140px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie_result .options {
|
.movie_result .options {
|
||||||
height: 140px;
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-width: 1px 0;
|
border-width: 1px 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@@ -133,17 +138,21 @@
|
|||||||
|
|
||||||
.movie_result .data {
|
.movie_result .data {
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
width: 470px;
|
position: absolute;
|
||||||
position: relative;
|
height: 100%;
|
||||||
height: 140px;
|
width: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
margin: -140px 0 0 0;
|
left: 0;
|
||||||
background: #5c697b;
|
background: #5c697b;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
border-bottom: 1px solid #333;
|
border-bottom: 1px solid #333;
|
||||||
border-top: 1px solid rgba(255,255,255, 0.15);
|
border-top: 1px solid rgba(255,255,255, 0.15);
|
||||||
|
transition: all .6s cubic-bezier(0.9,0,0.1,1);
|
||||||
}
|
}
|
||||||
|
.movie_result .data.open {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.movie_result:last-child .data { border-bottom: 0; }
|
.movie_result:last-child .data { border-bottom: 0; }
|
||||||
|
|
||||||
@@ -193,4 +202,9 @@
|
|||||||
|
|
||||||
.search_form .mask {
|
.search_form .mask {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,9 @@ Block.Search = new Class({
|
|||||||
self.hideResults(false)
|
self.hideResults(false)
|
||||||
},
|
},
|
||||||
'blur': function(){
|
'blur': function(){
|
||||||
self.el.removeClass('focused')
|
(function(){
|
||||||
|
self.el.removeClass('focused')
|
||||||
|
}).delay(2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -117,7 +119,7 @@ Block.Search = new Class({
|
|||||||
self.hideResults(false);
|
self.hideResults(false);
|
||||||
|
|
||||||
if(!cache){
|
if(!cache){
|
||||||
self.positionMask().fade('in');
|
self.mask.fade('in');
|
||||||
|
|
||||||
if(!self.spinner)
|
if(!self.spinner)
|
||||||
self.spinner = createSpinner(self.mask);
|
self.spinner = createSpinner(self.mask);
|
||||||
@@ -139,7 +141,6 @@ Block.Search = new Class({
|
|||||||
fill: function(q, json){
|
fill: function(q, json){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.positionMask()
|
|
||||||
self.cache[q] = json
|
self.cache[q] = json
|
||||||
|
|
||||||
self.movies = {}
|
self.movies = {}
|
||||||
@@ -168,19 +169,6 @@ Block.Search = new Class({
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
positionMask: function(){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var s = self.result_container.getSize()
|
|
||||||
|
|
||||||
return self.mask.setStyles({
|
|
||||||
'width': s.x,
|
|
||||||
'height': s.y
|
|
||||||
}).position({
|
|
||||||
'relativeTo': self.result_container
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
loading: function(bool){
|
loading: function(bool){
|
||||||
this.el[bool ? 'addClass' : 'removeClass']('loading')
|
this.el[bool ? 'addClass' : 'removeClass']('loading')
|
||||||
},
|
},
|
||||||
@@ -193,7 +181,7 @@ Block.Search = new Class({
|
|||||||
|
|
||||||
Block.Search.Item = new Class({
|
Block.Search.Item = new Class({
|
||||||
|
|
||||||
initialize: function(info){
|
initialize: function(info, options){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.info = info;
|
self.info = info;
|
||||||
@@ -203,14 +191,13 @@ Block.Search.Item = new Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
create: function(){
|
create: function(){
|
||||||
var self = this;
|
var self = this,
|
||||||
|
info = self.info;
|
||||||
var info = self.info;
|
|
||||||
|
|
||||||
self.el = new Element('div.movie_result', {
|
self.el = new Element('div.movie_result', {
|
||||||
'id': info.imdb
|
'id': info.imdb
|
||||||
}).adopt(
|
}).adopt(
|
||||||
self.options = 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': {
|
'tween': {
|
||||||
duration: 400,
|
duration: 400,
|
||||||
@@ -273,11 +260,7 @@ Block.Search.Item = new Class({
|
|||||||
|
|
||||||
self.createOptions();
|
self.createOptions();
|
||||||
|
|
||||||
if(!self.width)
|
self.data_container.addClass('open');
|
||||||
self.width = self.data_container.getCoordinates().width
|
|
||||||
|
|
||||||
self.data_container.tween('left', 0, self.width);
|
|
||||||
|
|
||||||
self.el.addEvent('outerClick', self.closeOptions.bind(self))
|
self.el.addEvent('outerClick', self.closeOptions.bind(self))
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -285,7 +268,7 @@ Block.Search.Item = new Class({
|
|||||||
closeOptions: function(){
|
closeOptions: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.data_container.tween('left', self.width, 0);
|
self.data_container.removeClass('open');
|
||||||
self.el.removeEvents('outerClick')
|
self.el.removeEvents('outerClick')
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -302,28 +285,31 @@ Block.Search.Item = new Class({
|
|||||||
'profile_id': self.profile_select.get('value')
|
'profile_id': self.profile_select.get('value')
|
||||||
},
|
},
|
||||||
'onComplete': function(json){
|
'onComplete': function(json){
|
||||||
self.options.empty();
|
self.options_el.empty();
|
||||||
self.options.adopt(
|
self.options_el.adopt(
|
||||||
new Element('div.message', {
|
new Element('div.message', {
|
||||||
'text': json.added ? 'Movie succesfully added.' : 'Movie didn\'t add properly. Check logs'
|
'text': json.added ? 'Movie successfully added.' : 'Movie didn\'t add properly. Check logs'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
self.mask.fade('out');
|
||||||
},
|
},
|
||||||
'onFailure': function(){
|
'onFailure': function(){
|
||||||
self.options.empty();
|
self.options_el.empty();
|
||||||
self.options.adopt(
|
self.options_el.adopt(
|
||||||
new Element('div.message', {
|
new Element('div.message', {
|
||||||
'text': 'Something went wrong, check the logs for more info.'
|
'text': 'Something went wrong, check the logs for more info.'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
self.mask.fade('out');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
createOptions: function(){
|
createOptions: function(){
|
||||||
var self = this;
|
var self = this,
|
||||||
|
info = self.info;
|
||||||
|
|
||||||
if(!self.options.hasClass('set')){
|
if(!self.options_el.hasClass('set')){
|
||||||
|
|
||||||
if(self.info.in_library){
|
if(self.info.in_library){
|
||||||
var in_library = [];
|
var in_library = [];
|
||||||
@@ -332,10 +318,10 @@ Block.Search.Item = new Class({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.options.adopt(
|
self.options_el.grab(
|
||||||
new Element('div').adopt(
|
new Element('div').adopt(
|
||||||
self.option_thumbnail = self.info.images && self.info.images.poster.length > 0 ? new Element('img.thumbnail', {
|
self.thumbnail = (info.images && info.images.poster.length > 0) ? new Element('img.thumbnail', {
|
||||||
'src': self.info.images.poster[0],
|
'src': info.images.poster[0],
|
||||||
'height': null,
|
'height': null,
|
||||||
'width': null
|
'width': null
|
||||||
}) : null,
|
}) : null,
|
||||||
@@ -372,7 +358,7 @@ Block.Search.Item = new Class({
|
|||||||
}).inject(self.profile_select)
|
}).inject(self.profile_select)
|
||||||
});
|
});
|
||||||
|
|
||||||
self.options.addClass('set');
|
self.options_el.addClass('set');
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -380,17 +366,7 @@ Block.Search.Item = new Class({
|
|||||||
loadingMask: function(){
|
loadingMask: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var s = self.options.getSize();
|
self.mask = new Element('span.mask').inject(self.el).fade('hide')
|
||||||
|
|
||||||
self.mask = new Element('span.mask', {
|
|
||||||
'styles': {
|
|
||||||
'position': 'relative',
|
|
||||||
'width': s.x,
|
|
||||||
'height': s.y,
|
|
||||||
'top': -s.y,
|
|
||||||
'display': 'block'
|
|
||||||
}
|
|
||||||
}).inject(self.options).fade('hide')
|
|
||||||
|
|
||||||
createSpinner(self.mask)
|
createSpinner(self.mask)
|
||||||
self.mask.fade('in')
|
self.mask.fade('in')
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from couchpotato.core.helpers.variable import mergeDicts, md5, getExt
|
|||||||
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 Quality, Profile, ProfileType
|
from couchpotato.core.settings.model import Quality, Profile, ProfileType
|
||||||
|
from sqlalchemy.sql.expression import or_
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
@@ -18,15 +19,15 @@ 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, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']},
|
{'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, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']},
|
{'identifier': '720p', 'hd': True, 'size': (3500, 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'], 'ext':['avi']},
|
{'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': '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']},
|
{'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': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip'], 'allow': ['dvdr', 'dvd'], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip'], 'allow': ['dvdr', 'dvd'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': [], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': [], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
{'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync', 'hdts'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
|
||||||
{'identifier': 'cam', 'size': (600, 1000), 'label': 'Cam', 'alternative': ['camrip', 'hdcam'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']}
|
{'identifier': 'cam', 'size': (600, 1000), 'label': 'Cam', 'alternative': ['camrip', 'hdcam'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']}
|
||||||
]
|
]
|
||||||
pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr']
|
pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr']
|
||||||
@@ -76,7 +77,7 @@ class QualityPlugin(Plugin):
|
|||||||
db = get_session()
|
db = get_session()
|
||||||
quality_dict = {}
|
quality_dict = {}
|
||||||
|
|
||||||
quality = db.query(Quality).filter_by(identifier = identifier).first()
|
quality = db.query(Quality).filter(or_(Quality.identifier == identifier, Quality.id == identifier)).first()
|
||||||
if quality:
|
if quality:
|
||||||
quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict())
|
quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict())
|
||||||
|
|
||||||
@@ -198,9 +199,14 @@ class QualityPlugin(Plugin):
|
|||||||
|
|
||||||
for quality in self.all():
|
for quality in self.all():
|
||||||
|
|
||||||
# Last check on resolution only
|
# Check width resolution, range 20
|
||||||
if quality.get('width', 480) == extra.get('resolution_width', 0):
|
if (quality.get('width', 720) - 20) <= extra.get('resolution_width', 0) <= (quality.get('width', 720) + 20):
|
||||||
log.debug('Found %s via resolution_width: %s == %s', (quality['identifier'], quality.get('width', 480), extra.get('resolution_width', 0)))
|
log.debug('Found %s via resolution_width: %s == %s', (quality['identifier'], quality.get('width', 720), extra.get('resolution_width', 0)))
|
||||||
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
|
# Check height resolution, range 20
|
||||||
|
if (quality.get('height', 480) - 20) <= extra.get('resolution_height', 0) <= (quality.get('height', 480) + 20):
|
||||||
|
log.debug('Found %s via resolution_height: %s == %s', (quality['identifier'], quality.get('height', 480), extra.get('resolution_height', 0)))
|
||||||
return self.setCache(hash, quality)
|
return self.setCache(hash, quality)
|
||||||
|
|
||||||
if 480 <= extra.get('resolution_width', 0) <= 720:
|
if 480 <= extra.get('resolution_width', 0) <= 720:
|
||||||
|
|||||||
@@ -133,6 +133,9 @@ class Release(Plugin):
|
|||||||
db.delete(release_file)
|
db.delete(release_file)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
if len(rel.files) == 0:
|
||||||
|
self.delete(id)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -82,6 +82,15 @@ config = [{
|
|||||||
'unit': 'min(s)',
|
'unit': 'min(s)',
|
||||||
'description': 'Detect movie status every X minutes. Will start the renamer if movie is <strong>completed</strong> or handle <strong>failed</strong> download if these options are enabled',
|
'description': 'Detect movie status every X minutes. Will start the renamer if movie is <strong>completed</strong> or handle <strong>failed</strong> download if these options are enabled',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'advanced': True,
|
||||||
|
'name': 'force_every',
|
||||||
|
'label': 'Force every',
|
||||||
|
'default': 2,
|
||||||
|
'type': 'int',
|
||||||
|
'unit': 'hour(s)',
|
||||||
|
'description': 'Forces the renamer to scan every X hours',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'name': 'next_on_failed',
|
'name': 'next_on_failed',
|
||||||
@@ -124,13 +133,6 @@ config = [{
|
|||||||
'type': 'choice',
|
'type': 'choice',
|
||||||
'options': rename_options
|
'options': rename_options
|
||||||
},
|
},
|
||||||
{
|
|
||||||
'name': 'trailer_name',
|
|
||||||
'label': 'Trailer naming',
|
|
||||||
'default': '<filename>-trailer.<ext>',
|
|
||||||
'type': 'choice',
|
|
||||||
'options': rename_options
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -33,9 +33,13 @@ class Renamer(Plugin):
|
|||||||
addEvent('renamer.check_snatched', self.checkSnatched)
|
addEvent('renamer.check_snatched', self.checkSnatched)
|
||||||
|
|
||||||
addEvent('app.load', self.scan)
|
addEvent('app.load', self.scan)
|
||||||
|
addEvent('app.load', self.checkSnatched)
|
||||||
|
|
||||||
fireEvent('schedule.interval', 'renamer.check_snatched', self.checkSnatched, minutes = self.conf('run_every'))
|
if self.conf('run_every') > 0:
|
||||||
fireEvent('schedule.interval', 'renamer.check_snatched_forced', self.scan, hours = 2)
|
fireEvent('schedule.interval', 'renamer.check_snatched', self.checkSnatched, minutes = self.conf('run_every'))
|
||||||
|
|
||||||
|
if self.conf('force_every') > 0:
|
||||||
|
fireEvent('schedule.interval', 'renamer.check_snatched_forced', self.scan, hours = self.conf('force_every'))
|
||||||
|
|
||||||
def scanView(self):
|
def scanView(self):
|
||||||
|
|
||||||
@@ -310,7 +314,10 @@ class Renamer(Plugin):
|
|||||||
elif release.status_id is snatched_status.get('id'):
|
elif release.status_id is snatched_status.get('id'):
|
||||||
if release.quality.id is group['meta_data']['quality']['id']:
|
if release.quality.id is group['meta_data']['quality']['id']:
|
||||||
log.debug('Marking release as downloaded')
|
log.debug('Marking release as downloaded')
|
||||||
release.status_id = downloaded_status.get('id')
|
try:
|
||||||
|
release.status_id = downloaded_status.get('id')
|
||||||
|
except Exception, e:
|
||||||
|
log.error('Failed marking release as finished: %s %s', (e, traceback.format_exc()))
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
# Remove leftover files
|
# Remove leftover files
|
||||||
@@ -334,6 +341,7 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
log.info('Removing "%s"', src)
|
log.info('Removing "%s"', src)
|
||||||
try:
|
try:
|
||||||
|
src = ss(src)
|
||||||
if os.path.isfile(src):
|
if os.path.isfile(src):
|
||||||
os.remove(src)
|
os.remove(src)
|
||||||
|
|
||||||
@@ -347,7 +355,10 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
# Delete leftover folder from older releases
|
# Delete leftover folder from older releases
|
||||||
for delete_folder in delete_folders:
|
for delete_folder in delete_folders:
|
||||||
self.deleteEmptyFolder(delete_folder, show_error = False)
|
try:
|
||||||
|
self.deleteEmptyFolder(delete_folder, show_error = False)
|
||||||
|
except Exception, e:
|
||||||
|
log.error('Failed to delete folder: %s %s', (e, traceback.format_exc()))
|
||||||
|
|
||||||
# Rename all files marked
|
# Rename all files marked
|
||||||
group['renamed_files'] = []
|
group['renamed_files'] = []
|
||||||
@@ -383,7 +394,10 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
# Notify on download, search for trailers etc
|
# Notify on download, search for trailers etc
|
||||||
download_message = 'Downloaded %s (%s)' % (movie_title, replacements['quality'])
|
download_message = 'Downloaded %s (%s)' % (movie_title, replacements['quality'])
|
||||||
fireEvent('renamer.after', message = download_message, group = group, in_order = True)
|
try:
|
||||||
|
fireEvent('renamer.after', message = download_message, group = group, in_order = True)
|
||||||
|
except:
|
||||||
|
log.error('Failed firing (some) of the renamer.after events: %s', traceback.format_exc())
|
||||||
|
|
||||||
# Break if CP wants to shut down
|
# Break if CP wants to shut down
|
||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
@@ -485,6 +499,7 @@ class Renamer(Plugin):
|
|||||||
return string.replace(' ', ' ').replace(' .', '.')
|
return string.replace(' ', ' ').replace(' .', '.')
|
||||||
|
|
||||||
def deleteEmptyFolder(self, folder, show_error = True):
|
def deleteEmptyFolder(self, folder, show_error = True):
|
||||||
|
folder = ss(folder)
|
||||||
|
|
||||||
loge = log.error if show_error else log.debug
|
loge = log.error if show_error else log.debug
|
||||||
for root, dirs, files in os.walk(folder):
|
for root, dirs, files in os.walk(folder):
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Scanner(Plugin):
|
|||||||
'media': 314572800, # 300MB
|
'media': 314572800, # 300MB
|
||||||
'trailer': 1048576, # 1MB
|
'trailer': 1048576, # 1MB
|
||||||
}
|
}
|
||||||
ignored_in_path = ['_unpack', '_failed_', '_unknown_', '_exists_', '_failed_remove_', '_failed_rename_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files
|
ignored_in_path = ['extracting', '_unpack', '_failed_', '_unknown_', '_exists_', '_failed_remove_', '_failed_rename_', '.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']
|
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
|
||||||
extensions = {
|
extensions = {
|
||||||
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'],
|
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'],
|
||||||
@@ -53,6 +53,20 @@ class Scanner(Plugin):
|
|||||||
'video': ['x264', 'h264', 'divx', 'xvid']
|
'video': ['x264', 'h264', 'divx', 'xvid']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audio_codec_map = {
|
||||||
|
0x2000: 'ac3',
|
||||||
|
0x2001: 'dts',
|
||||||
|
0x0055: 'mp3',
|
||||||
|
0x0050: 'mp2',
|
||||||
|
0x0001: 'pcm',
|
||||||
|
0x003: 'pcm',
|
||||||
|
0x77a1: 'tta1',
|
||||||
|
0x5756: 'wav',
|
||||||
|
0x6750: 'vorbis',
|
||||||
|
0xF1AC: 'flac',
|
||||||
|
0x00ff: 'aac',
|
||||||
|
}
|
||||||
|
|
||||||
source_media = {
|
source_media = {
|
||||||
'bluray': ['bluray', 'blu-ray', 'brrip', 'br-rip'],
|
'bluray': ['bluray', 'blu-ray', 'brrip', 'br-rip'],
|
||||||
'hddvd': ['hddvd', 'hd-dvd'],
|
'hddvd': ['hddvd', 'hd-dvd'],
|
||||||
@@ -75,7 +89,7 @@ class Scanner(Plugin):
|
|||||||
'()([ab])(\.....?)$' #*a.mkv
|
'()([ab])(\.....?)$' #*a.mkv
|
||||||
]
|
]
|
||||||
|
|
||||||
cp_imdb = '(\.cp\((?P<id>tt[0-9{7}]+)\))'
|
cp_imdb = '(.cp.(?P<id>tt[0-9{7}]+).)'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
@@ -327,11 +341,11 @@ class Scanner(Plugin):
|
|||||||
group['files']['movie'] = self.getMediaFiles(group['unsorted_files'])
|
group['files']['movie'] = self.getMediaFiles(group['unsorted_files'])
|
||||||
|
|
||||||
if len(group['files']['movie']) == 0:
|
if len(group['files']['movie']) == 0:
|
||||||
log.error('Couldn\t find any movie files for %s', identifier)
|
log.error('Couldn\'t find any movie files for %s', identifier)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
log.debug('Getting metadata for %s', identifier)
|
log.debug('Getting metadata for %s', identifier)
|
||||||
group['meta_data'] = self.getMetaData(group)
|
group['meta_data'] = self.getMetaData(group, folder = folder)
|
||||||
|
|
||||||
# Subtitle meta
|
# Subtitle meta
|
||||||
group['subtitle_language'] = self.getSubtitleLanguage(group) if not simple else {}
|
group['subtitle_language'] = self.getSubtitleLanguage(group) if not simple else {}
|
||||||
@@ -381,7 +395,7 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
return processed_movies
|
return processed_movies
|
||||||
|
|
||||||
def getMetaData(self, group):
|
def getMetaData(self, group, folder = ''):
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
files = list(group['files']['movie'])
|
files = list(group['files']['movie'])
|
||||||
@@ -407,10 +421,10 @@ class Scanner(Plugin):
|
|||||||
if not data['quality']:
|
if not data['quality']:
|
||||||
data['quality'] = fireEvent('quality.single', 'dvdr' if group['is_dvd'] else 'dvdrip', single = True)
|
data['quality'] = fireEvent('quality.single', 'dvdr' if group['is_dvd'] else 'dvdrip', single = True)
|
||||||
|
|
||||||
data['quality_type'] = 'HD' if data.get('resolution_width', 0) >= 1280 else 'SD'
|
data['quality_type'] = 'HD' if data.get('resolution_width', 0) >= 1280 or data['quality'].get('hd') else 'SD'
|
||||||
|
|
||||||
filename = re.sub('(.cp\(tt[0-9{7}]+\))', '', files[0])
|
filename = re.sub('(.cp\(tt[0-9{7}]+\))', '', files[0])
|
||||||
data['group'] = self.getGroup(filename)
|
data['group'] = self.getGroup(filename[len(folder):])
|
||||||
data['source'] = self.getSourceMedia(filename)
|
data['source'] = self.getSourceMedia(filename)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
@@ -419,9 +433,18 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
p = enzyme.parse(filename)
|
p = enzyme.parse(filename)
|
||||||
|
|
||||||
|
# Video codec
|
||||||
|
vc = ('h264' if p.video[0].codec == 'AVC1' else p.video[0].codec).lower()
|
||||||
|
|
||||||
|
# Audio codec
|
||||||
|
ac = p.audio[0].codec
|
||||||
|
try: ac = self.audio_codec_map.get(p.audio[0].codec)
|
||||||
|
except: pass
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'video': p.video[0].codec,
|
'video': vc,
|
||||||
'audio': p.audio[0].codec,
|
'audio': ac,
|
||||||
'resolution_width': tryInt(p.video[0].width),
|
'resolution_width': tryInt(p.video[0].width),
|
||||||
'resolution_height': tryInt(p.video[0].height),
|
'resolution_height': tryInt(p.video[0].height),
|
||||||
}
|
}
|
||||||
@@ -738,8 +761,8 @@ class Scanner(Plugin):
|
|||||||
|
|
||||||
def getGroup(self, file):
|
def getGroup(self, file):
|
||||||
try:
|
try:
|
||||||
match = re.search('-(?P<group>[A-Z0-9]+).', file, re.I)
|
match = re.findall('\-([A-Z0-9]+)[\.\/]', file, re.I)
|
||||||
return match.group('group') or ''
|
return match[-1] or ''
|
||||||
except:
|
except:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@@ -752,7 +775,7 @@ class Scanner(Plugin):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def findYear(self, text):
|
def findYear(self, text):
|
||||||
matches = re.search('(?P<year>[12]{1}[0-9]{3})', text)
|
matches = re.search('(?P<year>19[0-9]{2}|20[0-9]{2})', text)
|
||||||
if matches:
|
if matches:
|
||||||
return matches.group('year')
|
return matches.group('year')
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ class Score(Plugin):
|
|||||||
score += sizeScore(nzb['size'])
|
score += sizeScore(nzb['size'])
|
||||||
|
|
||||||
# Torrents only
|
# Torrents only
|
||||||
if nzb.get('seeds'):
|
if nzb.get('seeders'):
|
||||||
try:
|
try:
|
||||||
score += nzb.get('seeds') / 5
|
score += nzb.get('seeders') / 5
|
||||||
score += nzb.get('leechers') / 10
|
score += nzb.get('leechers') / 10
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ def sizeScore(size):
|
|||||||
|
|
||||||
|
|
||||||
def providerScore(provider):
|
def providerScore(provider):
|
||||||
if provider in ['NZBMatrix', 'Nzbs', 'Newzbin']:
|
if provider in ['OMGWTFNZBs', 'PassThePopcorn', 'SceneAccess', 'TorrentLeech']:
|
||||||
return 20
|
return 20
|
||||||
|
|
||||||
if provider in ['Newznab']:
|
if provider in ['Newznab']:
|
||||||
|
|||||||
@@ -24,18 +24,19 @@ config = [{
|
|||||||
'name': 'required_words',
|
'name': 'required_words',
|
||||||
'label': 'Required words',
|
'label': 'Required words',
|
||||||
'default': '',
|
'default': '',
|
||||||
'description': 'Ignore releases that don\'t contain at least one of these words.'
|
'placeholder': 'Example: DTS, AC3 & English',
|
||||||
|
'description': 'Ignore releases that don\'t contain at least one set of words. Sets are separated by "," and each word within a set must be separated with "&"'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'ignored_words',
|
'name': 'ignored_words',
|
||||||
'label': 'Ignored words',
|
'label': 'Ignored words',
|
||||||
'default': 'german, dutch, french, truefrench, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub',
|
'default': 'german, dutch, french, truefrench, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub, dksubs',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'preferred_method',
|
'name': 'preferred_method',
|
||||||
'label': 'First search',
|
'label': 'First search',
|
||||||
'description': 'Which of the methods do you prefer',
|
'description': 'Which of the methods do you prefer',
|
||||||
'default': 'nzb',
|
'default': 'both',
|
||||||
'type': 'dropdown',
|
'type': 'dropdown',
|
||||||
'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')],
|
'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ from couchpotato.api import addApiView
|
|||||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
||||||
from couchpotato.core.helpers.request import jsonified, getParam
|
from couchpotato.core.helpers.request import jsonified, getParam
|
||||||
from couchpotato.core.helpers.variable import md5, getTitle
|
from couchpotato.core.helpers.variable import md5, getTitle, splitString, \
|
||||||
|
possibleTitles
|
||||||
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 Movie, Release, ReleaseInfo
|
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
|
||||||
@@ -204,6 +205,10 @@ class Searcher(Plugin):
|
|||||||
|
|
||||||
|
|
||||||
for nzb in sorted_results:
|
for nzb in sorted_results:
|
||||||
|
if not quality_type.get('finish', False) and quality_type.get('wait_for', 0) > 0 and nzb.get('age') <= quality_type.get('wait_for', 0):
|
||||||
|
log.info('Ignored, waiting %s days: %s', (quality_type.get('wait_for'), nzb['name']))
|
||||||
|
continue
|
||||||
|
|
||||||
if nzb['status_id'] == ignored_status.get('id'):
|
if nzb['status_id'] == ignored_status.get('id'):
|
||||||
log.info('Ignored: %s', nzb['name'])
|
log.info('Ignored: %s', nzb['name'])
|
||||||
continue
|
continue
|
||||||
@@ -239,7 +244,7 @@ class Searcher(Plugin):
|
|||||||
filedata = None
|
filedata = None
|
||||||
if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
|
if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
|
||||||
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
|
||||||
if filedata is 'try_next':
|
if filedata == 'try_next':
|
||||||
return filedata
|
return filedata
|
||||||
|
|
||||||
successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
|
successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
|
||||||
@@ -293,49 +298,54 @@ class Searcher(Plugin):
|
|||||||
imdb_results = kwargs.get('imdb_results', False)
|
imdb_results = kwargs.get('imdb_results', False)
|
||||||
retention = Env.setting('retention', section = 'nzb')
|
retention = Env.setting('retention', section = 'nzb')
|
||||||
|
|
||||||
if nzb.get('seeds') is None and 0 < retention < nzb.get('age', 0):
|
if nzb.get('seeders') is None and 0 < retention < nzb.get('age', 0):
|
||||||
log.info('Wrong: Outside retention, age is %s, needs %s or lower: %s', (nzb['age'], retention, nzb['name']))
|
log.info2('Wrong: Outside retention, age is %s, needs %s or lower: %s', (nzb['age'], retention, nzb['name']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
movie_name = getTitle(movie['library'])
|
movie_name = getTitle(movie['library'])
|
||||||
movie_words = re.split('\W+', simplifyString(movie_name))
|
movie_words = re.split('\W+', simplifyString(movie_name))
|
||||||
nzb_name = simplifyString(nzb['name'])
|
nzb_name = simplifyString(nzb['name'])
|
||||||
nzb_words = re.split('\W+', nzb_name)
|
nzb_words = re.split('\W+', nzb_name)
|
||||||
required_words = [x.strip().lower() for x in self.conf('required_words').lower().split(',')]
|
required_words = splitString(self.conf('required_words').lower())
|
||||||
|
|
||||||
if self.conf('required_words') and not list(set(nzb_words) & set(required_words)):
|
req_match = 0
|
||||||
log.info("Wrong: Required word missing: %s" % nzb['name'])
|
for req_set in required_words:
|
||||||
|
req = splitString(req_set, '&')
|
||||||
|
req_match += len(list(set(nzb_words) & set(req))) == len(req)
|
||||||
|
|
||||||
|
if self.conf('required_words') and req_match == 0:
|
||||||
|
log.info2("Wrong: Required word missing: %s" % nzb['name'])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
ignored_words = [x.strip().lower() for x in self.conf('ignored_words').split(',')]
|
ignored_words = splitString(self.conf('ignored_words').lower())
|
||||||
blacklisted = list(set(nzb_words) & set(ignored_words))
|
blacklisted = list(set(nzb_words) & set(ignored_words) - set(movie_words))
|
||||||
if self.conf('ignored_words') and blacklisted:
|
if self.conf('ignored_words') and blacklisted:
|
||||||
log.info("Wrong: '%s' blacklisted words: %s" % (nzb['name'], ", ".join(blacklisted)))
|
log.info2("Wrong: '%s' blacklisted words: %s" % (nzb['name'], ", ".join(blacklisted)))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs', 'erotica', 'erotic']
|
pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs', 'erotica', 'erotic']
|
||||||
for p_tag in pron_tags:
|
pron_words = list(set(nzb_words) & set(pron_tags) - set(movie_words))
|
||||||
if p_tag in nzb_words and p_tag not in movie_words:
|
if pron_words:
|
||||||
log.info('Wrong: %s, probably pr0n', (nzb['name']))
|
log.info('Wrong: %s, probably pr0n', (nzb['name']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#qualities = fireEvent('quality.all', single = True)
|
#qualities = fireEvent('quality.all', single = True)
|
||||||
preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True)
|
preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True)
|
||||||
|
|
||||||
# Contains lower quality string
|
# Contains lower quality string
|
||||||
if self.containsOtherQuality(nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality):
|
if self.containsOtherQuality(nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality):
|
||||||
log.info('Wrong: %s, looking for %s', (nzb['name'], quality['label']))
|
log.info2('Wrong: %s, looking for %s', (nzb['name'], quality['label']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# File to small
|
# File to small
|
||||||
if nzb['size'] and preferred_quality['size_min'] > nzb['size']:
|
if nzb['size'] and preferred_quality['size_min'] > nzb['size']:
|
||||||
log.info('"%s" is too small to be %s. %sMB instead of the minimal of %sMB.', (nzb['name'], preferred_quality['label'], nzb['size'], preferred_quality['size_min']))
|
log.info2('Wrong: "%s" is too small to be %s. %sMB instead of the minimal of %sMB.', (nzb['name'], preferred_quality['label'], nzb['size'], preferred_quality['size_min']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# File to large
|
# File to large
|
||||||
if nzb['size'] and preferred_quality.get('size_max') < nzb['size']:
|
if nzb['size'] and preferred_quality.get('size_max') < nzb['size']:
|
||||||
log.info('"%s" is too large to be %s. %sMB instead of the maximum of %sMB.', (nzb['name'], preferred_quality['label'], nzb['size'], preferred_quality['size_max']))
|
log.info2('Wrong: "%s" is too large to be %s. %sMB instead of the maximum of %sMB.', (nzb['name'], preferred_quality['label'], nzb['size'], preferred_quality['size_max']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -353,20 +363,21 @@ class Searcher(Plugin):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# Check if nzb contains imdb link
|
# Check if nzb contains imdb link
|
||||||
if self.checkIMDB([nzb['description']], movie['library']['identifier']):
|
if self.checkIMDB([nzb.get('description', '')], movie['library']['identifier']):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
for movie_title in movie['library']['titles']:
|
for raw_title in movie['library']['titles']:
|
||||||
movie_words = re.split('\W+', simplifyString(movie_title['title']))
|
for movie_title in possibleTitles(raw_title['title']):
|
||||||
|
movie_words = re.split('\W+', simplifyString(movie_title))
|
||||||
|
|
||||||
if self.correctName(nzb['name'], movie_title['title']):
|
if self.correctName(nzb['name'], movie_title):
|
||||||
# if no IMDB link, at least check year range 1
|
# if no IMDB link, at least check year range 1
|
||||||
if len(movie_words) > 2 and self.correctYear([nzb['name']], movie['library']['year'], 1):
|
if len(movie_words) > 2 and self.correctYear([nzb['name']], movie['library']['year'], 1):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# if no IMDB link, at least check year
|
# if no IMDB link, at least check year
|
||||||
if len(movie_words) <= 2 and self.correctYear([nzb['name']], movie['library']['year'], 0):
|
if len(movie_words) <= 2 and self.correctYear([nzb['name']], movie['library']['year'], 0):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'" % (nzb['name'], movie_name, movie['library']['year']))
|
log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'" % (nzb['name'], movie_name, movie['library']['year']))
|
||||||
return False
|
return False
|
||||||
@@ -389,9 +400,14 @@ class Searcher(Plugin):
|
|||||||
if list(set(nzb_words) & set(quality['alternative'])):
|
if list(set(nzb_words) & set(quality['alternative'])):
|
||||||
found[quality['identifier']] = True
|
found[quality['identifier']] = True
|
||||||
|
|
||||||
|
# Try guessing via quality tags
|
||||||
|
guess = fireEvent('quality.guess', [nzb.get('name')], single = True)
|
||||||
|
if guess:
|
||||||
|
found[guess['identifier']] = True
|
||||||
|
|
||||||
# Hack for older movies that don't contain quality tag
|
# Hack for older movies that don't contain quality tag
|
||||||
year_name = fireEvent('scanner.name_year', name, single = True)
|
year_name = fireEvent('scanner.name_year', name, single = True)
|
||||||
if movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None):
|
if len(found) == 0 and movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None):
|
||||||
if size > 3000: # Assume dvdr
|
if size > 3000: # Assume dvdr
|
||||||
log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', (size))
|
log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', (size))
|
||||||
found['dvdr'] = True
|
found['dvdr'] = True
|
||||||
@@ -430,12 +446,16 @@ class Searcher(Plugin):
|
|||||||
def correctName(self, check_name, movie_name):
|
def correctName(self, check_name, movie_name):
|
||||||
|
|
||||||
check_names = [check_name]
|
check_names = [check_name]
|
||||||
try:
|
|
||||||
check_names.append(re.search(r'([\'"])[^\1]*\1', check_name).group(0))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for check_name in check_names:
|
# Match names between "
|
||||||
|
try: check_names.append(re.search(r'([\'"])[^\1]*\1', check_name).group(0))
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
# Match longest name between []
|
||||||
|
try: check_names.append(max(check_name.split('['), key = len))
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
for check_name in list(set(check_names)):
|
||||||
check_movie = fireEvent('scanner.name_year', check_name, single = True)
|
check_movie = fireEvent('scanner.name_year', check_name, single = True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class StatusPlugin(Plugin):
|
|||||||
'failed': 'Failed',
|
'failed': 'Failed',
|
||||||
'deleted': 'Deleted',
|
'deleted': 'Deleted',
|
||||||
'ignored': 'Ignored',
|
'ignored': 'Ignored',
|
||||||
|
'available': 'Available',
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ config = [{
|
|||||||
'type': 'dropdown',
|
'type': 'dropdown',
|
||||||
'values': [('1080P', '1080p'), ('720P', '720p'), ('480P', '480p')],
|
'values': [('1080P', '1080p'), ('720P', '720p'), ('480P', '480p')],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'name',
|
||||||
|
'label': 'Naming',
|
||||||
|
'default': '<filename>-trailer',
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Use <filename> to use above settings.'
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ class Trailer(Plugin):
|
|||||||
trailers = fireEvent('trailer.search', group = group, merge = True)
|
trailers = fireEvent('trailer.search', group = group, merge = True)
|
||||||
if not trailers or trailers == []:
|
if not trailers or trailers == []:
|
||||||
log.info('No trailers found for: %s', getTitle(group['library']))
|
log.info('No trailers found for: %s', getTitle(group['library']))
|
||||||
return
|
return False
|
||||||
|
|
||||||
for trailer in trailers.get(self.conf('quality'), []):
|
for trailer in trailers.get(self.conf('quality'), []):
|
||||||
destination = '%s-trailer.%s' % (self.getRootName(group), getExt(trailer))
|
filename = self.conf('name').replace('<filename>', group['filename']) + ('.%s' % getExt(trailer))
|
||||||
|
destination = os.path.join(group['destination_dir'], filename)
|
||||||
if not os.path.isfile(destination):
|
if not os.path.isfile(destination):
|
||||||
fireEvent('file.download', url = trailer, dest = destination, urlopen_kwargs = {'headers': {'User-Agent': 'Quicktime'}}, single = True)
|
fireEvent('file.download', url = trailer, dest = destination, urlopen_kwargs = {'headers': {'User-Agent': 'Quicktime'}}, single = True)
|
||||||
else:
|
else:
|
||||||
@@ -33,5 +34,5 @@ class Trailer(Plugin):
|
|||||||
# Download first and break
|
# Download first and break
|
||||||
break
|
break
|
||||||
|
|
||||||
def getRootName(self, data = {}):
|
return True
|
||||||
return os.path.join(data['destination_dir'], data['filename'])
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class Userscript(Plugin):
|
|||||||
addApiView('userscript.get/<random>/<path:filename>', self.getUserScript, static = True)
|
addApiView('userscript.get/<random>/<path:filename>', self.getUserScript, static = True)
|
||||||
addApiView('userscript', self.iFrame)
|
addApiView('userscript', self.iFrame)
|
||||||
addApiView('userscript.add_via_url', self.getViaUrl)
|
addApiView('userscript.add_via_url', self.getViaUrl)
|
||||||
|
addApiView('userscript.includes', self.getIncludes)
|
||||||
addApiView('userscript.bookmark', self.bookmark)
|
addApiView('userscript.bookmark', self.bookmark)
|
||||||
|
|
||||||
addEvent('userscript.get_version', self.getVersion)
|
addEvent('userscript.get_version', self.getVersion)
|
||||||
@@ -35,6 +36,13 @@ class Userscript(Plugin):
|
|||||||
|
|
||||||
return self.renderTemplate(__file__, 'bookmark.js', **params)
|
return self.renderTemplate(__file__, 'bookmark.js', **params)
|
||||||
|
|
||||||
|
def getIncludes(self):
|
||||||
|
|
||||||
|
return jsonified({
|
||||||
|
'includes': fireEvent('userscript.get_includes', merge = True),
|
||||||
|
'excludes': fireEvent('userscript.get_excludes', merge = True),
|
||||||
|
})
|
||||||
|
|
||||||
def getUserScript(self, random = '', filename = ''):
|
def getUserScript(self, random = '', filename = ''):
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Page.Wizard = new Class({
|
|||||||
headers: {
|
headers: {
|
||||||
'welcome': {
|
'welcome': {
|
||||||
'title': 'Welcome to the new CouchPotato',
|
'title': 'Welcome to the new CouchPotato',
|
||||||
'description': 'To get started, fill in each of the following settings as much as your can. <br />Maybe first start with importing your movies from the previous CouchPotato',
|
'description': 'To get started, fill in each of the following settings as much as you can. <br />Maybe first start with importing your movies from the previous CouchPotato',
|
||||||
'content': new Element('div', {
|
'content': new Element('div', {
|
||||||
'styles': {
|
'styles': {
|
||||||
'margin': '0 0 0 30px'
|
'margin': '0 0 0 30px'
|
||||||
@@ -37,7 +37,7 @@ Page.Wizard = new Class({
|
|||||||
},
|
},
|
||||||
'downloaders': {
|
'downloaders': {
|
||||||
'title': 'What download apps are you using?',
|
'title': 'What download apps are you using?',
|
||||||
'description': 'CP needs an external download app to work with. Choose one below. For more downloaders check settings after you have filled in the wizard. If your download app isn\'t in the list, use Blackhole.'
|
'description': 'CP needs an external download app to work with. Choose one below. For more downloaders check settings after you have filled in the wizard. If your download app isn\'t in the list, use the default Blackhole.'
|
||||||
},
|
},
|
||||||
'providers': {
|
'providers': {
|
||||||
'title': 'Are you registered at any of these sites?',
|
'title': 'Are you registered at any of these sites?',
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.providers.base import Provider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
import time
|
import time
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Automation(Plugin):
|
class Automation(Provider):
|
||||||
|
|
||||||
enabled_option = 'automation_enabled'
|
enabled_option = 'automation_enabled'
|
||||||
|
|
||||||
@@ -19,6 +19,9 @@ class Automation(Plugin):
|
|||||||
|
|
||||||
def _getMovies(self):
|
def _getMovies(self):
|
||||||
|
|
||||||
|
if self.isDisabled():
|
||||||
|
return
|
||||||
|
|
||||||
if not self.canCheck():
|
if not self.canCheck():
|
||||||
log.debug('Just checked, skipping %s', self.getName())
|
log.debug('Just checked, skipping %s', self.getName())
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import md5, tryInt
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.automation.base import Automation
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
import xml.etree.ElementTree as XMLTree
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -14,32 +13,24 @@ class Bluray(Automation, RSS):
|
|||||||
|
|
||||||
def getIMDBids(self):
|
def getIMDBids(self):
|
||||||
|
|
||||||
if self.isDisabled():
|
|
||||||
return
|
|
||||||
|
|
||||||
movies = []
|
movies = []
|
||||||
|
|
||||||
cache_key = 'bluray.%s' % md5(self.rss_url)
|
rss_movies = self.getRSSData(self.rss_url)
|
||||||
rss_data = self.getCache(cache_key, self.rss_url)
|
|
||||||
data = XMLTree.fromstring(rss_data)
|
|
||||||
|
|
||||||
if data is not None:
|
for movie in rss_movies:
|
||||||
rss_movies = self.getElements(data, 'channel/item')
|
name = self.getTextElement(movie, 'title').lower().split('blu-ray')[0].strip('(').rstrip()
|
||||||
|
year = self.getTextElement(movie, 'description').split('|')[1].strip('(').strip()
|
||||||
|
|
||||||
for movie in rss_movies:
|
if not name.find('/') == -1: # make sure it is not a double movie release
|
||||||
name = self.getTextElement(movie, "title").lower().split("blu-ray")[0].strip("(").rstrip()
|
continue
|
||||||
year = self.getTextElement(movie, "description").split("|")[1].strip("(").strip()
|
|
||||||
|
|
||||||
if not name.find("/") == -1: # make sure it is not a double movie release
|
if tryInt(year) < self.getMinimal('year'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if tryInt(year) < self.getMinimal('year'):
|
imdb = self.search(name, year)
|
||||||
continue
|
|
||||||
|
|
||||||
imdb = self.search(name, year)
|
if imdb:
|
||||||
|
if self.isMinimalMovie(imdb):
|
||||||
if imdb:
|
movies.append(imdb['imdb'])
|
||||||
if self.isMinimalMovie(imdb):
|
|
||||||
movies.append(imdb['imdb'])
|
|
||||||
|
|
||||||
return movies
|
return movies
|
||||||
|
|||||||
@@ -8,7 +8,4 @@ class CP(Automation):
|
|||||||
|
|
||||||
def getMovies(self):
|
def getMovies(self):
|
||||||
|
|
||||||
if self.isDisabled():
|
|
||||||
return
|
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import md5, getImdb
|
from couchpotato.core.helpers.variable import getImdb, splitString, tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.automation.base import Automation
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
import traceback
|
import traceback
|
||||||
import xml.etree.ElementTree as XMLTree
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -14,35 +13,26 @@ class IMDB(Automation, RSS):
|
|||||||
|
|
||||||
def getIMDBids(self):
|
def getIMDBids(self):
|
||||||
|
|
||||||
if self.isDisabled():
|
|
||||||
return
|
|
||||||
|
|
||||||
movies = []
|
movies = []
|
||||||
|
|
||||||
enablers = self.conf('automation_urls_use').split(',')
|
enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
|
||||||
|
urls = splitString(self.conf('automation_urls'))
|
||||||
|
|
||||||
index = -1
|
index = -1
|
||||||
for rss_url in self.conf('automation_urls').split(','):
|
for url in urls:
|
||||||
|
|
||||||
index += 1
|
index += 1
|
||||||
if not enablers[index]:
|
if not enablers[index]:
|
||||||
continue
|
continue
|
||||||
elif 'rss.imdb' not in rss_url:
|
|
||||||
log.error('This isn\'t the correct url.: %s', rss_url)
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cache_key = 'imdb.rss.%s' % md5(rss_url)
|
rss_data = self.getHTMLData(url)
|
||||||
|
imdbs = getImdb(rss_data, multiple = True)
|
||||||
|
|
||||||
rss_data = self.getCache(cache_key, rss_url)
|
for imdb in imdbs:
|
||||||
data = XMLTree.fromstring(rss_data)
|
|
||||||
rss_movies = self.getElements(data, 'channel/item')
|
|
||||||
|
|
||||||
for movie in rss_movies:
|
|
||||||
imdb = getImdb(self.getTextElement(movie, "link"))
|
|
||||||
movies.append(imdb)
|
movies.append(imdb)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed loading IMDB watchlist: %s %s', (rss_url, traceback.format_exc()))
|
log.error('Failed loading IMDB watchlist: %s %s', (url, traceback.format_exc()))
|
||||||
|
|
||||||
return movies
|
return movies
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
from .main import ITunes
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return ITunes()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'itunes',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'automation',
|
||||||
|
'name': 'itunes_automation',
|
||||||
|
'label': 'iTunes',
|
||||||
|
'description': 'From any <a href="http://itunes.apple.com/rss">iTunes</a> Store feed. Url should be the RSS link. (uses minimal requirements)',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'automation_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'automation_urls_use',
|
||||||
|
'label': 'Use',
|
||||||
|
'default': ',',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'automation_urls',
|
||||||
|
'label': 'url',
|
||||||
|
'type': 'combined',
|
||||||
|
'combine': ['automation_urls_use', 'automation_urls'],
|
||||||
|
'default': 'https://itunes.apple.com/rss/topmovies/limit=25/xml,',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
from couchpotato.core.helpers.rss import RSS
|
||||||
|
from couchpotato.core.helpers.variable import md5, splitString, tryInt
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
|
from xml.etree.ElementTree import QName
|
||||||
|
import datetime
|
||||||
|
import traceback
|
||||||
|
import xml.etree.ElementTree as XMLTree
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ITunes(Automation, RSS):
|
||||||
|
|
||||||
|
interval = 1800
|
||||||
|
|
||||||
|
def getIMDBids(self):
|
||||||
|
|
||||||
|
if self.isDisabled():
|
||||||
|
return
|
||||||
|
|
||||||
|
movies = []
|
||||||
|
|
||||||
|
enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
|
||||||
|
urls = splitString(self.conf('automation_urls'))
|
||||||
|
|
||||||
|
namespace = 'http://www.w3.org/2005/Atom'
|
||||||
|
namespaceIM = 'http://itunes.apple.com/rss'
|
||||||
|
|
||||||
|
index = -1
|
||||||
|
for url in urls:
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
if not enablers[index]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
cache_key = 'itunes.rss.%s' % md5(url)
|
||||||
|
rss_data = self.getCache(cache_key, url)
|
||||||
|
|
||||||
|
data = XMLTree.fromstring(rss_data)
|
||||||
|
|
||||||
|
if data is not None:
|
||||||
|
entry_tag = str(QName(namespace, 'entry'))
|
||||||
|
rss_movies = self.getElements(data, entry_tag)
|
||||||
|
|
||||||
|
for movie in rss_movies:
|
||||||
|
name_tag = str(QName(namespaceIM, 'name'))
|
||||||
|
name = self.getTextElement(movie, name_tag)
|
||||||
|
|
||||||
|
releaseDate_tag = str(QName(namespaceIM, 'releaseDate'))
|
||||||
|
releaseDateText = self.getTextElement(movie, releaseDate_tag)
|
||||||
|
year = datetime.datetime.strptime(releaseDateText, '%Y-%m-%dT00:00:00-07:00').strftime("%Y")
|
||||||
|
|
||||||
|
imdb = self.search(name, year)
|
||||||
|
|
||||||
|
if imdb and self.isMinimalMovie(imdb):
|
||||||
|
movies.append(imdb['imdb'])
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed loading iTunes rss feed: %s %s', (url, traceback.format_exc()))
|
||||||
|
|
||||||
|
return movies
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import md5
|
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.automation.base import Automation
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
import datetime
|
import datetime
|
||||||
import xml.etree.ElementTree as XMLTree
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -15,25 +13,17 @@ class Kinepolis(Automation, RSS):
|
|||||||
|
|
||||||
def getIMDBids(self):
|
def getIMDBids(self):
|
||||||
|
|
||||||
if self.isDisabled():
|
|
||||||
return
|
|
||||||
|
|
||||||
movies = []
|
movies = []
|
||||||
|
|
||||||
cache_key = 'kinepolis.%s' % md5(self.rss_url)
|
rss_movies = self.getRSSData(self.rss_url)
|
||||||
rss_data = self.getCache(cache_key, self.rss_url)
|
|
||||||
data = XMLTree.fromstring(rss_data)
|
|
||||||
|
|
||||||
if data is not None:
|
for movie in rss_movies:
|
||||||
rss_movies = self.getElements(data, 'channel/item')
|
name = self.getTextElement(movie, 'title')
|
||||||
|
year = datetime.datetime.now().strftime('%Y')
|
||||||
|
|
||||||
for movie in rss_movies:
|
imdb = self.search(name, year)
|
||||||
name = self.getTextElement(movie, "title")
|
|
||||||
year = datetime.datetime.now().strftime("%Y")
|
|
||||||
|
|
||||||
imdb = self.search(name, year)
|
if imdb and self.isMinimalMovie(imdb):
|
||||||
|
movies.append(imdb['imdb'])
|
||||||
if imdb and self.isMinimalMovie(imdb):
|
|
||||||
movies.append(imdb['imdb'])
|
|
||||||
|
|
||||||
return movies
|
return movies
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from .main import Moviemeter
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Moviemeter()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'moviemeter',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'automation',
|
||||||
|
'name': 'moviemeter_automation',
|
||||||
|
'label': 'Moviemeter',
|
||||||
|
'description': 'Imports movies from the current top 10 of moviemeter.nl. (uses minimal requirements)',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'automation_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
from couchpotato.core.event import fireEvent
|
||||||
|
from couchpotato.core.helpers.rss import RSS
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Moviemeter(Automation, RSS):
|
||||||
|
|
||||||
|
interval = 1800
|
||||||
|
rss_url = 'http://www.moviemeter.nl/rss/cinema'
|
||||||
|
|
||||||
|
def getIMDBids(self):
|
||||||
|
|
||||||
|
movies = []
|
||||||
|
|
||||||
|
rss_movies = self.getRSSData(self.rss_url)
|
||||||
|
|
||||||
|
for movie in rss_movies:
|
||||||
|
|
||||||
|
name_year = fireEvent('scanner.name_year', self.getTextElement(movie, 'title'), single = True)
|
||||||
|
imdb = self.search(name_year.get('name'), name_year.get('year'))
|
||||||
|
|
||||||
|
if imdb and self.isMinimalMovie(imdb):
|
||||||
|
movies.append(imdb['imdb'])
|
||||||
|
|
||||||
|
return movies
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import md5
|
from couchpotato.core.helpers.variable import tryInt, splitString
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.automation.base import Automation
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
from xml.etree.ElementTree import ParseError
|
|
||||||
import traceback
|
|
||||||
import xml.etree.ElementTree as XMLTree
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -16,39 +13,27 @@ class MoviesIO(Automation, RSS):
|
|||||||
|
|
||||||
def getIMDBids(self):
|
def getIMDBids(self):
|
||||||
|
|
||||||
if self.isDisabled():
|
|
||||||
return
|
|
||||||
|
|
||||||
movies = []
|
movies = []
|
||||||
|
|
||||||
enablers = self.conf('automation_urls_use').split(',')
|
enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
|
||||||
|
|
||||||
index = -1
|
index = -1
|
||||||
for rss_url in self.conf('automation_urls').split(','):
|
for rss_url in splitString(self.conf('automation_urls')):
|
||||||
|
|
||||||
index += 1
|
index += 1
|
||||||
if not enablers[index]:
|
if not enablers[index]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
rss_movies = self.getRSSData(rss_url, headers = {'Referer': ''})
|
||||||
cache_key = 'imdb.rss.%s' % md5(rss_url)
|
|
||||||
|
|
||||||
rss_data = self.getCache(cache_key, rss_url, headers = {'Referer': ''})
|
for movie in rss_movies:
|
||||||
data = XMLTree.fromstring(rss_data)
|
|
||||||
rss_movies = self.getElements(data, 'channel/item')
|
|
||||||
|
|
||||||
for movie in rss_movies:
|
nameyear = fireEvent('scanner.name_year', self.getTextElement(movie, 'title'), single = True)
|
||||||
|
imdb = self.search(nameyear.get('name'), nameyear.get('year'), imdb_only = True)
|
||||||
|
|
||||||
nameyear = fireEvent('scanner.name_year', self.getTextElement(movie, "title"), single = True)
|
if not imdb:
|
||||||
imdb = self.search(nameyear.get('name'), nameyear.get('year'), imdb_only = True)
|
continue
|
||||||
|
|
||||||
if not imdb:
|
movies.append(imdb)
|
||||||
continue
|
|
||||||
|
|
||||||
movies.append(imdb)
|
|
||||||
except ParseError:
|
|
||||||
log.debug('Failed loading Movies.io watchlist, probably empty: %s', (rss_url))
|
|
||||||
except:
|
|
||||||
log.error('Failed loading Movies.io watchlist: %s %s', (rss_url, traceback.format_exc()))
|
|
||||||
|
|
||||||
return movies
|
return movies
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.variable import md5, sha1
|
from couchpotato.core.helpers.variable import sha1
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.automation.base import Automation
|
from couchpotato.core.providers.automation.base import Automation
|
||||||
import base64
|
import base64
|
||||||
import json
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -25,9 +24,6 @@ class Trakt(Automation):
|
|||||||
|
|
||||||
def getIMDBids(self):
|
def getIMDBids(self):
|
||||||
|
|
||||||
if self.isDisabled():
|
|
||||||
return
|
|
||||||
|
|
||||||
movies = []
|
movies = []
|
||||||
for movie in self.getWatchlist():
|
for movie in self.getWatchlist():
|
||||||
movies.append(movie.get('imdb_id'))
|
movies.append(movie.get('imdb_id'))
|
||||||
@@ -38,22 +34,11 @@ class Trakt(Automation):
|
|||||||
method = (self.urls['watchlist'] % self.conf('automation_api_key')) + self.conf('automation_username')
|
method = (self.urls['watchlist'] % self.conf('automation_api_key')) + self.conf('automation_username')
|
||||||
return self.call(method)
|
return self.call(method)
|
||||||
|
|
||||||
|
|
||||||
def call(self, method_url):
|
def call(self, method_url):
|
||||||
|
|
||||||
try:
|
headers = {}
|
||||||
if self.conf('automation_password'):
|
if self.conf('automation_password'):
|
||||||
headers = {
|
headers['Authorization'] = 'Basic %s' % base64.encodestring('%s:%s' % (self.conf('automation_username'), self.conf('automation_password')))[:-1]
|
||||||
'Authorization': 'Basic %s' % base64.encodestring('%s:%s' % (self.conf('automation_username'), self.conf('automation_password')))[:-1]
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
headers = {}
|
|
||||||
|
|
||||||
cache_key = 'trakt.%s' % md5(method_url)
|
data = self.getJsonData(self.urls['base'] + method_url, headers = headers)
|
||||||
json_string = self.getCache(cache_key, self.urls['base'] + method_url, headers = headers)
|
return data if data else []
|
||||||
if json_string:
|
|
||||||
return json.loads(json_string)
|
|
||||||
except:
|
|
||||||
log.error('Failed to get data from trakt, check your login.')
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.variable import tryFloat
|
from couchpotato.core.helpers.variable import tryFloat, mergeDicts, md5, \
|
||||||
|
possibleTitles, getTitle
|
||||||
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.environment import Env
|
from couchpotato.environment import Env
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
import cookielib
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
import traceback
|
||||||
|
import urllib2
|
||||||
|
import xml.etree.ElementTree as XMLTree
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -38,6 +44,34 @@ class Provider(Plugin):
|
|||||||
|
|
||||||
return self.is_available.get(host, False)
|
return self.is_available.get(host, False)
|
||||||
|
|
||||||
|
def getJsonData(self, url, **kwargs):
|
||||||
|
|
||||||
|
data = self.getCache(md5(url), url, **kwargs)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
return json.loads(data)
|
||||||
|
except:
|
||||||
|
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def getRSSData(self, url, **kwargs):
|
||||||
|
|
||||||
|
data = self.getCache(md5(url), url, **kwargs)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
data = XMLTree.fromstring(data)
|
||||||
|
return self.getElements(data, 'channel/item')
|
||||||
|
except:
|
||||||
|
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def getHTMLData(self, url, **kwargs):
|
||||||
|
return self.getCache(md5(url), url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class YarrProvider(Provider):
|
class YarrProvider(Provider):
|
||||||
|
|
||||||
@@ -47,22 +81,76 @@ class YarrProvider(Provider):
|
|||||||
sizeMb = ['mb', 'mib']
|
sizeMb = ['mb', 'mib']
|
||||||
sizeKb = ['kb', 'kib']
|
sizeKb = ['kb', 'kib']
|
||||||
|
|
||||||
|
login_opener = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('provider.belongs_to', self.belongsTo)
|
addEvent('provider.belongs_to', self.belongsTo)
|
||||||
|
|
||||||
addEvent('%s.search' % self.type, self.search)
|
|
||||||
addEvent('yarr.search', self.search)
|
addEvent('yarr.search', self.search)
|
||||||
|
|
||||||
addEvent('nzb.feed', self.feed)
|
def login(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
cookiejar = cookielib.CookieJar()
|
||||||
|
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar))
|
||||||
|
urllib2.install_opener(opener)
|
||||||
|
log.info2('Logging into %s', self.urls['login'])
|
||||||
|
f = opener.open(self.urls['login'], self.getLoginParams())
|
||||||
|
output = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
if self.loginSuccess(output):
|
||||||
|
self.login_opener = opener
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
log.error('Failed to login %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def loginSuccess(self, output):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def loginDownload(self, url = '', nzb_id = ''):
|
||||||
|
try:
|
||||||
|
if not self.login_opener and not self.login():
|
||||||
|
log.error('Failed downloading from %s', self.getName())
|
||||||
|
return self.urlopen(url, opener = self.login_opener)
|
||||||
|
except:
|
||||||
|
log.error('Failed downloading from %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
|
def getLoginParams(self):
|
||||||
|
return ''
|
||||||
|
|
||||||
def download(self, url = '', nzb_id = ''):
|
def download(self, url = '', nzb_id = ''):
|
||||||
return self.urlopen(url)
|
try:
|
||||||
|
return self.urlopen(url, headers = {'User-Agent': Env.getIdentifier()}, show_error = False)
|
||||||
|
except:
|
||||||
|
log.error('Failed getting nzb from %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
def feed(self):
|
return 'try_next'
|
||||||
return []
|
|
||||||
|
|
||||||
def search(self, movie, quality):
|
def search(self, movie, quality):
|
||||||
return []
|
|
||||||
|
if self.isDisabled():
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Login if needed
|
||||||
|
if self.urls.get('login') and (not self.login_opener and not self.login()):
|
||||||
|
log.error('Failed to login to: %s', self.getName())
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Create result container
|
||||||
|
imdb_results = hasattr(self, '_search')
|
||||||
|
results = ResultList(self, movie, quality, imdb_results = imdb_results)
|
||||||
|
|
||||||
|
# Do search based on imdb id
|
||||||
|
if imdb_results:
|
||||||
|
self._search(movie, quality, results)
|
||||||
|
# Search possible titles
|
||||||
|
else:
|
||||||
|
for title in possibleTitles(getTitle(movie['library'])):
|
||||||
|
self._searchOnTitle(title, movie, quality, results)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
def belongsTo(self, url, provider = None, host = None):
|
def belongsTo(self, url, provider = None, host = None):
|
||||||
try:
|
try:
|
||||||
@@ -110,5 +198,65 @@ class YarrProvider(Provider):
|
|||||||
|
|
||||||
return [self.cat_backup_id]
|
return [self.cat_backup_id]
|
||||||
|
|
||||||
def found(self, new):
|
|
||||||
log.info('Found: score(%(score)s) on %(provider)s: %(name)s', new)
|
class ResultList(list):
|
||||||
|
|
||||||
|
result_ids = None
|
||||||
|
provider = None
|
||||||
|
movie = None
|
||||||
|
quality = None
|
||||||
|
|
||||||
|
def __init__(self, provider, movie, quality, **kwargs):
|
||||||
|
|
||||||
|
self.result_ids = []
|
||||||
|
self.provider = provider
|
||||||
|
self.movie = movie
|
||||||
|
self.quality = quality
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
super(ResultList, self).__init__()
|
||||||
|
|
||||||
|
def extend(self, results):
|
||||||
|
for r in results:
|
||||||
|
self.append(r)
|
||||||
|
|
||||||
|
def append(self, result):
|
||||||
|
|
||||||
|
new_result = self.fillResult(result)
|
||||||
|
|
||||||
|
is_correct_movie = fireEvent('searcher.correct_movie',
|
||||||
|
nzb = new_result, movie = self.movie, quality = self.quality,
|
||||||
|
imdb_results = self.kwargs.get('imdb_results', False), single = True)
|
||||||
|
|
||||||
|
if is_correct_movie and new_result['id'] not in self.result_ids:
|
||||||
|
new_result['score'] += fireEvent('score.calculate', new_result, self.movie, single = True)
|
||||||
|
|
||||||
|
self.found(new_result)
|
||||||
|
self.result_ids.append(result['id'])
|
||||||
|
|
||||||
|
super(ResultList, self).append(new_result)
|
||||||
|
|
||||||
|
def fillResult(self, result):
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'id': 0,
|
||||||
|
'type': self.provider.type,
|
||||||
|
'provider': self.provider.getName(),
|
||||||
|
'download': self.provider.download,
|
||||||
|
'url': '',
|
||||||
|
'name': '',
|
||||||
|
'age': 0,
|
||||||
|
'size': 0,
|
||||||
|
'description': '',
|
||||||
|
'score': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeDicts(defaults, result)
|
||||||
|
|
||||||
|
def found(self, new_result):
|
||||||
|
if not new_result.get('provider_extra'):
|
||||||
|
new_result['provider_extra'] = ''
|
||||||
|
else:
|
||||||
|
new_result['provider_extra'] = ', %s' % new_result['provider_extra']
|
||||||
|
|
||||||
|
log.info('Found: score(%(score)s) on %(provider)s%(provider_extra)s: %(name)s', new_result)
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ config = [{
|
|||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': '<strong>%s</strong> is the rootname of the movie. For example "/path/to/movie cd1.mkv" will be "/path/to/movie"'
|
'description': '<strong>%s</strong> is the rootname of the movie. For example "/path/to/movie cd1.mkv" will be "/path/to/movie"'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_url_only',
|
||||||
|
'label': 'Only IMDB URL',
|
||||||
|
'default': False,
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Create a nfo with only the IMDB url inside',
|
||||||
|
'type': 'bool',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'meta_fanart',
|
'name': 'meta_fanart',
|
||||||
'label': 'Fanart',
|
'label': 'Fanart',
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ class XBMC(MetaDataBase):
|
|||||||
return os.path.join(root, basename.replace('%s', name))
|
return os.path.join(root, basename.replace('%s', name))
|
||||||
|
|
||||||
def getNfo(self, movie_info = {}, data = {}):
|
def getNfo(self, movie_info = {}, data = {}):
|
||||||
|
|
||||||
|
# return imdb url only
|
||||||
|
if self.conf('meta_url_only'):
|
||||||
|
return 'http://www.imdb.com/title/%s/' % toUnicode(data['library']['identifier'])
|
||||||
|
|
||||||
nfoxml = Element('movie')
|
nfoxml = Element('movie')
|
||||||
|
|
||||||
# Title
|
# Title
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
from .main import IMDBAPI
|
|
||||||
|
|
||||||
def start():
|
|
||||||
return IMDBAPI()
|
|
||||||
|
|
||||||
config = []
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from .main import OMDBAPI
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return OMDBAPI()
|
||||||
|
|
||||||
|
config = []
|
||||||
+5
-5
@@ -10,11 +10,11 @@ import traceback
|
|||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class IMDBAPI(MovieProvider):
|
class OMDBAPI(MovieProvider):
|
||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'search': 'http://www.imdbapi.com/?%s',
|
'search': 'http://www.omdbapi.com/?%s',
|
||||||
'info': 'http://www.imdbapi.com/?i=%s',
|
'info': 'http://www.omdbapi.com/?i=%s',
|
||||||
}
|
}
|
||||||
|
|
||||||
http_time_between_calls = 0
|
http_time_between_calls = 0
|
||||||
@@ -32,7 +32,7 @@ class IMDBAPI(MovieProvider):
|
|||||||
'name': q
|
'name': q
|
||||||
}
|
}
|
||||||
|
|
||||||
cache_key = 'imdbapi.cache.%s' % q
|
cache_key = 'omdbapi.cache.%s' % q
|
||||||
cached = self.getCache(cache_key, self.urls['search'] % tryUrlencode({'t': name_year.get('name'), 'y': name_year.get('year', '')}), timeout = 3)
|
cached = self.getCache(cache_key, self.urls['search'] % tryUrlencode({'t': name_year.get('name'), 'y': name_year.get('year', '')}), timeout = 3)
|
||||||
|
|
||||||
if cached:
|
if cached:
|
||||||
@@ -50,7 +50,7 @@ class IMDBAPI(MovieProvider):
|
|||||||
if not identifier:
|
if not identifier:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
cache_key = 'imdbapi.cache.%s' % identifier
|
cache_key = 'omdbapi.cache.%s' % identifier
|
||||||
cached = self.getCache(cache_key, self.urls['info'] % identifier, timeout = 3)
|
cached = self.getCache(cache_key, self.urls['info'] % identifier, timeout = 3)
|
||||||
|
|
||||||
if cached:
|
if cached:
|
||||||
@@ -3,6 +3,7 @@ from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
|||||||
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 libs.themoviedb import tmdb
|
from libs.themoviedb import tmdb
|
||||||
|
import traceback
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -61,7 +62,12 @@ class TheMovieDb(MovieProvider):
|
|||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
log.debug('Searching for movie: %s', q)
|
log.debug('Searching for movie: %s', q)
|
||||||
raw = tmdb.search(search_string)
|
|
||||||
|
raw = None
|
||||||
|
try:
|
||||||
|
raw = tmdb.search(search_string)
|
||||||
|
except:
|
||||||
|
log.error('Failed searching TMDB for "%s": %s', (search_string, traceback.format_exc()))
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
if raw:
|
if raw:
|
||||||
|
|||||||
+5
-6
@@ -1,21 +1,20 @@
|
|||||||
from .main import Mysterbin
|
from .main import BinSearch
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
return Mysterbin()
|
return BinSearch()
|
||||||
|
|
||||||
config = [{
|
config = [{
|
||||||
'name': 'mysterbin',
|
'name': 'binsearch',
|
||||||
'groups': [
|
'groups': [
|
||||||
{
|
{
|
||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'nzb_providers',
|
'subtab': 'nzb_providers',
|
||||||
'name': 'Mysterbin',
|
'name': 'binsearch',
|
||||||
'description': 'Free provider, less accurate. See <a href="https://www.mysterbin.com/">Mysterbin</a>',
|
'description': 'Free provider, less accurate. See <a href="https://www.binsearch.info/">BinSearch</a>',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
'default': False,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BinSearch(NZBProvider):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'download': 'https://www.binsearch.info/fcgi/nzb.fcgi?q=%s',
|
||||||
|
'detail': 'https://www.binsearch.info%s',
|
||||||
|
'search': 'https://www.binsearch.info/index.php?%s',
|
||||||
|
}
|
||||||
|
|
||||||
|
http_time_between_calls = 4 # Seconds
|
||||||
|
|
||||||
|
def _search(self, movie, quality, results):
|
||||||
|
|
||||||
|
q = '%s %s' % (movie['library']['identifier'], quality.get('identifier'))
|
||||||
|
arguments = tryUrlencode({
|
||||||
|
'q': q,
|
||||||
|
'm': 'n',
|
||||||
|
'max': 250,
|
||||||
|
'adv_age': Env.setting('retention', 'nzb'),
|
||||||
|
'adv_sort': 'date',
|
||||||
|
'adv_col': 'on',
|
||||||
|
'adv_nfo': 'on',
|
||||||
|
'minsize': quality.get('size_min'),
|
||||||
|
'maxsize': quality.get('size_max'),
|
||||||
|
})
|
||||||
|
|
||||||
|
data = self.getHTMLData(self.urls['search'] % arguments)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
|
||||||
|
html = BeautifulSoup(data)
|
||||||
|
main_table = html.find('table', attrs = {'id':'r2'})
|
||||||
|
|
||||||
|
if not main_table:
|
||||||
|
return
|
||||||
|
|
||||||
|
items = main_table.find_all('tr')
|
||||||
|
|
||||||
|
for row in items:
|
||||||
|
title = row.find('span', attrs = {'class':'s'})
|
||||||
|
|
||||||
|
if not title: continue
|
||||||
|
|
||||||
|
nzb_id = row.find('input', attrs = {'type':'checkbox'})['name']
|
||||||
|
info = row.find('span', attrs = {'class':'d'})
|
||||||
|
size_match = re.search('size:.(?P<size>[0-9\.]+.[GMB]+)', info.text)
|
||||||
|
|
||||||
|
def extra_check(item):
|
||||||
|
parts = re.search('available:.(?P<parts>\d+)./.(?P<total>\d+)', info.text)
|
||||||
|
total = tryInt(parts.group('total'))
|
||||||
|
parts = tryInt(parts.group('parts'))
|
||||||
|
|
||||||
|
if (total / parts) < 0.95 or ((total / parts) >= 0.95 and not 'par2' in info.text.lower()):
|
||||||
|
log.info2('Wrong: \'%s\', not complete: %s out of %s', (item['name'], parts, total))
|
||||||
|
return False
|
||||||
|
|
||||||
|
if 'requires password' in info.text.lower():
|
||||||
|
log.info2('Wrong: \'%s\', passworded', (item['name']))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
'id': nzb_id,
|
||||||
|
'name': title.text,
|
||||||
|
'age': tryInt(re.search('(?P<size>\d+d)', row.find_all('td')[-1:][0].text).group('size')[:-1]),
|
||||||
|
'size': self.parseSize(size_match.group('size')),
|
||||||
|
'url': self.urls['download'] % nzb_id,
|
||||||
|
'detail_url': self.urls['detail'] % info.find('a')['href'],
|
||||||
|
'extra_check': extra_check
|
||||||
|
})
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed to parse HTML response from BinSearch: %s', traceback.format_exc())
|
||||||
|
|
||||||
|
def download(self, url = '', nzb_id = ''):
|
||||||
|
|
||||||
|
params = {'action': 'nzb'}
|
||||||
|
params[nzb_id] = 'on'
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.urlopen(url, params = params, show_error = False)
|
||||||
|
except:
|
||||||
|
log.error('Failed getting nzb from %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
|
return 'try_next'
|
||||||
|
|
||||||
+5
-6
@@ -1,17 +1,16 @@
|
|||||||
from .main import Newzbin
|
from .main import FTDWorld
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
return Newzbin()
|
return FTDWorld()
|
||||||
|
|
||||||
config = [{
|
config = [{
|
||||||
'name': 'newzbin',
|
'name': 'ftdworld',
|
||||||
'groups': [
|
'groups': [
|
||||||
{
|
{
|
||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'nzb_providers',
|
'subtab': 'nzb_providers',
|
||||||
'name': 'newzbin',
|
'name': 'FTDWorld',
|
||||||
'description': 'See <a href="https://www.newzbin2.es/">Newzbin</a>',
|
'description': 'Free provider, less accurate. See <a href="http://ftdworld.net">FTDWorld</a>',
|
||||||
'wizard': True,
|
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
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.nzb.base import NZBProvider
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
from dateutil.parser import parse
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FTDWorld(NZBProvider):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'search': 'http://ftdworld.net/api/index.php?%s',
|
||||||
|
'detail': 'http://ftdworld.net/spotinfo.php?id=%s',
|
||||||
|
'download': 'http://ftdworld.net/cgi-bin/nzbdown.pl?fileID=%s',
|
||||||
|
'login': 'http://ftdworld.net/index.php',
|
||||||
|
}
|
||||||
|
|
||||||
|
http_time_between_calls = 3 #seconds
|
||||||
|
|
||||||
|
cat_ids = [
|
||||||
|
([4, 11], ['dvdr']),
|
||||||
|
([1], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr', 'brrip']),
|
||||||
|
([7, 10, 13, 14], ['bd50', '720p', '1080p']),
|
||||||
|
]
|
||||||
|
cat_backup_id = 1
|
||||||
|
|
||||||
|
def _searchOnTitle(self, title, movie, quality, results):
|
||||||
|
|
||||||
|
q = '"%s" %s' % (title, movie['library']['year'])
|
||||||
|
|
||||||
|
params = tryUrlencode({
|
||||||
|
'ctitle': q,
|
||||||
|
'customQuery': 'usr',
|
||||||
|
'cage': Env.setting('retention', 'nzb'),
|
||||||
|
'csizemin': quality.get('size_min'),
|
||||||
|
'csizemax': quality.get('size_max'),
|
||||||
|
'ccategory': 14,
|
||||||
|
'ctype': ','.join([str(x) for x in self.getCatId(quality['identifier'])]),
|
||||||
|
})
|
||||||
|
|
||||||
|
data = self.getJsonData(self.urls['search'] % params, opener = self.login_opener)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
|
||||||
|
if data.get('numRes') == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
for item in data.get('data'):
|
||||||
|
|
||||||
|
nzb_id = tryInt(item.get('id'))
|
||||||
|
results.append({
|
||||||
|
'id': nzb_id,
|
||||||
|
'name': toUnicode(item.get('Title')),
|
||||||
|
'age': self.calculateAge(tryInt(item.get('Created'))),
|
||||||
|
'url': self.urls['download'] % nzb_id,
|
||||||
|
'download': self.loginDownload,
|
||||||
|
'detail_url': self.urls['detail'] % nzb_id,
|
||||||
|
'score': (tryInt(item.get('webPlus', 0)) - tryInt(item.get('webMin', 0))) * 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed to parse HTML response from FTDWorld: %s', traceback.format_exc())
|
||||||
|
|
||||||
|
def getLoginParams(self):
|
||||||
|
return tryUrlencode({
|
||||||
|
'userlogin': self.conf('username'),
|
||||||
|
'passlogin': self.conf('password'),
|
||||||
|
'submit': 'Log In',
|
||||||
|
})
|
||||||
|
|
||||||
|
def loginSuccess(self, output):
|
||||||
|
return 'password is incorrect' not in output
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
from bs4 import BeautifulSoup
|
|
||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode, \
|
|
||||||
simplifyString
|
|
||||||
from couchpotato.core.helpers.variable import tryInt, getTitle
|
|
||||||
from couchpotato.core.logger import CPLog
|
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
|
||||||
from couchpotato.environment import Env
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Mysterbin(NZBProvider):
|
|
||||||
|
|
||||||
urls = {
|
|
||||||
'search': 'https://www.mysterbin.com/advsearch?%s',
|
|
||||||
'download': 'https://www.mysterbin.com/nzb?c=%s',
|
|
||||||
'nfo': 'https://www.mysterbin.com/nfo?c=%s',
|
|
||||||
}
|
|
||||||
|
|
||||||
http_time_between_calls = 1 #seconds
|
|
||||||
|
|
||||||
def search(self, movie, quality):
|
|
||||||
|
|
||||||
results = []
|
|
||||||
if self.isDisabled():
|
|
||||||
return results
|
|
||||||
|
|
||||||
q = '"%s" %s %s' % (simplifyString(getTitle(movie['library'])), movie['library']['year'], quality.get('identifier'))
|
|
||||||
for ignored in Env.setting('ignored_words', 'searcher').split(','):
|
|
||||||
if len(q) + len(ignored.strip()) > 126:
|
|
||||||
break
|
|
||||||
q = '%s -%s' % (q, ignored.strip())
|
|
||||||
|
|
||||||
params = {
|
|
||||||
'q': q,
|
|
||||||
'match': 'normal',
|
|
||||||
'minSize': quality.get('size_min'),
|
|
||||||
'maxSize': quality.get('size_max'),
|
|
||||||
'complete': 2,
|
|
||||||
'maxAge': Env.setting('retention', 'nzb'),
|
|
||||||
'nopasswd': 'on',
|
|
||||||
}
|
|
||||||
|
|
||||||
cache_key = 'mysterbin.%s.%s.%s' % (movie['library']['identifier'], quality.get('identifier'), q)
|
|
||||||
data = self.getCache(cache_key, self.urls['search'] % tryUrlencode(params))
|
|
||||||
if data:
|
|
||||||
|
|
||||||
try:
|
|
||||||
html = BeautifulSoup(data)
|
|
||||||
resultable = html.find('table', attrs = {'class':'t'})
|
|
||||||
for result in resultable.find_all('tr'):
|
|
||||||
|
|
||||||
try:
|
|
||||||
myster_id = result.find('input', attrs = {'class': 'check4nzb'})['value']
|
|
||||||
|
|
||||||
# Age
|
|
||||||
age = ''
|
|
||||||
for temp in result.find('td', attrs = {'class': 'cdetail'}).find_all(text = True):
|
|
||||||
if 'days' in temp:
|
|
||||||
age = tryInt(temp.split(' ')[0])
|
|
||||||
break
|
|
||||||
|
|
||||||
# size
|
|
||||||
size = None
|
|
||||||
for temp in result.find('div', attrs = {'class': 'cdetail'}).find_all(text = True):
|
|
||||||
if 'gb' in temp.lower() or 'mb' in temp.lower() or 'kb' in temp.lower():
|
|
||||||
size = self.parseSize(temp)
|
|
||||||
break
|
|
||||||
|
|
||||||
description = ''
|
|
||||||
if result.find('a', text = 'View NFO'):
|
|
||||||
description = toUnicode(self.getCache('mysterbin.%s' % myster_id, self.urls['nfo'] % myster_id, cache_timeout = 25920000))
|
|
||||||
|
|
||||||
new = {
|
|
||||||
'id': myster_id,
|
|
||||||
'name': ''.join(result.find('span', attrs = {'class': 'cname'}).find_all(text = True)),
|
|
||||||
'type': 'nzb',
|
|
||||||
'provider': self.getName(),
|
|
||||||
'age': age,
|
|
||||||
'size': size,
|
|
||||||
'url': self.urls['download'] % myster_id,
|
|
||||||
'description': description,
|
|
||||||
'download': self.download,
|
|
||||||
'check_nzb': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
|
||||||
nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = False, single = True)
|
|
||||||
if is_correct_movie:
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return results
|
|
||||||
except AttributeError:
|
|
||||||
log.debug('No search results found.')
|
|
||||||
|
|
||||||
return results
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
|
||||||
from couchpotato.core.helpers.rss import RSS
|
|
||||||
from couchpotato.core.logger import CPLog
|
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
|
||||||
from dateutil.parser import parse
|
|
||||||
import base64
|
|
||||||
import time
|
|
||||||
import xml.etree.ElementTree as XMLTree
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Newzbin(NZBProvider, RSS):
|
|
||||||
|
|
||||||
urls = {
|
|
||||||
'download': 'https://www.newzbin2.es/api/dnzb/',
|
|
||||||
'search': 'https://www.newzbin2.es/search/',
|
|
||||||
}
|
|
||||||
|
|
||||||
format_ids = {
|
|
||||||
2: ['scr'],
|
|
||||||
1: ['cam'],
|
|
||||||
4: ['tc'],
|
|
||||||
8: ['ts'],
|
|
||||||
1024: ['r5'],
|
|
||||||
}
|
|
||||||
cat_ids = [
|
|
||||||
([262144], ['bd50']),
|
|
||||||
([2097152], ['1080p']),
|
|
||||||
([524288], ['720p']),
|
|
||||||
([262144], ['brrip']),
|
|
||||||
([2], ['dvdr']),
|
|
||||||
]
|
|
||||||
cat_backup_id = -1
|
|
||||||
|
|
||||||
http_time_between_calls = 3 # Seconds
|
|
||||||
|
|
||||||
def search(self, movie, quality):
|
|
||||||
|
|
||||||
results = []
|
|
||||||
if self.isDisabled():
|
|
||||||
return results
|
|
||||||
|
|
||||||
format_id = self.getFormatId(type)
|
|
||||||
cat_id = self.getCatId(type)
|
|
||||||
|
|
||||||
arguments = tryUrlencode({
|
|
||||||
'searchaction': 'Search',
|
|
||||||
'u_url_posts_only': '0',
|
|
||||||
'u_show_passworded': '0',
|
|
||||||
'q_url': 'imdb.com/title/' + movie['library']['identifier'],
|
|
||||||
'sort': 'ps_totalsize',
|
|
||||||
'order': 'asc',
|
|
||||||
'u_post_results_amt': '100',
|
|
||||||
'feed': 'rss',
|
|
||||||
'category': '6',
|
|
||||||
'ps_rb_video_format': str(cat_id),
|
|
||||||
'ps_rb_source': str(format_id),
|
|
||||||
'u_post_larger_than': quality.get('size_min'),
|
|
||||||
'u_post_smaller_than': quality.get('size_max'),
|
|
||||||
})
|
|
||||||
|
|
||||||
url = "%s?%s" % (self.urls['search'], arguments)
|
|
||||||
cache_key = str('newzbin.%s.%s.%s' % (movie['library']['identifier'], str(format_id), str(cat_id)))
|
|
||||||
|
|
||||||
data = self.getCache(cache_key)
|
|
||||||
if not data:
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'Authorization': "Basic %s" % base64.encodestring('%s:%s' % (self.conf('username'), self.conf('password')))[:-1]
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
data = self.urlopen(url, headers = headers)
|
|
||||||
self.setCache(cache_key, data)
|
|
||||||
except:
|
|
||||||
return results
|
|
||||||
|
|
||||||
if data:
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
data = XMLTree.fromstring(data)
|
|
||||||
nzbs = self.getElements(data, 'channel/item')
|
|
||||||
except Exception, e:
|
|
||||||
log.debug('%s, %s', (self.getName(), e))
|
|
||||||
return results
|
|
||||||
|
|
||||||
for nzb in nzbs:
|
|
||||||
|
|
||||||
title = self.getTextElement(nzb, "title")
|
|
||||||
if 'error' in title.lower(): continue
|
|
||||||
|
|
||||||
REPORT_NS = 'http://www.newzbin2.es/DTD/2007/feeds/report/';
|
|
||||||
|
|
||||||
# Add attributes to name
|
|
||||||
try:
|
|
||||||
for attr in nzb.find('{%s}attributes' % REPORT_NS):
|
|
||||||
title += ' ' + attr.text
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
id = int(self.getTextElement(nzb, '{%s}id' % REPORT_NS))
|
|
||||||
size = str(int(self.getTextElement(nzb, '{%s}size' % REPORT_NS)) / 1024 / 1024) + ' mb'
|
|
||||||
date = str(self.getTextElement(nzb, '{%s}postdate' % REPORT_NS))
|
|
||||||
|
|
||||||
new = {
|
|
||||||
'id': id,
|
|
||||||
'type': 'nzb',
|
|
||||||
'provider': self.getName(),
|
|
||||||
'name': title,
|
|
||||||
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
|
||||||
'size': self.parseSize(size),
|
|
||||||
'url': str(self.getTextElement(nzb, '{%s}nzb' % REPORT_NS)),
|
|
||||||
'download': self.download,
|
|
||||||
'detail_url': str(self.getTextElement(nzb, 'link')),
|
|
||||||
'description': self.getTextElement(nzb, "description"),
|
|
||||||
'check_nzb': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
|
||||||
nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = True, single = True)
|
|
||||||
if is_correct_movie:
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
|
|
||||||
return results
|
|
||||||
except SyntaxError:
|
|
||||||
log.error('Failed to parse XML response from newzbin')
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def download(self, url = '', nzb_id = ''):
|
|
||||||
try:
|
|
||||||
log.info('Download nzb from newzbin, report id: %s ', nzb_id)
|
|
||||||
|
|
||||||
return self.urlopen(self.urls['download'], params = {
|
|
||||||
'username' : self.conf('username'),
|
|
||||||
'password' : self.conf('password'),
|
|
||||||
'reportid' : nzb_id
|
|
||||||
}, show_error = False)
|
|
||||||
except Exception, e:
|
|
||||||
log.error('Failed downloading from newzbin, check credit: %s', e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def getFormatId(self, format):
|
|
||||||
for id, quality in self.format_ids.iteritems():
|
|
||||||
for q in quality:
|
|
||||||
if q == format:
|
|
||||||
return id
|
|
||||||
|
|
||||||
return self.cat_backup_id
|
|
||||||
|
|
||||||
def isEnabled(self):
|
|
||||||
return NZBProvider.isEnabled(self) and self.conf('enabled') and self.conf('username') and self.conf('password')
|
|
||||||
@@ -11,7 +11,9 @@ config = [{
|
|||||||
'subtab': 'nzb_providers',
|
'subtab': 'nzb_providers',
|
||||||
'name': 'newznab',
|
'name': 'newznab',
|
||||||
'order': 10,
|
'order': 10,
|
||||||
'description': 'Enable multiple NewzNab providers such as <a href="https://nzb.su" target="_blank">NZB.su</a> and <a href="https://nzbs.org" target="_blank">nzbs.org</a>',
|
'description': 'Enable <a href="http://newznab.com/" target="_blank">NewzNab providers</a> such as <a href="https://nzb.su" target="_blank">NZB.su</a>, \
|
||||||
|
<a href="https://nzbs.org" target="_blank">NZBs.org</a>, <a href="http://dognzb.cr/" target="_blank">DOGnzb.cr</a>, \
|
||||||
|
<a href="https://github.com/spotweb/spotweb" target="_blank">Spotweb</a> or <a href="https://nzbgeek.info/" target="_blank">NZBGeek</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
@@ -20,16 +22,16 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'use',
|
'name': 'use',
|
||||||
'default': '0,0,0'
|
'default': '0,0,0,0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'host',
|
'name': 'host',
|
||||||
'default': 'nzb.su,dognzb.cr,nzbs.org',
|
'default': 'nzb.su,dognzb.cr,nzbs.org,https://index.nzbgeek.info',
|
||||||
'description': 'The hostname of your newznab provider',
|
'description': 'The hostname of your newznab provider',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'api_key',
|
'name': 'api_key',
|
||||||
'default': ',,',
|
'default': ',,,',
|
||||||
'label': 'Api Key',
|
'label': 'Api Key',
|
||||||
'description': 'Can be found on your profile page',
|
'description': 'Can be found on your profile page',
|
||||||
'type': 'combined',
|
'type': 'combined',
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import cleanHost, splitString
|
from couchpotato.core.helpers.variable import cleanHost, splitString
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.base import ResultList
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
@@ -10,7 +10,6 @@ from urllib2 import HTTPError
|
|||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import xml.etree.ElementTree as XMLTree
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -25,139 +24,59 @@ class Newznab(NZBProvider, RSS):
|
|||||||
|
|
||||||
limits_reached = {}
|
limits_reached = {}
|
||||||
|
|
||||||
cat_ids = [
|
|
||||||
([2010], ['dvdr']),
|
|
||||||
([2030], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
|
||||||
([2040], ['720p', '1080p']),
|
|
||||||
([2050], ['bd50']),
|
|
||||||
]
|
|
||||||
cat_backup_id = 2000
|
|
||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
http_time_between_calls = 1 # Seconds
|
||||||
|
|
||||||
def feed(self):
|
|
||||||
|
|
||||||
hosts = self.getHosts()
|
|
||||||
|
|
||||||
results = []
|
|
||||||
for host in hosts:
|
|
||||||
result = self.singleFeed(host)
|
|
||||||
|
|
||||||
if result:
|
|
||||||
results.extend(result)
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def singleFeed(self, host):
|
|
||||||
|
|
||||||
results = []
|
|
||||||
if self.isDisabled(host):
|
|
||||||
return results
|
|
||||||
|
|
||||||
arguments = tryUrlencode({
|
|
||||||
't': self.cat_backup_id,
|
|
||||||
'r': host['api_key'],
|
|
||||||
'i': 58,
|
|
||||||
})
|
|
||||||
url = "%s?%s" % (cleanHost(host['host']) + 'rss', arguments)
|
|
||||||
cache_key = 'newznab.%s.feed.%s' % (host['host'], arguments)
|
|
||||||
|
|
||||||
results = self.createItems(url, cache_key, host, for_feed = True)
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def search(self, movie, quality):
|
def search(self, movie, quality):
|
||||||
|
|
||||||
hosts = self.getHosts()
|
hosts = self.getHosts()
|
||||||
|
|
||||||
results = []
|
results = ResultList(self, movie, quality, imdb_results = True)
|
||||||
for host in hosts:
|
|
||||||
result = self.singleSearch(host, movie, quality)
|
|
||||||
|
|
||||||
if result:
|
for host in hosts:
|
||||||
results.extend(result)
|
if self.isDisabled(host):
|
||||||
|
continue
|
||||||
|
|
||||||
|
self._searchOnHost(host, movie, quality, results)
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def singleSearch(self, host, movie, quality):
|
def _searchOnHost(self, host, movie, quality, results):
|
||||||
|
|
||||||
results = []
|
|
||||||
if self.isDisabled(host):
|
|
||||||
return results
|
|
||||||
|
|
||||||
cat_id = self.getCatId(quality['identifier'])
|
|
||||||
arguments = tryUrlencode({
|
arguments = tryUrlencode({
|
||||||
'imdbid': movie['library']['identifier'].replace('tt', ''),
|
'imdbid': movie['library']['identifier'].replace('tt', ''),
|
||||||
'cat': cat_id[0],
|
|
||||||
'apikey': host['api_key'],
|
'apikey': host['api_key'],
|
||||||
'extended': 1
|
'extended': 1
|
||||||
})
|
})
|
||||||
url = "%s&%s" % (self.getUrl(host['host'], self.urls['search']), arguments)
|
url = '%s&%s' % (self.getUrl(host['host'], self.urls['search']), arguments)
|
||||||
|
|
||||||
cache_key = 'newznab.%s.%s.%s' % (host['host'], movie['library']['identifier'], cat_id[0])
|
nzbs = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
|
|
||||||
results = self.createItems(url, cache_key, host, movie = movie, quality = quality)
|
for nzb in nzbs:
|
||||||
|
|
||||||
return results
|
date = None
|
||||||
|
for item in nzb:
|
||||||
|
if item.attrib.get('name') == 'usenetdate':
|
||||||
|
date = item.attrib.get('value')
|
||||||
|
break
|
||||||
|
|
||||||
def createItems(self, url, cache_key, host, movie = None, quality = None, for_feed = False):
|
if not date:
|
||||||
results = []
|
date = self.getTextElement(nzb, 'pubDate')
|
||||||
|
|
||||||
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
nzb_id = self.getTextElement(nzb, 'guid').split('/')[-1:].pop()
|
||||||
if data:
|
name = self.getTextElement(nzb, 'title')
|
||||||
try:
|
|
||||||
try:
|
|
||||||
data = XMLTree.fromstring(data)
|
|
||||||
nzbs = self.getElements(data, 'channel/item')
|
|
||||||
except Exception, e:
|
|
||||||
log.debug('%s, %s', (self.getName(), e))
|
|
||||||
return results
|
|
||||||
|
|
||||||
results = []
|
if not name:
|
||||||
for nzb in nzbs:
|
continue
|
||||||
|
|
||||||
date = ''
|
results.append({
|
||||||
size = 0
|
'id': nzb_id,
|
||||||
for item in nzb:
|
'provider_extra': host['host'],
|
||||||
if item.attrib.get('name') == 'size':
|
'name': self.getTextElement(nzb, 'title'),
|
||||||
size = item.attrib.get('value')
|
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
||||||
elif item.attrib.get('name') == 'usenetdate':
|
'size': int(self.getElement(nzb, 'enclosure').attrib['length']) / 1024 / 1024,
|
||||||
date = item.attrib.get('value')
|
'url': (self.getUrl(host['host'], self.urls['download']) % tryUrlencode(nzb_id)) + self.getApiExt(host),
|
||||||
|
'detail_url': '%sdetails/%s' % (cleanHost(host['host']), tryUrlencode(nzb_id)),
|
||||||
if date is '': log.debug('Date not parsed properly or not available for %s: %s', (host['host'], self.getTextElement(nzb, "title")))
|
'content': self.getTextElement(nzb, 'description'),
|
||||||
if size is 0: log.debug('Size not parsed properly or not available for %s: %s', (host['host'], self.getTextElement(nzb, "title")))
|
})
|
||||||
|
|
||||||
id = self.getTextElement(nzb, "guid").split('/')[-1:].pop()
|
|
||||||
new = {
|
|
||||||
'id': id,
|
|
||||||
'provider': self.getName(),
|
|
||||||
'type': 'nzb',
|
|
||||||
'name': self.getTextElement(nzb, "title"),
|
|
||||||
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
|
||||||
'size': int(size) / 1024 / 1024,
|
|
||||||
'url': (self.getUrl(host['host'], self.urls['download']) % id) + self.getApiExt(host),
|
|
||||||
'download': self.download,
|
|
||||||
'detail_url': '%sdetails/%s' % (cleanHost(host['host']), id),
|
|
||||||
'content': self.getTextElement(nzb, "description"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if not for_feed:
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
|
||||||
nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = True, single = True)
|
|
||||||
|
|
||||||
if is_correct_movie:
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
else:
|
|
||||||
results.append(new)
|
|
||||||
|
|
||||||
return results
|
|
||||||
except SyntaxError:
|
|
||||||
log.error('Failed to parse XML response from Newznab: %s', host)
|
|
||||||
return results
|
|
||||||
|
|
||||||
def getHosts(self):
|
def getHosts(self):
|
||||||
|
|
||||||
@@ -217,5 +136,6 @@ class Newznab(NZBProvider, RSS):
|
|||||||
self.limits_reached[host] = time.time()
|
self.limits_reached[host] = time.time()
|
||||||
return 'try_next'
|
return 'try_next'
|
||||||
|
|
||||||
log.error('Failed download from %s', (host, traceback.format_exc()))
|
log.error('Failed download from %s: %s', (host, traceback.format_exc()))
|
||||||
raise
|
|
||||||
|
return 'try_next'
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ config = [{
|
|||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
'default': True,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode, \
|
|
||||||
simplifyString
|
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import tryInt, getTitle
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
from couchpotato.environment import Env
|
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
import time
|
import time
|
||||||
import xml.etree.ElementTree as XMLTree
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -17,87 +13,53 @@ log = CPLog(__name__)
|
|||||||
class NZBClub(NZBProvider, RSS):
|
class NZBClub(NZBProvider, RSS):
|
||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'search': 'https://www.nzbclub.com/nzbfeed.aspx?%s',
|
'search': 'http://www.nzbclub.com/nzbfeed.aspx?%s',
|
||||||
}
|
}
|
||||||
|
|
||||||
http_time_between_calls = 4 #seconds
|
http_time_between_calls = 4 #seconds
|
||||||
|
|
||||||
def search(self, movie, quality):
|
def _searchOnTitle(self, title, movie, quality, results):
|
||||||
|
|
||||||
results = []
|
q = '"%s %s" %s' % (title, movie['library']['year'], quality.get('identifier'))
|
||||||
if self.isDisabled():
|
|
||||||
return results
|
|
||||||
|
|
||||||
q = '"%s %s" %s' % (simplifyString(getTitle(movie['library'])), movie['library']['year'], quality.get('identifier'))
|
params = tryUrlencode({
|
||||||
for ignored in Env.setting('ignored_words', 'searcher').split(','):
|
|
||||||
q = '%s -%s' % (q, ignored.strip())
|
|
||||||
|
|
||||||
params = {
|
|
||||||
'q': q,
|
'q': q,
|
||||||
'ig': '1',
|
'ig': '1',
|
||||||
'rpp': 200,
|
'rpp': 200,
|
||||||
'st': 1,
|
'st': 1,
|
||||||
'sp': 1,
|
'sp': 1,
|
||||||
'ns': 1,
|
'ns': 1,
|
||||||
}
|
})
|
||||||
|
|
||||||
cache_key = 'nzbclub.%s.%s.%s' % (movie['library']['identifier'], quality.get('identifier'), q)
|
nzbs = self.getRSSData(self.urls['search'] % params)
|
||||||
data = self.getCache(cache_key, self.urls['search'] % tryUrlencode(params))
|
|
||||||
if data:
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
data = XMLTree.fromstring(data)
|
|
||||||
nzbs = self.getElements(data, 'channel/item')
|
|
||||||
except Exception, e:
|
|
||||||
log.debug('%s, %s', (self.getName(), e))
|
|
||||||
return results
|
|
||||||
|
|
||||||
for nzb in nzbs:
|
for nzb in nzbs:
|
||||||
|
|
||||||
nzbclub_id = tryInt(self.getTextElement(nzb, "link").split('/nzb_view/')[1].split('/')[0])
|
nzbclub_id = tryInt(self.getTextElement(nzb, "link").split('/nzb_view/')[1].split('/')[0])
|
||||||
enclosure = self.getElement(nzb, "enclosure").attrib
|
enclosure = self.getElement(nzb, "enclosure").attrib
|
||||||
size = enclosure['length']
|
size = enclosure['length']
|
||||||
date = self.getTextElement(nzb, "pubDate")
|
date = self.getTextElement(nzb, "pubDate")
|
||||||
|
|
||||||
def extra_check(item):
|
def extra_check(item):
|
||||||
full_description = self.getCache('nzbclub.%s' % nzbclub_id, item['detail_url'], cache_timeout = 25920000)
|
full_description = self.getCache('nzbclub.%s' % nzbclub_id, item['detail_url'], cache_timeout = 25920000)
|
||||||
|
|
||||||
for ignored in ['ARCHIVE inside ARCHIVE', 'Incomplete', 'repair impossible']:
|
for ignored in ['ARCHIVE inside ARCHIVE', 'Incomplete', 'repair impossible']:
|
||||||
if ignored in full_description:
|
if ignored in full_description:
|
||||||
log.info('Wrong: Seems to be passworded or corrupted files: %s', new['name'])
|
log.info('Wrong: Seems to be passworded or corrupted files: %s', item['name'])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
new = {
|
results.append({
|
||||||
'id': nzbclub_id,
|
'id': nzbclub_id,
|
||||||
'type': 'nzb',
|
'name': toUnicode(self.getTextElement(nzb, "title")),
|
||||||
'provider': self.getName(),
|
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
||||||
'name': toUnicode(self.getTextElement(nzb, "title")),
|
'size': tryInt(size) / 1024 / 1024,
|
||||||
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
'url': enclosure['url'].replace(' ', '_'),
|
||||||
'size': tryInt(size) / 1024 / 1024,
|
'detail_url': self.getTextElement(nzb, "link"),
|
||||||
'url': enclosure['url'].replace(' ', '_'),
|
'get_more_info': self.getMoreInfo,
|
||||||
'download': self.download,
|
'extra_check': extra_check
|
||||||
'detail_url': self.getTextElement(nzb, "link"),
|
})
|
||||||
'description': '',
|
|
||||||
'get_more_info': self.getMoreInfo,
|
|
||||||
'extra_check': extra_check
|
|
||||||
}
|
|
||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
|
||||||
nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = False, single = True)
|
|
||||||
|
|
||||||
if is_correct_movie:
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
|
|
||||||
return results
|
|
||||||
except SyntaxError:
|
|
||||||
log.error('Failed to parse XML response from NZBClub')
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def getMoreInfo(self, item):
|
def getMoreInfo(self, item):
|
||||||
full_description = self.getCache('nzbclub.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
full_description = self.getCache('nzbclub.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ config = [{
|
|||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
|
'default': True,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode, \
|
|
||||||
simplifyString
|
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import tryInt, getTitle
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import traceback
|
|
||||||
import xml.etree.ElementTree as XMLTree
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -20,18 +16,14 @@ class NzbIndex(NZBProvider, RSS):
|
|||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'download': 'https://www.nzbindex.com/download/',
|
'download': 'https://www.nzbindex.com/download/',
|
||||||
'api': 'https://www.nzbindex.com/rss/',
|
'search': 'https://www.nzbindex.com/rss/?%s',
|
||||||
}
|
}
|
||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
http_time_between_calls = 1 # Seconds
|
||||||
|
|
||||||
def search(self, movie, quality):
|
def _searchOnTitle(self, title, movie, quality, results):
|
||||||
|
|
||||||
results = []
|
q = '"%s" %s %s' % (title, movie['library']['year'], quality.get('identifier'))
|
||||||
if self.isDisabled():
|
|
||||||
return results
|
|
||||||
|
|
||||||
q = '"%s %s" %s' % (simplifyString(getTitle(movie['library'])), movie['library']['year'], quality.get('identifier'))
|
|
||||||
arguments = tryUrlencode({
|
arguments = tryUrlencode({
|
||||||
'q': q,
|
'q': q,
|
||||||
'age': Env.setting('retention', 'nzb'),
|
'age': Env.setting('retention', 'nzb'),
|
||||||
@@ -43,68 +35,37 @@ class NzbIndex(NZBProvider, RSS):
|
|||||||
'more': 1,
|
'more': 1,
|
||||||
'complete': 1,
|
'complete': 1,
|
||||||
})
|
})
|
||||||
url = "%s?%s" % (self.urls['api'], arguments)
|
|
||||||
|
|
||||||
cache_key = 'nzbindex.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
nzbs = self.getRSSData(self.urls['search'] % arguments)
|
||||||
|
|
||||||
|
for nzb in nzbs:
|
||||||
|
|
||||||
|
enclosure = self.getElement(nzb, 'enclosure').attrib
|
||||||
|
nzbindex_id = int(self.getTextElement(nzb, "link").split('/')[4])
|
||||||
|
|
||||||
data = self.getCache(cache_key, url)
|
|
||||||
if data:
|
|
||||||
try:
|
try:
|
||||||
try:
|
description = self.getTextElement(nzb, "description")
|
||||||
data = XMLTree.fromstring(data)
|
|
||||||
nzbs = self.getElements(data, 'channel/item')
|
|
||||||
except Exception, e:
|
|
||||||
log.debug('%s, %s', (self.getName(), e))
|
|
||||||
return results
|
|
||||||
|
|
||||||
for nzb in nzbs:
|
|
||||||
|
|
||||||
enclosure = self.getElement(nzb, 'enclosure').attrib
|
|
||||||
|
|
||||||
nzbindex_id = int(self.getTextElement(nzb, "link").split('/')[4])
|
|
||||||
|
|
||||||
try:
|
|
||||||
description = self.getTextElement(nzb, "description")
|
|
||||||
except:
|
|
||||||
description = ''
|
|
||||||
|
|
||||||
def extra_check(new):
|
|
||||||
if '#c20000' in new['description'].lower():
|
|
||||||
log.info('Wrong: Seems to be passworded: %s', new['name'])
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
new = {
|
|
||||||
'id': nzbindex_id,
|
|
||||||
'type': 'nzb',
|
|
||||||
'provider': self.getName(),
|
|
||||||
'download': self.download,
|
|
||||||
'name': self.getTextElement(nzb, "title"),
|
|
||||||
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))),
|
|
||||||
'size': tryInt(enclosure['length']) / 1024 / 1024,
|
|
||||||
'url': enclosure['url'],
|
|
||||||
'detail_url': enclosure['url'].replace('/download/', '/release/'),
|
|
||||||
'description': description,
|
|
||||||
'get_more_info': self.getMoreInfo,
|
|
||||||
'extra_check': extra_check,
|
|
||||||
'check_nzb': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
|
||||||
nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = False, single = True)
|
|
||||||
|
|
||||||
if is_correct_movie:
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
|
|
||||||
return results
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
description = ''
|
||||||
|
|
||||||
return results
|
def extra_check(item):
|
||||||
|
if '#c20000' in item['description'].lower():
|
||||||
|
log.info('Wrong: Seems to be passworded: %s', item['name'])
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
'id': nzbindex_id,
|
||||||
|
'name': self.getTextElement(nzb, "title"),
|
||||||
|
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, "pubDate")).timetuple()))),
|
||||||
|
'size': tryInt(enclosure['length']) / 1024 / 1024,
|
||||||
|
'url': enclosure['url'],
|
||||||
|
'detail_url': enclosure['url'].replace('/download/', '/release/'),
|
||||||
|
'description': description,
|
||||||
|
'get_more_info': self.getMoreInfo,
|
||||||
|
'extra_check': extra_check,
|
||||||
|
})
|
||||||
|
|
||||||
def getMoreInfo(self, item):
|
def getMoreInfo(self, item):
|
||||||
try:
|
try:
|
||||||
@@ -116,5 +77,3 @@ class NzbIndex(NZBProvider, RSS):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def isEnabled(self):
|
|
||||||
return NZBProvider.isEnabled(self) and self.conf('enabled')
|
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
from .main import NZBMatrix
|
|
||||||
|
|
||||||
def start():
|
|
||||||
return NZBMatrix()
|
|
||||||
|
|
||||||
config = [{
|
|
||||||
'name': 'nzbmatrix',
|
|
||||||
'groups': [
|
|
||||||
{
|
|
||||||
'tab': 'searcher',
|
|
||||||
'subtab': 'nzb_providers',
|
|
||||||
'name': 'nzbmatrix',
|
|
||||||
'label': 'NZBMatrix',
|
|
||||||
'description': 'See <a href="https://nzbmatrix.com/">NZBMatrix</a>',
|
|
||||||
'wizard': True,
|
|
||||||
'options': [
|
|
||||||
{
|
|
||||||
'name': 'enabled',
|
|
||||||
'type': 'enabler',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'username',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'api_key',
|
|
||||||
'default': '',
|
|
||||||
'label': 'Api Key',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'english_only',
|
|
||||||
'default': 1,
|
|
||||||
'type': 'bool',
|
|
||||||
'label': 'English only',
|
|
||||||
'description': 'Only search for English spoken movies on NZBMatrix',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}]
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
|
||||||
from couchpotato.core.helpers.rss import RSS
|
|
||||||
from couchpotato.core.logger import CPLog
|
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
|
||||||
from couchpotato.environment import Env
|
|
||||||
from dateutil.parser import parse
|
|
||||||
import time
|
|
||||||
import xml.etree.ElementTree as XMLTree
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class NZBMatrix(NZBProvider, RSS):
|
|
||||||
|
|
||||||
urls = {
|
|
||||||
'download': 'https://api.nzbmatrix.com/v1.1/download.php?id=%s',
|
|
||||||
'detail': 'https://nzbmatrix.com/nzb-details.php?id=%s&hit=1',
|
|
||||||
'search': 'https://rss.nzbmatrix.com/rss.php',
|
|
||||||
}
|
|
||||||
|
|
||||||
cat_ids = [
|
|
||||||
([50], ['bd50']),
|
|
||||||
([42, 53], ['720p', '1080p']),
|
|
||||||
([2, 9], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
|
||||||
([54], ['brrip']),
|
|
||||||
([1], ['dvdr']),
|
|
||||||
]
|
|
||||||
cat_backup_id = 2
|
|
||||||
|
|
||||||
def search(self, movie, quality):
|
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
if self.isDisabled():
|
|
||||||
return results
|
|
||||||
|
|
||||||
cat_ids = ','.join(['%s' % x for x in self.getCatId(quality.get('identifier'))])
|
|
||||||
|
|
||||||
arguments = tryUrlencode({
|
|
||||||
'term': movie['library']['identifier'],
|
|
||||||
'subcat': cat_ids,
|
|
||||||
'username': self.conf('username'),
|
|
||||||
'apikey': self.conf('api_key'),
|
|
||||||
'searchin': 'weblink',
|
|
||||||
'maxage': Env.setting('retention', section = 'nzb'),
|
|
||||||
'english': self.conf('english_only'),
|
|
||||||
})
|
|
||||||
url = "%s?%s" % (self.urls['search'], arguments)
|
|
||||||
|
|
||||||
cache_key = 'nzbmatrix.%s.%s' % (movie['library'].get('identifier'), cat_ids)
|
|
||||||
|
|
||||||
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
|
||||||
if data:
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
data = XMLTree.fromstring(data)
|
|
||||||
nzbs = self.getElements(data, 'channel/item')
|
|
||||||
except Exception, e:
|
|
||||||
log.debug('%s, %s', (self.getName(), e))
|
|
||||||
return results
|
|
||||||
|
|
||||||
for nzb in nzbs:
|
|
||||||
|
|
||||||
title = self.getTextElement(nzb, "title")
|
|
||||||
if 'error' in title.lower(): continue
|
|
||||||
|
|
||||||
id = int(self.getTextElement(nzb, "link").split('&')[0].partition('id=')[2])
|
|
||||||
size = self.getTextElement(nzb, "description").split('<br /><b>')[2].split('> ')[1]
|
|
||||||
date = str(self.getTextElement(nzb, "description").split('<br /><b>')[3].partition('Added:</b> ')[2])
|
|
||||||
|
|
||||||
new = {
|
|
||||||
'id': id,
|
|
||||||
'type': 'nzb',
|
|
||||||
'provider': self.getName(),
|
|
||||||
'name': title,
|
|
||||||
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
|
||||||
'size': self.parseSize(size),
|
|
||||||
'url': self.urls['download'] % id + self.getApiExt(),
|
|
||||||
'download': self.download,
|
|
||||||
'detail_url': self.urls['detail'] % id,
|
|
||||||
'description': self.getTextElement(nzb, "description"),
|
|
||||||
'check_nzb': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
|
||||||
nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = True, single = True)
|
|
||||||
|
|
||||||
if is_correct_movie:
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
|
|
||||||
return results
|
|
||||||
except SyntaxError:
|
|
||||||
log.error('Failed to parse XML response from NZBMatrix.com')
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def download(self, url = '', nzb_id = ''):
|
|
||||||
return self.urlopen(url, headers = {'User-Agent': Env.getIdentifier()})
|
|
||||||
|
|
||||||
def getApiExt(self):
|
|
||||||
return '&username=%s&apikey=%s' % (self.conf('username'), self.conf('api_key'))
|
|
||||||
|
|
||||||
def isEnabled(self):
|
|
||||||
return NZBProvider.isEnabled(self) and self.conf('username') and self.conf('api_key')
|
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
import time
|
import time
|
||||||
import xml.etree.ElementTree as XMLTree
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -23,15 +21,9 @@ class Nzbsrus(NZBProvider, RSS):
|
|||||||
]
|
]
|
||||||
cat_backup_id = 240
|
cat_backup_id = 240
|
||||||
|
|
||||||
def search(self, movie, quality):
|
def _search(self, movie, quality, results):
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
if self.isDisabled():
|
|
||||||
return results
|
|
||||||
|
|
||||||
cat_id_string = '&'.join(['c%s=1' % x for x in self.getCatId(quality.get('identifier'))])
|
cat_id_string = '&'.join(['c%s=1' % x for x in self.getCatId(quality.get('identifier'))])
|
||||||
|
|
||||||
arguments = tryUrlencode({
|
arguments = tryUrlencode({
|
||||||
'searchtext': 'imdb:' + movie['library']['identifier'][2:],
|
'searchtext': 'imdb:' + movie['library']['identifier'][2:],
|
||||||
'uid': self.conf('userid'),
|
'uid': self.conf('userid'),
|
||||||
@@ -42,63 +34,29 @@ class Nzbsrus(NZBProvider, RSS):
|
|||||||
|
|
||||||
# check for english_only
|
# check for english_only
|
||||||
if self.conf('english_only'):
|
if self.conf('english_only'):
|
||||||
arguments += "&lang0=1&lang3=1&lang1=1"
|
arguments += '&lang0=1&lang3=1&lang1=1'
|
||||||
|
|
||||||
url = "%s&%s&%s" % (self.urls['search'], arguments , cat_id_string)
|
url = '%s&%s&%s' % (self.urls['search'], arguments , cat_id_string)
|
||||||
|
nzbs = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
|
|
||||||
cache_key = 'nzbsrus_1.%s.%s' % (movie['library'].get('identifier'), cat_id_string)
|
for nzb in nzbs:
|
||||||
single_cat = True
|
|
||||||
|
|
||||||
data = self.getCache(cache_key, url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
title = self.getTextElement(nzb, 'name')
|
||||||
if data:
|
if 'error' in title.lower(): continue
|
||||||
try:
|
|
||||||
try:
|
|
||||||
data = XMLTree.fromstring(data)
|
|
||||||
nzbs = self.getElements(data, 'results/result')
|
|
||||||
except Exception, e:
|
|
||||||
log.debug('%s, %s', (self.getName(), e))
|
|
||||||
return results
|
|
||||||
|
|
||||||
for nzb in nzbs:
|
nzb_id = self.getTextElement(nzb, 'id')
|
||||||
|
size = int(round(int(self.getTextElement(nzb, 'size')) / 1048576))
|
||||||
|
age = int(round((time.time() - int(self.getTextElement(nzb, 'postdate'))) / 86400))
|
||||||
|
|
||||||
title = self.getTextElement(nzb, "name")
|
results.append({
|
||||||
if 'error' in title.lower(): continue
|
'id': nzb_id,
|
||||||
|
'name': title,
|
||||||
id = self.getTextElement(nzb, "id")
|
'age': age,
|
||||||
size = int(round(int(self.getTextElement(nzb, "size")) / 1048576))
|
'size': size,
|
||||||
age = int(round((time.time() - int(self.getTextElement(nzb, "postdate"))) / 86400))
|
'url': self.urls['download'] % id + self.getApiExt() + self.getTextElement(nzb, 'key'),
|
||||||
|
'detail_url': self.urls['detail'] % nzb_id,
|
||||||
new = {
|
'description': self.getTextElement(nzb, 'addtext'),
|
||||||
'id': id,
|
})
|
||||||
'type': 'nzb',
|
|
||||||
'provider': self.getName(),
|
|
||||||
'name': title,
|
|
||||||
'age': age,
|
|
||||||
'size': size,
|
|
||||||
'url': self.urls['download'] % id + self.getApiExt() + self.getTextElement(nzb, "key"),
|
|
||||||
'download': self.download,
|
|
||||||
'detail_url': self.urls['detail'] % id,
|
|
||||||
'description': self.getTextElement(nzb, "addtext"),
|
|
||||||
'check_nzb': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
|
||||||
nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = True, single = True)
|
|
||||||
|
|
||||||
if is_correct_movie:
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
|
|
||||||
return results
|
|
||||||
except SyntaxError:
|
|
||||||
log.error('Failed to parse XML response from Nzbsrus.com')
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def download(self, url = '', nzb_id = ''):
|
|
||||||
return self.urlopen(url, headers = {'User-Agent': Env.getIdentifier()})
|
|
||||||
|
|
||||||
def getApiExt(self):
|
def getApiExt(self):
|
||||||
return '/%s/' % (self.conf('userid'))
|
return '/%s/' % (self.conf('userid'))
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from .main import Nzbx
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return Nzbx()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'nzbx',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'searcher',
|
||||||
|
'subtab': 'nzb_providers',
|
||||||
|
'name': 'nzbX',
|
||||||
|
'description': 'Free provider. See <a href="https://www.nzbx.co/">nzbX</a>',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'enabled',
|
||||||
|
'type': 'enabler',
|
||||||
|
'default': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Nzbx(NZBProvider):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'search': 'https://nzbx.co/api/search?%s',
|
||||||
|
'details': 'https://nzbx.co/api/details?guid=%s',
|
||||||
|
}
|
||||||
|
|
||||||
|
http_time_between_calls = 1 # Seconds
|
||||||
|
|
||||||
|
def _search(self, movie, quality, results):
|
||||||
|
|
||||||
|
# Get nbzs
|
||||||
|
arguments = tryUrlencode({
|
||||||
|
'q': movie['library']['identifier'].replace('tt', ''),
|
||||||
|
'sf': quality.get('size_min'),
|
||||||
|
})
|
||||||
|
nzbs = self.getJsonData(self.urls['search'] % arguments, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
|
|
||||||
|
for nzb in nzbs:
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
'id': nzb['guid'],
|
||||||
|
'url': nzb['nzb'],
|
||||||
|
'detail_url': self.urls['details'] % nzb['guid'],
|
||||||
|
'name': nzb['name'],
|
||||||
|
'age': self.calculateAge(int(nzb['postdate'])),
|
||||||
|
'size': tryInt(nzb['size']) / 1024 / 1024,
|
||||||
|
'score': 5 if nzb['votes']['upvotes'] > nzb['votes']['downvotes'] else 0
|
||||||
|
})
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
from .main import OMGWTFNZBs
|
||||||
|
|
||||||
|
def start():
|
||||||
|
return OMGWTFNZBs()
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'omgwtfnzbs',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'searcher',
|
||||||
|
'subtab': 'nzb_providers',
|
||||||
|
'name': 'OMGWTFNZBs',
|
||||||
|
'description': 'See <a href="http://www.omgwtfnzbs.com/">OMGWTFNZBs</a>',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'enabled',
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'username',
|
||||||
|
'default': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'api_key',
|
||||||
|
'label': 'Api Key',
|
||||||
|
'default': '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
from couchpotato.core.event import fireEvent
|
||||||
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
||||||
|
from couchpotato.core.helpers.rss import RSS
|
||||||
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
|
from dateutil.parser import parse
|
||||||
|
from urlparse import urlparse, parse_qs
|
||||||
|
import time
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OMGWTFNZBs(NZBProvider, RSS):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'search': 'http://rss.omgwtfnzbs.org/rss-search.php?%s',
|
||||||
|
}
|
||||||
|
|
||||||
|
http_time_between_calls = 1 #seconds
|
||||||
|
|
||||||
|
cat_ids = [
|
||||||
|
([15], ['dvdrip']),
|
||||||
|
([15, 16], ['brrip']),
|
||||||
|
([16], ['720p', '1080p', 'bd50']),
|
||||||
|
([17], ['dvdr']),
|
||||||
|
]
|
||||||
|
cat_backup_id = 'movie'
|
||||||
|
|
||||||
|
def search(self, movie, quality):
|
||||||
|
|
||||||
|
if quality['identifier'] in fireEvent('quality.pre_releases', single = True):
|
||||||
|
return []
|
||||||
|
|
||||||
|
return super(OMGWTFNZBs, self).search(movie, quality)
|
||||||
|
|
||||||
|
def _searchOnTitle(self, title, movie, quality, results):
|
||||||
|
|
||||||
|
q = '%s %s' % (title, movie['library']['year'])
|
||||||
|
params = tryUrlencode({
|
||||||
|
'search': q,
|
||||||
|
'catid': ','.join([str(x) for x in self.getCatId(quality['identifier'])]),
|
||||||
|
'user': self.conf('username', default = ''),
|
||||||
|
'api': self.conf('api_key', default = ''),
|
||||||
|
})
|
||||||
|
|
||||||
|
nzbs = self.getRSSData(self.urls['search'] % params)
|
||||||
|
|
||||||
|
for nzb in nzbs:
|
||||||
|
|
||||||
|
enclosure = self.getElement(nzb, 'enclosure').attrib
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
'id': parse_qs(urlparse(self.getTextElement(nzb, 'link')).query).get('id')[0],
|
||||||
|
'name': toUnicode(self.getTextElement(nzb, 'title')),
|
||||||
|
'age': self.calculateAge(int(time.mktime(parse(self.getTextElement(nzb, 'pubDate')).timetuple()))),
|
||||||
|
'size': tryInt(enclosure['length']) / 1024 / 1024,
|
||||||
|
'url': enclosure['url'],
|
||||||
|
'detail_url': self.getTextElement(nzb, 'link'),
|
||||||
|
'description': self.getTextElement(nzb, 'description')
|
||||||
|
})
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
from couchpotato.core.helpers.variable import getImdb, md5
|
from couchpotato.core.helpers.variable import getImdb, md5
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.base import YarrProvider
|
from couchpotato.core.providers.base import YarrProvider
|
||||||
import cookielib
|
|
||||||
import traceback
|
|
||||||
import urllib2
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
@@ -11,7 +8,6 @@ log = CPLog(__name__)
|
|||||||
class TorrentProvider(YarrProvider):
|
class TorrentProvider(YarrProvider):
|
||||||
|
|
||||||
type = 'torrent'
|
type = 'torrent'
|
||||||
login_opener = None
|
|
||||||
|
|
||||||
def imdbMatch(self, url, imdbId):
|
def imdbMatch(self, url, imdbId):
|
||||||
if getImdb(url) == imdbId:
|
if getImdb(url) == imdbId:
|
||||||
@@ -29,29 +25,8 @@ class TorrentProvider(YarrProvider):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def login(self):
|
class TorrentMagnetProvider(TorrentProvider):
|
||||||
|
|
||||||
try:
|
type = 'torrent_magnet'
|
||||||
cookiejar = cookielib.CookieJar()
|
|
||||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar))
|
|
||||||
urllib2.install_opener(opener)
|
|
||||||
f = opener.open(self.urls['login'], self.getLoginParams())
|
|
||||||
f.read()
|
|
||||||
f.close()
|
|
||||||
self.login_opener = opener
|
|
||||||
return True
|
|
||||||
except:
|
|
||||||
log.error('Failed to login %s: %s', (self.getName(), traceback.format_exc()))
|
|
||||||
|
|
||||||
return False
|
download = None
|
||||||
|
|
||||||
def loginDownload(self, url = '', nzb_id = ''):
|
|
||||||
try:
|
|
||||||
if not self.login_opener and not self.login():
|
|
||||||
log.error('Failed downloading from %s', self.getName())
|
|
||||||
return self.urlopen(url, opener = self.login_opener)
|
|
||||||
except:
|
|
||||||
log.error('Failed downloading from %s: %s', (self.getName(), traceback.format_exc()))
|
|
||||||
|
|
||||||
def getLoginParams(self):
|
|
||||||
return ''
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ config = [{
|
|||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
'default': False,
|
'default': True,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.helpers.encoding import simplifyString
|
|
||||||
from couchpotato.core.helpers.variable import tryInt, getTitle
|
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.torrent.base import TorrentProvider
|
from couchpotato.core.providers.torrent.base import TorrentMagnetProvider
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class KickAssTorrents(TorrentProvider):
|
class KickAssTorrents(TorrentMagnetProvider):
|
||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'test': 'https://kat.ph/',
|
'test': 'https://kat.ph/',
|
||||||
@@ -30,16 +28,10 @@ class KickAssTorrents(TorrentProvider):
|
|||||||
http_time_between_calls = 1 #seconds
|
http_time_between_calls = 1 #seconds
|
||||||
cat_backup_id = None
|
cat_backup_id = None
|
||||||
|
|
||||||
def search(self, movie, quality):
|
def _search(self, movie, quality, results):
|
||||||
|
|
||||||
results = []
|
data = self.getHTMLData(self.urls['search'] % ('m', movie['library']['identifier'].replace('tt', '')))
|
||||||
if self.isDisabled():
|
|
||||||
return results
|
|
||||||
|
|
||||||
title = simplifyString(getTitle(movie['library'])).replace(' ', '-')
|
|
||||||
|
|
||||||
cache_key = 'kickasstorrents.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
|
||||||
data = self.getCache(cache_key, self.urls['search'] % (title, movie['library']['identifier'].replace('tt', '')))
|
|
||||||
if data:
|
if data:
|
||||||
|
|
||||||
cat_ids = self.getCatId(quality['identifier'])
|
cat_ids = self.getCatId(quality['identifier'])
|
||||||
@@ -53,61 +45,42 @@ class KickAssTorrents(TorrentProvider):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
for temp in result.find_all('tr'):
|
||||||
|
if temp['class'] is 'firstr' or not temp.get('id'):
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
new = {}
|
||||||
for temp in result.find_all('tr'):
|
|
||||||
if temp['class'] is 'firstr' or not temp.get('id'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
new = {
|
nr = 0
|
||||||
'type': 'torrent_magnet',
|
for td in temp.find_all('td'):
|
||||||
'check_nzb': False,
|
column_name = table_order[nr]
|
||||||
'description': '',
|
if column_name:
|
||||||
'provider': self.getName(),
|
|
||||||
'score': 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
nr = 0
|
if column_name is 'name':
|
||||||
for td in temp.find_all('td'):
|
link = td.find('div', {'class': 'torrentname'}).find_all('a')[1]
|
||||||
column_name = table_order[nr]
|
new['id'] = temp.get('id')[-8:]
|
||||||
if column_name:
|
new['name'] = link.text
|
||||||
|
new['url'] = td.find('a', 'imagnet')['href']
|
||||||
|
new['detail_url'] = self.urls['detail'] % link['href'][1:]
|
||||||
|
new['score'] = 20 if td.find('a', 'iverif') else 0
|
||||||
|
elif column_name is 'size':
|
||||||
|
new['size'] = self.parseSize(td.text)
|
||||||
|
elif column_name is 'age':
|
||||||
|
new['age'] = self.ageToDays(td.text)
|
||||||
|
elif column_name is 'seeds':
|
||||||
|
new['seeders'] = tryInt(td.text)
|
||||||
|
elif column_name is 'leechers':
|
||||||
|
new['leechers'] = tryInt(td.text)
|
||||||
|
|
||||||
if column_name is 'name':
|
nr += 1
|
||||||
link = td.find('div', {'class': 'torrentname'}).find_all('a')[1]
|
|
||||||
new['id'] = temp.get('id')[-8:]
|
|
||||||
new['name'] = link.text
|
|
||||||
new['url'] = td.find('a', 'imagnet')['href']
|
|
||||||
new['detail_url'] = self.urls['detail'] % link['href'][1:]
|
|
||||||
new['score'] = 20 if td.find('a', 'iverif') else 0
|
|
||||||
elif column_name is 'size':
|
|
||||||
new['size'] = self.parseSize(td.text)
|
|
||||||
elif column_name is 'age':
|
|
||||||
new['age'] = self.ageToDays(td.text)
|
|
||||||
elif column_name is 'seeds':
|
|
||||||
new['seeds'] = tryInt(td.text)
|
|
||||||
elif column_name is 'leechers':
|
|
||||||
new['leechers'] = tryInt(td.text)
|
|
||||||
|
|
||||||
nr += 1
|
results.append(new)
|
||||||
|
|
||||||
new['score'] += fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie',
|
|
||||||
nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = True, single = True)
|
|
||||||
if is_correct_movie:
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
except:
|
|
||||||
log.error('Failed parsing KickAssTorrents: %s', traceback.format_exc())
|
|
||||||
except:
|
except:
|
||||||
pass
|
log.error('Failed parsing KickAssTorrents: %s', traceback.format_exc())
|
||||||
|
|
||||||
return results
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
log.debug('No search results found.')
|
log.debug('No search results found.')
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def ageToDays(self, age_str):
|
def ageToDays(self, age_str):
|
||||||
age = 0
|
age = 0
|
||||||
age_str = age_str.replace(' ', ' ')
|
age_str = age_str.replace(' ', ' ')
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ config = [{
|
|||||||
'name': 'password',
|
'name': 'password',
|
||||||
'default': '',
|
'default': '',
|
||||||
'type': 'password',
|
'type': 'password',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'passkey',
|
||||||
|
'default': '',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.variable import getTitle, tryInt, mergeDicts
|
from couchpotato.core.helpers.variable import getTitle, tryInt, mergeDicts
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -21,7 +20,7 @@ class PassThePopcorn(TorrentProvider):
|
|||||||
'domain': 'https://tls.passthepopcorn.me',
|
'domain': 'https://tls.passthepopcorn.me',
|
||||||
'detail': 'https://tls.passthepopcorn.me/torrents.php?torrentid=%s',
|
'detail': 'https://tls.passthepopcorn.me/torrents.php?torrentid=%s',
|
||||||
'torrent': 'https://tls.passthepopcorn.me/torrents.php',
|
'torrent': 'https://tls.passthepopcorn.me/torrents.php',
|
||||||
'login': 'https://tls.passthepopcorn.me/login.php',
|
'login': 'https://tls.passthepopcorn.me/ajax.php?action=login',
|
||||||
'search': 'https://tls.passthepopcorn.me/search/%s/0/7/%d'
|
'search': 'https://tls.passthepopcorn.me/search/%s/0/7/%d'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,18 +64,11 @@ class PassThePopcorn(TorrentProvider):
|
|||||||
else:
|
else:
|
||||||
raise PassThePopcorn.NotLoggedInHTTPError(req.get_full_url(), code, msg, headers, fp)
|
raise PassThePopcorn.NotLoggedInHTTPError(req.get_full_url(), code, msg, headers, fp)
|
||||||
|
|
||||||
def search(self, movie, quality):
|
def _search(self, movie, quality, results):
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
if self.isDisabled():
|
|
||||||
return results
|
|
||||||
|
|
||||||
movie_title = getTitle(movie['library'])
|
movie_title = getTitle(movie['library'])
|
||||||
quality_id = quality['identifier']
|
quality_id = quality['identifier']
|
||||||
|
|
||||||
log.info('Searching for %s at quality %s' % (movie_title, quality_id))
|
|
||||||
|
|
||||||
params = mergeDicts(self.quality_search_params[quality_id].copy(), {
|
params = mergeDicts(self.quality_search_params[quality_id].copy(), {
|
||||||
'order_by': 'relevance',
|
'order_by': 'relevance',
|
||||||
'order_way': 'descending',
|
'order_way': 'descending',
|
||||||
@@ -85,7 +77,7 @@ class PassThePopcorn(TorrentProvider):
|
|||||||
|
|
||||||
# Do login for the cookies
|
# Do login for the cookies
|
||||||
if not self.login_opener and not self.login():
|
if not self.login_opener and not self.login():
|
||||||
return results
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
url = '%s?json=noredirect&%s' % (self.urls['torrent'], tryUrlencode(params))
|
url = '%s?json=noredirect&%s' % (self.urls['torrent'], tryUrlencode(params))
|
||||||
@@ -93,12 +85,11 @@ class PassThePopcorn(TorrentProvider):
|
|||||||
res = json.loads(txt)
|
res = json.loads(txt)
|
||||||
except:
|
except:
|
||||||
log.error('Search on PassThePopcorn.me (%s) failed (could not decode JSON)' % params)
|
log.error('Search on PassThePopcorn.me (%s) failed (could not decode JSON)' % params)
|
||||||
return []
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not 'Movies' in res:
|
if not 'Movies' in res:
|
||||||
log.info("PTP search returned nothing for '%s' at quality '%s' with search parameters %s" % (movie_title, quality_id, params))
|
return
|
||||||
return []
|
|
||||||
|
|
||||||
authkey = res['AuthKey']
|
authkey = res['AuthKey']
|
||||||
passkey = res['PassKey']
|
passkey = res['PassKey']
|
||||||
@@ -118,7 +109,6 @@ class PassThePopcorn(TorrentProvider):
|
|||||||
if 'Scene' in torrent and torrent['Scene']:
|
if 'Scene' in torrent and torrent['Scene']:
|
||||||
torrentdesc += ' Scene'
|
torrentdesc += ' Scene'
|
||||||
if 'RemasterTitle' in torrent and torrent['RemasterTitle']:
|
if 'RemasterTitle' in torrent and torrent['RemasterTitle']:
|
||||||
# eliminate odd characters...
|
|
||||||
torrentdesc += self.htmlToASCII(' %s' % torrent['RemasterTitle'])
|
torrentdesc += self.htmlToASCII(' %s' % torrent['RemasterTitle'])
|
||||||
|
|
||||||
torrentdesc += ' (%s)' % quality_id
|
torrentdesc += ' (%s)' % quality_id
|
||||||
@@ -127,39 +117,23 @@ class PassThePopcorn(TorrentProvider):
|
|||||||
def extra_check(item):
|
def extra_check(item):
|
||||||
return self.torrentMeetsQualitySpec(item, type)
|
return self.torrentMeetsQualitySpec(item, type)
|
||||||
|
|
||||||
def extra_score(item):
|
results.append({
|
||||||
return 50 if torrent['GoldenPopcorn'] else 0
|
|
||||||
|
|
||||||
new = {
|
|
||||||
'id': torrent_id,
|
'id': torrent_id,
|
||||||
'type': 'torrent',
|
|
||||||
'provider': self.getName(),
|
|
||||||
'name': torrent_name,
|
'name': torrent_name,
|
||||||
'description': '',
|
|
||||||
'url': '%s?action=download&id=%d&authkey=%s&torrent_pass=%s' % (self.urls['torrent'], torrent_id, authkey, passkey),
|
'url': '%s?action=download&id=%d&authkey=%s&torrent_pass=%s' % (self.urls['torrent'], torrent_id, authkey, passkey),
|
||||||
'detail_url': self.urls['detail'] % torrent_id,
|
'detail_url': self.urls['detail'] % torrent_id,
|
||||||
'date': tryInt(time.mktime(parse(torrent['UploadTime']).timetuple())),
|
'date': tryInt(time.mktime(parse(torrent['UploadTime']).timetuple())),
|
||||||
'size': tryInt(torrent['Size']) / (1024 * 1024),
|
'size': tryInt(torrent['Size']) / (1024 * 1024),
|
||||||
'provider': self.getName(),
|
|
||||||
'seeders': tryInt(torrent['Seeders']),
|
'seeders': tryInt(torrent['Seeders']),
|
||||||
'leechers': tryInt(torrent['Leechers']),
|
'leechers': tryInt(torrent['Leechers']),
|
||||||
'extra_score': extra_score,
|
'score': 50 if torrent['GoldenPopcorn'] else 0,
|
||||||
'extra_check': extra_check,
|
'extra_check': extra_check,
|
||||||
'download': self.loginDownload,
|
'download': self.loginDownload,
|
||||||
}
|
})
|
||||||
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
|
|
||||||
if fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality):
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
|
|
||||||
return results
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
|
|
||||||
cookieprocessor = urllib2.HTTPCookieProcessor(cookielib.CookieJar())
|
cookieprocessor = urllib2.HTTPCookieProcessor(cookielib.CookieJar())
|
||||||
@@ -249,6 +223,7 @@ class PassThePopcorn(TorrentProvider):
|
|||||||
return tryUrlencode({
|
return tryUrlencode({
|
||||||
'username': self.conf('username'),
|
'username': self.conf('username'),
|
||||||
'password': self.conf('password'),
|
'password': self.conf('password'),
|
||||||
|
'passkey': self.conf('passkey'),
|
||||||
'keeplogged': '1',
|
'keeplogged': '1',
|
||||||
'login': 'Login'
|
'login': 'Login'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'subtab': 'torrent_providers',
|
'subtab': 'torrent_providers',
|
||||||
'name': 'PublicHD',
|
'name': 'PublicHD',
|
||||||
'description': 'Public Torrent site with only HD content. See <a href="https://publichd.eu/">PublicHD</a>',
|
'description': 'Public Torrent site with only HD content. See <a href="https://publichd.se/">PublicHD</a>',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
'default': False,
|
'default': True,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
||||||
from couchpotato.core.helpers.variable import getTitle, tryInt
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.torrent.base import TorrentProvider
|
from couchpotato.core.providers.torrent.base import TorrentMagnetProvider
|
||||||
from urlparse import parse_qs
|
from urlparse import parse_qs
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
@@ -11,31 +10,31 @@ import traceback
|
|||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PublicHD(TorrentProvider):
|
class PublicHD(TorrentMagnetProvider):
|
||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'test': 'https://publichd.eu',
|
'test': 'https://publichd.se',
|
||||||
'detail': 'https://publichd.eu/index.php?page=torrent-details&id=%s',
|
'detail': 'https://publichd.se/index.php?page=torrent-details&id=%s',
|
||||||
'search': 'https://publichd.eu/index.php',
|
'search': 'https://publichd.se/index.php',
|
||||||
}
|
}
|
||||||
http_time_between_calls = 0
|
http_time_between_calls = 0
|
||||||
|
|
||||||
def search(self, movie, quality):
|
def search(self, movie, quality):
|
||||||
|
|
||||||
results = []
|
if not quality.get('hd', False):
|
||||||
|
return []
|
||||||
|
|
||||||
if self.isDisabled() or not quality.get('hd', False):
|
return super(PublicHD, self).search(movie, quality)
|
||||||
return results
|
|
||||||
|
def _searchOnTitle(self, title, movie, quality, results):
|
||||||
|
|
||||||
params = tryUrlencode({
|
params = tryUrlencode({
|
||||||
'page':'torrents',
|
'page':'torrents',
|
||||||
'search': '%s %s' % (getTitle(movie['library']), movie['library']['year']),
|
'search': '%s %s' % (title, movie['library']['year']),
|
||||||
'active': 1,
|
'active': 1,
|
||||||
})
|
})
|
||||||
url = '%s?%s' % (self.urls['search'], params)
|
|
||||||
|
|
||||||
cache_key = 'publichd.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
data = self.getHTMLData('%s?%s' % (self.urls['search'], params))
|
||||||
data = self.getCache(cache_key, url)
|
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
|
|
||||||
@@ -53,36 +52,20 @@ class PublicHD(TorrentProvider):
|
|||||||
|
|
||||||
url = parse_qs(info_url['href'])
|
url = parse_qs(info_url['href'])
|
||||||
|
|
||||||
new = {
|
results.append({
|
||||||
'id': url['id'][0],
|
'id': url['id'][0],
|
||||||
'name': info_url.string,
|
'name': info_url.string,
|
||||||
'type': 'torrent_magnet',
|
|
||||||
'check_nzb': False,
|
|
||||||
'description': '',
|
|
||||||
'provider': self.getName(),
|
|
||||||
'url': download['href'],
|
'url': download['href'],
|
||||||
'detail_url': self.urls['detail'] % url['id'][0],
|
'detail_url': self.urls['detail'] % url['id'][0],
|
||||||
'size': self.parseSize(result.find_all('td')[7].string),
|
'size': self.parseSize(result.find_all('td')[7].string),
|
||||||
'seeders': tryInt(result.find_all('td')[4].string),
|
'seeders': tryInt(result.find_all('td')[4].string),
|
||||||
'leechers': tryInt(result.find_all('td')[5].string),
|
'leechers': tryInt(result.find_all('td')[5].string),
|
||||||
'get_more_info': self.getMoreInfo
|
'get_more_info': self.getMoreInfo
|
||||||
}
|
})
|
||||||
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = False, single = True)
|
|
||||||
|
|
||||||
if is_correct_movie:
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
def getMoreInfo(self, item):
|
def getMoreInfo(self, item):
|
||||||
full_description = self.getCache('publichd.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
full_description = self.getCache('publichd.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
||||||
html = BeautifulSoup(full_description)
|
html = BeautifulSoup(full_description)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -27,30 +26,24 @@ class SceneAccess(TorrentProvider):
|
|||||||
|
|
||||||
http_time_between_calls = 1 #seconds
|
http_time_between_calls = 1 #seconds
|
||||||
|
|
||||||
def search(self, movie, quality):
|
def _search(self, movie, quality, results):
|
||||||
|
|
||||||
results = []
|
|
||||||
if self.isDisabled():
|
|
||||||
return results
|
|
||||||
|
|
||||||
url = self.urls['search'] % (
|
url = self.urls['search'] % (
|
||||||
self.getCatId(quality['identifier'])[0],
|
self.getCatId(quality['identifier'])[0],
|
||||||
self.getCatId(quality['identifier'])[0]
|
self.getCatId(quality['identifier'])[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
q = '%s %s' % (movie['library']['identifier'], quality.get('identifier'))
|
|
||||||
arguments = tryUrlencode({
|
arguments = tryUrlencode({
|
||||||
'search': q,
|
'search': movie['library']['identifier'],
|
||||||
'method': 1,
|
'method': 1,
|
||||||
})
|
})
|
||||||
url = "%s&%s" % (url, arguments)
|
url = "%s&%s" % (url, arguments)
|
||||||
|
|
||||||
# Do login for the cookies
|
# Do login for the cookies
|
||||||
if not self.login_opener and not self.login():
|
if not self.login_opener and not self.login():
|
||||||
return results
|
return
|
||||||
|
|
||||||
cache_key = 'sceneaccess.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
data = self.getHTMLData(url, opener = self.login_opener)
|
||||||
data = self.getCache(cache_key, url, opener = self.login_opener)
|
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
html = BeautifulSoup(data)
|
html = BeautifulSoup(data)
|
||||||
@@ -58,7 +51,7 @@ class SceneAccess(TorrentProvider):
|
|||||||
try:
|
try:
|
||||||
resultsTable = html.find('table', attrs = {'id' : 'torrents-table'})
|
resultsTable = html.find('table', attrs = {'id' : 'torrents-table'})
|
||||||
if resultsTable is None:
|
if resultsTable is None:
|
||||||
return results
|
return
|
||||||
|
|
||||||
entries = resultsTable.find_all('tr', attrs = {'class' : 'tt_row'})
|
entries = resultsTable.find_all('tr', attrs = {'class' : 'tt_row'})
|
||||||
for result in entries:
|
for result in entries:
|
||||||
@@ -66,38 +59,23 @@ class SceneAccess(TorrentProvider):
|
|||||||
link = result.find('td', attrs = {'class' : 'ttr_name'}).find('a')
|
link = result.find('td', attrs = {'class' : 'ttr_name'}).find('a')
|
||||||
url = result.find('td', attrs = {'class' : 'td_dl'}).find('a')
|
url = result.find('td', attrs = {'class' : 'td_dl'}).find('a')
|
||||||
leechers = result.find('td', attrs = {'class' : 'ttr_leechers'}).find('a')
|
leechers = result.find('td', attrs = {'class' : 'ttr_leechers'}).find('a')
|
||||||
id = link['href'].replace('details?id=', '')
|
torrent_id = link['href'].replace('details?id=', '')
|
||||||
|
|
||||||
new = {
|
results.append({
|
||||||
'id': id,
|
'id': torrent_id,
|
||||||
'type': 'torrent',
|
|
||||||
'check_nzb': False,
|
|
||||||
'description': '',
|
|
||||||
'provider': self.getName(),
|
|
||||||
'name': link['title'],
|
'name': link['title'],
|
||||||
'url': self.urls['download'] % url['href'],
|
'url': self.urls['download'] % url['href'],
|
||||||
'detail_url': self.urls['detail'] % id,
|
'detail_url': self.urls['detail'] % torrent_id,
|
||||||
'size': self.parseSize(result.find('td', attrs = {'class' : 'ttr_size'}).contents[0]),
|
'size': self.parseSize(result.find('td', attrs = {'class' : 'ttr_size'}).contents[0]),
|
||||||
'seeders': tryInt(result.find('td', attrs = {'class' : 'ttr_seeders'}).find('a').string),
|
'seeders': tryInt(result.find('td', attrs = {'class' : 'ttr_seeders'}).find('a').string),
|
||||||
'leechers': tryInt(leechers.string) if leechers else 0,
|
'leechers': tryInt(leechers.string) if leechers else 0,
|
||||||
'download': self.loginDownload,
|
'download': self.loginDownload,
|
||||||
'get_more_info': self.getMoreInfo,
|
'get_more_info': self.getMoreInfo,
|
||||||
}
|
})
|
||||||
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = False, single = True)
|
|
||||||
|
|
||||||
if is_correct_movie:
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
|
|
||||||
return results
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
def getLoginParams(self):
|
def getLoginParams(self):
|
||||||
return tryUrlencode({
|
return tryUrlencode({
|
||||||
'username': self.conf('username'),
|
'username': self.conf('username'),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.helpers.encoding import simplifyString, tryUrlencode
|
from couchpotato.core.helpers.encoding import simplifyString, tryUrlencode
|
||||||
from couchpotato.core.helpers.variable import getTitle, tryInt
|
from couchpotato.core.helpers.variable import tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.torrent.base import TorrentProvider
|
from couchpotato.core.providers.torrent.base import TorrentProvider
|
||||||
import traceback
|
import traceback
|
||||||
@@ -21,13 +20,9 @@ class SceneHD(TorrentProvider):
|
|||||||
|
|
||||||
http_time_between_calls = 1 #seconds
|
http_time_between_calls = 1 #seconds
|
||||||
|
|
||||||
def search(self, movie, quality):
|
def _searchOnTitle(self, title, movie, quality, results):
|
||||||
|
|
||||||
results = []
|
q = '"%s %s" %s' % (simplifyString(title), movie['library']['year'], quality.get('identifier'))
|
||||||
if self.isDisabled():
|
|
||||||
return results
|
|
||||||
|
|
||||||
q = '"%s %s" %s' % (simplifyString(getTitle(movie['library'])), movie['library']['year'], quality.get('identifier'))
|
|
||||||
arguments = tryUrlencode({
|
arguments = tryUrlencode({
|
||||||
'search': q,
|
'search': q,
|
||||||
})
|
})
|
||||||
@@ -35,10 +30,9 @@ class SceneHD(TorrentProvider):
|
|||||||
|
|
||||||
# Cookie login
|
# Cookie login
|
||||||
if not self.login_opener and not self.login():
|
if not self.login_opener and not self.login():
|
||||||
return results
|
return
|
||||||
|
|
||||||
cache_key = 'scenehd.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
data = self.getHTMLData(url, opener = self.login_opener)
|
||||||
data = self.getCache(cache_key, url, opener = self.login_opener)
|
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
html = BeautifulSoup(data)
|
html = BeautifulSoup(data)
|
||||||
@@ -52,7 +46,7 @@ class SceneHD(TorrentProvider):
|
|||||||
|
|
||||||
detail_link = all_cells[2].find('a')
|
detail_link = all_cells[2].find('a')
|
||||||
details = detail_link['href']
|
details = detail_link['href']
|
||||||
id = details.replace('details.php?id=', '')
|
torrent_id = details.replace('details.php?id=', '')
|
||||||
|
|
||||||
leechers = all_cells[11].find('a')
|
leechers = all_cells[11].find('a')
|
||||||
if leechers:
|
if leechers:
|
||||||
@@ -60,38 +54,20 @@ class SceneHD(TorrentProvider):
|
|||||||
else:
|
else:
|
||||||
leechers = all_cells[11].string
|
leechers = all_cells[11].string
|
||||||
|
|
||||||
new = {
|
results.append({
|
||||||
'id': id,
|
'id': torrent_id,
|
||||||
'name': detail_link['title'],
|
'name': detail_link['title'],
|
||||||
'type': 'torrent',
|
|
||||||
'check_nzb': False,
|
|
||||||
'description': '',
|
|
||||||
'provider': self.getName(),
|
|
||||||
'size': self.parseSize(all_cells[7].string),
|
'size': self.parseSize(all_cells[7].string),
|
||||||
'seeders': tryInt(all_cells[10].find('a').string),
|
'seeders': tryInt(all_cells[10].find('a').string),
|
||||||
'leechers': tryInt(leechers),
|
'leechers': tryInt(leechers),
|
||||||
'url': self.urls['download'] % id,
|
'url': self.urls['download'] % torrent_id,
|
||||||
'download': self.loginDownload,
|
'download': self.loginDownload,
|
||||||
}
|
'description': all_cells[1].find('a')['href'],
|
||||||
|
})
|
||||||
imdb_link = all_cells[1].find('a')
|
|
||||||
imdb_results = self.imdbMatch(imdb_link['href'], movie['library']['identifier']) if imdb_link else False
|
|
||||||
|
|
||||||
new['score'] = fireEvent('score.calculate', new, movie, single = True)
|
|
||||||
is_correct_movie = fireEvent('searcher.correct_movie', nzb = new, movie = movie, quality = quality,
|
|
||||||
imdb_results = imdb_results, single = True)
|
|
||||||
|
|
||||||
if is_correct_movie:
|
|
||||||
results.append(new)
|
|
||||||
self.found(new)
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def getLoginParams(self, params):
|
def getLoginParams(self, params):
|
||||||
return tryUrlencode({
|
return tryUrlencode({
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ config = [{
|
|||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
'default': False
|
'default': True
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'domain',
|
'name': 'domain',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user