Compare commits
528 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f99b40c2f3 | |||
| ae00e83c9d | |||
| d4f2f12924 | |||
| c9638ec3fa | |||
| 14d636d098 | |||
| e1d4df7937 | |||
| e08d06ba31 | |||
| 984ee7580d | |||
| ab118ea580 | |||
| f897eebb41 | |||
| 755873c5e7 | |||
| dbc254efbe | |||
| 9de8ed2dee | |||
| 230b7f47cc | |||
| 58878d8a0f | |||
| d9bb1bfbfb | |||
| f8674f9baa | |||
| 20f1076037 | |||
| e84f2aa04c | |||
| 01f70051f8 | |||
| 492f69b149 | |||
| 2270b2a28b | |||
| b5a0418a36 | |||
| e595722139 | |||
| 78ba855c68 | |||
| 158f638fb9 | |||
| 5bea9dd04f | |||
| 910393d00e | |||
| 4b66b0ea07 | |||
| 543226450c | |||
| b9dbadda0b | |||
| 7cb214d8a2 | |||
| f6d4ddbe80 | |||
| faefd7a5b5 | |||
| 8f02b0eea0 | |||
| 39d0f91de2 | |||
| b3d75cb485 | |||
| 17b940a271 | |||
| 3338b72d1f | |||
| 70ca31a265 | |||
| d7f43c2cf8 | |||
| b1f88c1c48 | |||
| 6fa6d530ec | |||
| 11e7fb23ca | |||
| da9d2b5ed8 | |||
| 2599bac1a4 | |||
| 0bae509311 | |||
| 2fa7834e6e | |||
| 2deb6ee6a7 | |||
| 0d166025d0 | |||
| 7861416dc5 | |||
| 2639c5e9ad | |||
| c4db4ace13 | |||
| db367a80d1 | |||
| 3093b21555 | |||
| 9b62e32da8 | |||
| a0b3ee8186 | |||
| d70da1edce | |||
| 7c674b3aab | |||
| 98540f2fcd | |||
| 2f0e197320 | |||
| db49585818 | |||
| 160bc1a5c4 | |||
| 8e23b02653 | |||
| 41e69aeac3 | |||
| be30200a18 | |||
| 387650d040 | |||
| 052d64eb39 | |||
| a3a8a820fe | |||
| 1b724b5606 | |||
| 5fc9d7182c | |||
| c948216e33 | |||
| 035b99bc8a | |||
| f74b837faa | |||
| 4c198f7116 | |||
| 76322c0145 | |||
| 12150c5efc | |||
| 4a9452672a | |||
| f7eeaf3eda | |||
| 002ce4d4e1 | |||
| 80df57f2b6 | |||
| 0358378cae | |||
| fa054b6b34 | |||
| 4b9e226cc6 | |||
| ca24bf031c | |||
| af8806e292 | |||
| 4f646094b5 | |||
| 6e8503cfc5 | |||
| 4879bc6251 | |||
| ab253f9030 | |||
| bc6d197004 | |||
| 1de457fa8d | |||
| 9e564c49b3 | |||
| 50a150f570 | |||
| 8d55b0c92a | |||
| 5a2df62462 | |||
| 9d21dd9196 | |||
| 3b34196901 | |||
| bad26026ae | |||
| 6e455e62d5 | |||
| c97bd38c83 | |||
| 356322c5b1 | |||
| 9dbb477dd8 | |||
| 089609d5d2 | |||
| 487ddf1c25 | |||
| 83b4c17969 | |||
| 08c381cf0d | |||
| 286f14a6d2 | |||
| 0b14fe5454 | |||
| c5a0d521d1 | |||
| 4a1f70da09 | |||
| 87e97cd8a5 | |||
| e0dffe20a4 | |||
| 73d37584ad | |||
| 5fd3e86624 | |||
| b0ff526c95 | |||
| 3cfe90d581 | |||
| 1d60d9caf1 | |||
| 8e0d1520e8 | |||
| b07f91d6a5 | |||
| 43af091b02 | |||
| 5f0543ba42 | |||
| ef8cd1aa40 | |||
| e01fe51b9e | |||
| afa782194d | |||
| 77e602f359 | |||
| a6063b0665 | |||
| 9a7e4ea500 | |||
| 1daedb7259 | |||
| 8e82e976f1 | |||
| 8b445ac9f9 | |||
| 91c24105cc | |||
| 13df26851e | |||
| ca58d25785 | |||
| 42d728f71e | |||
| 659960899e | |||
| d40b052cbc | |||
| 282f6fb73a | |||
| 416c9eabde | |||
| b4a15f344d | |||
| c545c9aab1 | |||
| c0492a41d9 | |||
| cfd92b8268 | |||
| 436883a96d | |||
| c381b719b1 | |||
| 81d4d9a4e2 | |||
| e2df3a4dfd | |||
| 7df92f2882 | |||
| 072b6d09fa | |||
| 3869e350bf | |||
| 058846f54f | |||
| cd836f3660 | |||
| d75f58f5ec | |||
| f2b0d3f80b | |||
| a366d57278 | |||
| a821d85bf2 | |||
| a1ce3e0d6b | |||
| e7be5c7809 | |||
| 89f3b6624e | |||
| 3546f29caf | |||
| e3414fe91f | |||
| bdadd00d93 | |||
| dd7de31e9f | |||
| 6897dab647 | |||
| accf19bb26 | |||
| 4126007cac | |||
| 9f12fe2636 | |||
| 9fb348f3a4 | |||
| e749d132cd | |||
| bed9458604 | |||
| 7984ee9fcf | |||
| 69e3e36fae | |||
| 456563eab0 | |||
| 7b6fa4f0e5 | |||
| cd1dc39ef2 | |||
| 0771aeac3b | |||
| cd0afd20e5 | |||
| 324920cd8c | |||
| 12cda35494 | |||
| 1c1af9f90c | |||
| 687221f035 | |||
| a99d52392f | |||
| bd6690b159 | |||
| b13df16b53 | |||
| 06f49be090 | |||
| 0b48ad5084 | |||
| 32ce93d2e9 | |||
| e0479e79bd | |||
| 04e22b3966 | |||
| 40a5ce087b | |||
| 330e15bbcb | |||
| d201d9fff9 | |||
| f765794c99 | |||
| 34320e617d | |||
| 169ddeef5d | |||
| 33ad4c22c7 | |||
| 265f90fe69 | |||
| 099b72ed27 | |||
| d20c0ee37e | |||
| f6030a333a | |||
| 4cbc089de2 | |||
| c45c04659f | |||
| 61a9037835 | |||
| ad33c0bcca | |||
| 7afc524a9f | |||
| c5a4bc9a1b | |||
| 1c0178dbaf | |||
| dbf7feca3e | |||
| d92de8ec4e | |||
| 8347da5a58 | |||
| 59e248d7de | |||
| 12e556e1d1 | |||
| 14d3ab93da | |||
| e27ece512f | |||
| b88d8efc8d | |||
| 9ec4c2837e | |||
| ffc3fc9ec4 | |||
| a566b4f428 | |||
| 69819460f3 | |||
| 24a8cb41fe | |||
| 1de0443492 | |||
| bb19b380b4 | |||
| b6b936ddf3 | |||
| b00b6acba8 | |||
| 3941076c06 | |||
| 7401201af2 | |||
| 5c586fbf30 | |||
| 5c891b7e8e | |||
| 5425fcae9e | |||
| 4008cce12f | |||
| d227105527 | |||
| 508649e6b6 | |||
| b4e25d4345 | |||
| 733f925c75 | |||
| 40e910192e | |||
| 424a3cd892 | |||
| 9f6036c8d6 | |||
| 5af5749d4a | |||
| f01449f14c | |||
| 03dff14ee9 | |||
| e55302592a | |||
| dbeaab052d | |||
| 9f07dd5a21 | |||
| b933cd8718 | |||
| 8d85dde2c6 | |||
| eaaa8dc834 | |||
| 5350dbf0ce | |||
| 28ffad10ab | |||
| a37517bf6a | |||
| fab9b96c8e | |||
| 50d6882a98 | |||
| 94064ac7da | |||
| 1c5f19a68a | |||
| a26abd0dbb | |||
| fb9080c18a | |||
| 15980471b0 | |||
| b11bb9cdac | |||
| 474cd45fc5 | |||
| 0b6843a1b9 | |||
| fdcdf07fa6 | |||
| 5617953d39 | |||
| 964144996f | |||
| 37214dd413 | |||
| 5a08fed0b6 | |||
| 443866ef04 | |||
| 96275adaff | |||
| 33884deb6c | |||
| 7db291fc93 | |||
| 9df14bd55a | |||
| 1e183625c9 | |||
| 643be19711 | |||
| 21a1770f3f | |||
| 07063d855a | |||
| cf95e417f1 | |||
| 3f92ed0ea0 | |||
| 578b74f2c0 | |||
| 8e17b9aea5 | |||
| 6f766aae8c | |||
| 5797348bb3 | |||
| 57ca5067ff | |||
| e8ff8a41de | |||
| 0b5dfe826a | |||
| 67fbcc8238 | |||
| dd61c7dc21 | |||
| 3786b5435f | |||
| 1857e047b0 | |||
| 648ac7793f | |||
| 664ce6421f | |||
| cfb77a1076 | |||
| f65ddbbb9e | |||
| 76126271fc | |||
| 3faece0b4c | |||
| 530d3cd91e | |||
| e659aba176 | |||
| a196a499ae | |||
| 58bd9cd7a1 | |||
| 9dd9f850c6 | |||
| cbecb74307 | |||
| 8ae1e58614 | |||
| 83e8ae392d | |||
| c0297f10cb | |||
| 41052ae508 | |||
| 2d243d51e4 | |||
| fdec80f676 | |||
| 5d3b0deb4d | |||
| f68c356944 | |||
| 553f8d6ccd | |||
| 60fb3e33ae | |||
| 9b7c1db509 | |||
| 963ce356fb | |||
| dcd0364ecc | |||
| a2da428777 | |||
| 876c602710 | |||
| 79cb716ced | |||
| ba9c975335 | |||
| ef407bcb3c | |||
| 2898a066fe | |||
| 7950c4bdb4 | |||
| 2499012d88 | |||
| 7788669de1 | |||
| d7f6fad3dd | |||
| 699c562d34 | |||
| 36d8225389 | |||
| 17ba9ee96b | |||
| 2769fc28d3 | |||
| f5f3cfba50 | |||
| 1b1c77d225 | |||
| cfc49e286b | |||
| a2b3677c59 | |||
| e5cfafdb00 | |||
| bff05925e8 | |||
| 05f4b2b8ce | |||
| 2eac294643 | |||
| f6789f79ea | |||
| 0b5976bdb1 | |||
| 7d2b2b9809 | |||
| cce92dc1f8 | |||
| fa7e59e842 | |||
| 8635f0ddb2 | |||
| c90a423012 | |||
| f0daee669b | |||
| d252b660f5 | |||
| e717a49c0c | |||
| 426155e65c | |||
| 6b9b446e3d | |||
| ab2b2cfe6e | |||
| 4b236c6ed6 | |||
| 2396fadf04 | |||
| a3bffb5867 | |||
| 1b44fc40af | |||
| b894139ca1 | |||
| daa0662869 | |||
| 81de9529c3 | |||
| 6b06caf00d | |||
| 9370366112 | |||
| 32bcf6e615 | |||
| aa804471a7 | |||
| 681d8b1ddc | |||
| c82b1f51e3 | |||
| 6d048e0003 | |||
| 0314910bbe | |||
| 3bd831782c | |||
| 40f01dca6f | |||
| 8dead66b58 | |||
| 18807191c0 | |||
| 9d9630a27a | |||
| 8ac851555d | |||
| 27f331a1fc | |||
| e6b4d32506 | |||
| a28ee58a1f | |||
| 47749c2d73 | |||
| d6d0ff724a | |||
| ba65700aad | |||
| 84a7cfe07d | |||
| 9ccd4a5e84 | |||
| 616434a00f | |||
| 4cf62f73da | |||
| 0145aecab4 | |||
| 6c4184d1f5 | |||
| 9d011b42a9 | |||
| bf81b5cacc | |||
| 8d2b6e4097 | |||
| 50d8399f09 | |||
| bc99b77dbe | |||
| 1c7edc9487 | |||
| 90c06fb3c9 | |||
| 10a04c16ba | |||
| 90a618bd7e | |||
| b630b84ab0 | |||
| a5ee362fc0 | |||
| 7c0870b6b8 | |||
| a42264b280 | |||
| e714604ec0 | |||
| c094120f04 | |||
| 6691c8ddd7 | |||
| 013705c318 | |||
| bda6f92a4d | |||
| 7ceb8dc79c | |||
| 7f48210c97 | |||
| 23c440cd58 | |||
| 0097167dec | |||
| 21e5f156bb | |||
| 08f55314d5 | |||
| 577bf09859 | |||
| c446cd2fb0 | |||
| 06a8414f12 | |||
| 1ac01456a9 | |||
| b86853f06f | |||
| 311a2798dd | |||
| fe9998fb9d | |||
| ce648c5d35 | |||
| 5a2a9bbf9a | |||
| 0f8ab05fd4 | |||
| b87c00c041 | |||
| 8999f51dc9 | |||
| d5e19db5e6 | |||
| 675bee83ca | |||
| 33e5dd1fdb | |||
| 4ff2794c83 | |||
| 81f9302da1 | |||
| 93f4b8b537 | |||
| 0587d2f8db | |||
| 6ba25b5468 | |||
| cc10969506 | |||
| c2eb50a7ee | |||
| 33d24068fd | |||
| 3a4c191b11 | |||
| e06b4ccb3f | |||
| 3c6b86ea28 | |||
| c4a9a13d6c | |||
| c0f1a3c603 | |||
| 9d3425061a | |||
| c2dcd2f67d | |||
| 24b822aecd | |||
| a7d3de766f | |||
| b56c897e4b | |||
| df14032107 | |||
| 66b4821f7f | |||
| d301cde266 | |||
| 0590a0d722 | |||
| fc71a03a12 | |||
| 923c794e39 | |||
| e7fbff5b3f | |||
| 1bd556fbb3 | |||
| 18a870f8c3 | |||
| 3e2a2c3bee | |||
| 73e74881a6 | |||
| b37112600e | |||
| 6172ce4960 | |||
| 3d277e1c01 | |||
| b3b13899f1 | |||
| 7c4a59539a | |||
| e6dfb3da16 | |||
| 8e220ededa | |||
| 11126f8083 | |||
| ac8a13db22 | |||
| 5ab10ff97a | |||
| f3b0346ba2 | |||
| 96c94f97f4 | |||
| 192c0200e5 | |||
| 03ae8f459c | |||
| 377fdd9e5e | |||
| daec7d20fe | |||
| 66a149590b | |||
| 1b6f010df2 | |||
| 7e4bc29b59 | |||
| 0284fa9b0a | |||
| e5bcea59b5 | |||
| 16f603ced2 | |||
| bdcb3b7e33 | |||
| 0def6fcfe3 | |||
| 75a352fef3 | |||
| 07eb1f7f4c | |||
| 8e35c02763 | |||
| c1f6d9a858 | |||
| 3e20a3bac7 | |||
| 818570fd2d | |||
| bcd2d22fbf | |||
| ffc99cd4f4 | |||
| bb56750c1a | |||
| b08d587a22 | |||
| 47f4132b39 | |||
| faefab5554 | |||
| 243a033055 | |||
| db1eeaae38 | |||
| 8c2960e891 | |||
| d6a86e8616 | |||
| 5260f42378 | |||
| 84f28f3c54 | |||
| 860b6793fb | |||
| df03409d7a | |||
| 6a81f2241d | |||
| 5ce817cee6 | |||
| 7cdf124f9d | |||
| ff46aa0226 | |||
| 669e331f6c | |||
| 4179ba642b | |||
| 00954d98f7 | |||
| 037e77860b | |||
| 47e187449d | |||
| 06e9afbe69 | |||
| bfe8aa5f5f | |||
| e51ddd7a50 | |||
| 442552c024 | |||
| ce4806df64 | |||
| 0c2e65c92b | |||
| b01aa2b385 | |||
| 2e04890756 | |||
| 1657857b4a | |||
| 72383592ba | |||
| d093f935f9 | |||
| 8cc7d101aa | |||
| f39eebbd22 | |||
| 3ac8bc738a | |||
| 0eac041a26 | |||
| ab0f5daaf3 | |||
| b59a0f82ab | |||
| 9b75e6af5c | |||
| aa37f2b0ef | |||
| d22237a5cc | |||
| 26f5e8aa4b | |||
| 9072c6cae0 | |||
| 8739c1197f | |||
| a477973862 | |||
| 95ce26d261 | |||
| 8c934c1ca8 | |||
| 349d7d4866 | |||
| f1ea8fa693 |
+12
-8
@@ -10,7 +10,6 @@ import socket
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
import time
|
|
||||||
|
|
||||||
# Root path
|
# Root path
|
||||||
base_path = dirname(os.path.abspath(__file__))
|
base_path = dirname(os.path.abspath(__file__))
|
||||||
@@ -19,7 +18,12 @@ base_path = dirname(os.path.abspath(__file__))
|
|||||||
sys.path.insert(0, os.path.join(base_path, 'libs'))
|
sys.path.insert(0, os.path.join(base_path, 'libs'))
|
||||||
|
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from couchpotato.core.helpers.variable import getDataDir
|
from couchpotato.core.helpers.variable import getDataDir, removePyc
|
||||||
|
|
||||||
|
|
||||||
|
# Remove pyc files before dynamic load (sees .pyc files regular .py modules)
|
||||||
|
removePyc(base_path)
|
||||||
|
|
||||||
|
|
||||||
class Loader(object):
|
class Loader(object):
|
||||||
|
|
||||||
@@ -29,7 +33,7 @@ class Loader(object):
|
|||||||
|
|
||||||
# Get options via arg
|
# Get options via arg
|
||||||
from couchpotato.runner import getOptions
|
from couchpotato.runner import getOptions
|
||||||
self.options = getOptions(base_path, sys.argv[1:])
|
self.options = getOptions(sys.argv[1:])
|
||||||
|
|
||||||
# Load settings
|
# Load settings
|
||||||
settings = Env.get('settings')
|
settings = Env.get('settings')
|
||||||
@@ -50,14 +54,14 @@ class Loader(object):
|
|||||||
# Create logging dir
|
# Create logging dir
|
||||||
self.log_dir = os.path.join(self.data_dir, 'logs');
|
self.log_dir = os.path.join(self.data_dir, 'logs');
|
||||||
if not os.path.isdir(self.log_dir):
|
if not os.path.isdir(self.log_dir):
|
||||||
os.mkdir(self.log_dir)
|
os.makedirs(self.log_dir)
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
self.log = CPLog(__name__)
|
self.log = CPLog(__name__)
|
||||||
|
|
||||||
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', '%H:%M:%S')
|
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', '%H:%M:%S')
|
||||||
hdlr = handlers.RotatingFileHandler(os.path.join(self.log_dir, 'error.log'), 'a', 500000, 10)
|
hdlr = handlers.RotatingFileHandler(os.path.join(self.log_dir, 'error.log'), 'a', 500000, 10, encoding = 'utf-8')
|
||||||
hdlr.setLevel(logging.CRITICAL)
|
hdlr.setLevel(logging.CRITICAL)
|
||||||
hdlr.setFormatter(formatter)
|
hdlr.setFormatter(formatter)
|
||||||
self.log.logger.addHandler(hdlr)
|
self.log.logger.addHandler(hdlr)
|
||||||
@@ -67,10 +71,11 @@ class Loader(object):
|
|||||||
signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1))
|
signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1))
|
||||||
|
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
addEvent('app.after_shutdown', self.afterShutdown)
|
addEvent('app.do_shutdown', self.setRestart)
|
||||||
|
|
||||||
def afterShutdown(self, restart):
|
def setRestart(self, restart):
|
||||||
self.do_restart = restart
|
self.do_restart = restart
|
||||||
|
return True
|
||||||
|
|
||||||
def onExit(self, signal, frame):
|
def onExit(self, signal, frame):
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
@@ -98,7 +103,6 @@ class Loader(object):
|
|||||||
|
|
||||||
# Release log files and shutdown logger
|
# Release log files and shutdown logger
|
||||||
logging.shutdown()
|
logging.shutdown()
|
||||||
time.sleep(3)
|
|
||||||
|
|
||||||
args = [sys.executable] + [os.path.join(base_path, os.path.basename(__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)
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ 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/`
|
* Your browser should open up, but if it doesn't go to `http://localhost:5050/`
|
||||||
|
|
||||||
OSx:
|
OS X:
|
||||||
|
|
||||||
* If you're on Leopard (10.5) install Python 2.6+: [Python 2.6.5](http://www.python.org/download/releases/2.6.5/)
|
* If you're on Leopard (10.5) install Python 2.6+: [Python 2.6.5](http://www.python.org/download/releases/2.6.5/)
|
||||||
* Install [GIT](http://git-scm.com/)
|
* Install [GIT](http://git-scm.com/)
|
||||||
@@ -27,20 +27,27 @@ 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/`
|
* Your browser should open up, but if it doesn't go to `http://localhost:5050/`
|
||||||
|
|
||||||
Linux (ubuntu / debian):
|
Linux:
|
||||||
|
|
||||||
* Install [GIT](http://git-scm.com/) with `apt-get install git-core`
|
* (Ubuntu / Debian) Install [GIT](http://git-scm.com/) with `apt-get install git-core`
|
||||||
|
* (Fedora / CentOS) Install [GIT](http://git-scm.com/) with `yum install git`
|
||||||
* 'cd' to the folder of your choosing.
|
* 'cd' to the folder of your choosing.
|
||||||
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`
|
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`
|
||||||
* Then do `python CouchPotatoServer/CouchPotato.py` to start
|
* Then do `python CouchPotatoServer/CouchPotato.py` to start
|
||||||
* To run on boot copy the init script. `sudo cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato`
|
* (Ubuntu / Debian) To run on boot copy the init script `sudo cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato`
|
||||||
* Change the paths inside the init script. `sudo nano /etc/init.d/couchpotato`
|
* (Ubuntu / Debian) Copy the default paths file `sudo cp CouchPotatoServer/init/ubuntu.default /etc/default/couchpotato`
|
||||||
* Make it executable. `sudo chmod +x /etc/init.d/couchpotato`
|
* (Ubuntu / Debian) Change the paths inside the default file `sudo nano /etc/default/couchpotato`
|
||||||
* Add it to defaults. `sudo update-rc.d couchpotato defaults`
|
* (Ubuntu / Debian) Make it executable `sudo chmod +x /etc/init.d/couchpotato`
|
||||||
* Open your browser and go to: `http://localhost:5050/`
|
* (Ubuntu / Debian) Add it to defaults `sudo update-rc.d couchpotato defaults`
|
||||||
|
* (systemd) To run on boot copy the systemd config `sudo cp CouchPotatoServer/init/couchpotato.fedora.service /etc/systemd/system/couchpotato.service`
|
||||||
|
* (systemd) Update the systemd config file with your user and path to CouchPotato.py
|
||||||
|
* (systemd) Enable it at boot with `sudo systemctl enable couchpotato`
|
||||||
|
* Open your browser and go to `http://localhost:5050/`
|
||||||
|
|
||||||
|
Docker:
|
||||||
|
* You can use [razorgirl's Dockerfile](https://github.com/razorgirl/docker-couchpotato) to quickly build your own isolated app container. It's based on the Linux instructions above. For more info about Docker check out the [official website](https://www.docker.com).
|
||||||
|
|
||||||
FreeBSD :
|
FreeBSD :
|
||||||
|
|
||||||
|
|||||||
+9
-5
@@ -13,6 +13,8 @@ Lastly, for anything related to CouchPotato, feel free to stop by the [forum](ht
|
|||||||
## Issues
|
## Issues
|
||||||
Issues are intended for reporting bugs and weird behaviour or suggesting improvements to CouchPotatoServer.
|
Issues are intended for reporting bugs and weird behaviour or suggesting improvements to CouchPotatoServer.
|
||||||
Before you submit an issue, please go through the following checklist:
|
Before you submit an issue, please go through the following checklist:
|
||||||
|
* **FILL IN ALL THE FIELDS ASKED FOR**
|
||||||
|
* **POST MORE THAN A SINGLE LINE LOG**, if you do, you'd better have a easy reproducable bug
|
||||||
* Search through existing issues (*including closed issues!*) first: you might be able to get your answer there.
|
* Search through existing issues (*including closed issues!*) first: you might be able to get your answer there.
|
||||||
* Double check your issue manually, because it could be an external issue.
|
* Double check your issue manually, because it could be an external issue.
|
||||||
* Post logs with your issue: Without seeing what is going on, the developers can't reproduce the error.
|
* Post logs with your issue: Without seeing what is going on, the developers can't reproduce the error.
|
||||||
@@ -22,15 +24,17 @@ Before you submit an issue, please go through the following checklist:
|
|||||||
* What providers are you using? (While your logs include these, scanning through hundreds of lines of logs isn't our hobby)
|
* What providers are you using? (While your logs include these, scanning through hundreds of lines of logs isn't our hobby)
|
||||||
* Post the logs from the *config* directory, please do not copy paste the UI. Use pastebin to store these logs!
|
* Post the logs from the *config* directory, please do not copy paste the UI. Use pastebin to store these logs!
|
||||||
* Give a short step by step of how to reproduce the error.
|
* Give a short step by step of how to reproduce the error.
|
||||||
* What hardware / OS are you using and what are its limitations? For example: NAS can be slow and maybe have a different version of python installed then when you use CP on OSX or Windows.
|
* What hardware / OS are you using and what are its limitations? For example: NAS can be slow and maybe have a different version of python installed than when you use CP on OS X or Windows.
|
||||||
* Your issue might be marked with the "can't reproduce" tag. Don't ask why your issue was closed if it says so in the tag.
|
* Your issue might be marked with the "can't reproduce" tag. Don't ask why your issue was closed if it says so in the tag.
|
||||||
* If you're running on a NAS (QNAP, Austor etc..) with pre-made packages, make sure these are set up to use our source repository (RuudBurger/CouchPotatoServer) and nothing else!!
|
* If you're running on a NAS (QNAP, Austor, Synology etc.) with pre-made packages, make sure these are set up to use our source repository (RuudBurger/CouchPotatoServer) and nothing else!
|
||||||
|
* Do not "bump" issues with "Any updates on this" or whatever. Yes I've seen it, you don't have to remind me of it. There will be an update when the code is done or I need information. If you feel the need to do so, you'd better have more info on the issue.
|
||||||
The more relevant information you can provide, the more likely it is the issue will be resolved rather than closed.
|
|
||||||
|
The more relevant information you provide, the more likely that your issue will be resolved.
|
||||||
|
If you don't follow any of the checks above, I'll close the issue. If you are wondering why (and ask) I'll block you from posting new issues and the repo.
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
Pull requests are intended for contributing code or documentation to the project. Before you submit a pull request, consider the following:
|
Pull requests are intended for contributing code or documentation to the project. Before you submit a pull request, consider the following:
|
||||||
* Make sure your pull request is made for the *develop* branch (or relevant feature branch).
|
* Make sure your pull request is made for the *develop* branch (or relevant feature branch).
|
||||||
* Have you tested your PR? If not, why?
|
* Have you tested your PR? If not, why?
|
||||||
* Does your PR have any limitations we should know of?
|
* Does your PR have any limitations I should know of?
|
||||||
* Is your PR up-to-date with the branch you're trying to push into?
|
* Is your PR up-to-date with the branch you're trying to push into?
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
|
||||||
from couchpotato.api import api_docs, api_docs_missing, api
|
from couchpotato.api import api_docs, api_docs_missing, api
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.variable import md5, tryInt
|
from couchpotato.core.helpers.variable import md5, tryInt
|
||||||
@@ -5,9 +9,6 @@ from couchpotato.core.logger import CPLog
|
|||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from tornado import template
|
from tornado import template
|
||||||
from tornado.web import RequestHandler, authenticated
|
from tornado.web import RequestHandler, authenticated
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -45,7 +46,7 @@ class WebHandler(BaseHandler):
|
|||||||
self.write({'success': False, 'error': 'Failed returning results'})
|
self.write({'success': False, 'error': 'Failed returning results'})
|
||||||
|
|
||||||
|
|
||||||
def addView(route, func, static = False):
|
def addView(route, func):
|
||||||
views[route] = func
|
views[route] = func
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+47
-27
@@ -7,9 +7,7 @@ import urllib
|
|||||||
|
|
||||||
from couchpotato.core.helpers.request import getParams
|
from couchpotato.core.helpers.request import getParams
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from tornado.gen import coroutine
|
|
||||||
from tornado.web import RequestHandler, asynchronous
|
from tornado.web import RequestHandler, asynchronous
|
||||||
import tornado
|
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -28,10 +26,18 @@ def run_async(func):
|
|||||||
def async_func(*args, **kwargs):
|
def async_func(*args, **kwargs):
|
||||||
func_hl = Thread(target = func, args = args, kwargs = kwargs)
|
func_hl = Thread(target = func, args = args, kwargs = kwargs)
|
||||||
func_hl.start()
|
func_hl.start()
|
||||||
return func_hl
|
|
||||||
|
|
||||||
return async_func
|
return async_func
|
||||||
|
|
||||||
|
@run_async
|
||||||
|
def run_handler(route, kwargs, callback = None):
|
||||||
|
try:
|
||||||
|
res = api[route](**kwargs)
|
||||||
|
callback(res, route)
|
||||||
|
except:
|
||||||
|
log.error('Failed doing api request "%s": %s', (route, traceback.format_exc()))
|
||||||
|
callback({'success': False, 'error': 'Failed returning results'}, route)
|
||||||
|
|
||||||
|
|
||||||
# NonBlock API handler
|
# NonBlock API handler
|
||||||
class NonBlockHandler(RequestHandler):
|
class NonBlockHandler(RequestHandler):
|
||||||
@@ -78,13 +84,18 @@ def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
|
|||||||
# Blocking API handler
|
# Blocking API handler
|
||||||
class ApiHandler(RequestHandler):
|
class ApiHandler(RequestHandler):
|
||||||
|
|
||||||
@coroutine
|
@asynchronous
|
||||||
def get(self, route, *args, **kwargs):
|
def get(self, route, *args, **kwargs):
|
||||||
route = route.strip('/')
|
route = route.strip('/')
|
||||||
if not api.get(route):
|
if not api.get(route):
|
||||||
self.write('API call doesn\'t seem to exist')
|
self.write('API call doesn\'t seem to exist')
|
||||||
|
self.finish()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Create lock if it doesn't exist
|
||||||
|
if route in api_locks and not api_locks.get(route):
|
||||||
|
api_locks[route] = threading.Lock()
|
||||||
|
|
||||||
api_locks[route].acquire()
|
api_locks[route].acquire()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -102,36 +113,45 @@ class ApiHandler(RequestHandler):
|
|||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
# Add async callback handler
|
# Add async callback handler
|
||||||
@run_async
|
run_handler(route, kwargs, callback = self.taskFinished)
|
||||||
def run_handler(callback):
|
|
||||||
try:
|
|
||||||
res = api[route](**kwargs)
|
|
||||||
callback(res)
|
|
||||||
except:
|
|
||||||
log.error('Failed doing api request "%s": %s', (route, traceback.format_exc()))
|
|
||||||
callback({'success': False, 'error': 'Failed returning results'})
|
|
||||||
|
|
||||||
result = yield tornado.gen.Task(run_handler)
|
|
||||||
|
|
||||||
# Check JSONP callback
|
|
||||||
jsonp_callback = self.get_argument('callback_func', default = None)
|
|
||||||
|
|
||||||
if jsonp_callback:
|
|
||||||
self.write(str(jsonp_callback) + '(' + json.dumps(result) + ')')
|
|
||||||
self.set_header("Content-Type", "text/javascript")
|
|
||||||
elif isinstance(result, tuple) and result[0] == 'redirect':
|
|
||||||
self.redirect(result[1])
|
|
||||||
else:
|
|
||||||
self.write(result)
|
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed doing api request "%s": %s', (route, traceback.format_exc()))
|
log.error('Failed doing api request "%s": %s', (route, traceback.format_exc()))
|
||||||
self.write({'success': False, 'error': 'Failed returning results'})
|
try:
|
||||||
|
self.write({'success': False, 'error': 'Failed returning results'})
|
||||||
|
self.finish()
|
||||||
|
except:
|
||||||
|
log.error('Failed write error "%s": %s', (route, traceback.format_exc()))
|
||||||
|
|
||||||
api_locks[route].release()
|
api_locks[route].release()
|
||||||
|
|
||||||
post = get
|
post = get
|
||||||
|
|
||||||
|
def taskFinished(self, result, route):
|
||||||
|
|
||||||
|
if not self.request.connection.stream.closed():
|
||||||
|
try:
|
||||||
|
# Check JSONP callback
|
||||||
|
jsonp_callback = self.get_argument('callback_func', default = None)
|
||||||
|
|
||||||
|
if jsonp_callback:
|
||||||
|
self.write(str(jsonp_callback) + '(' + json.dumps(result) + ')')
|
||||||
|
self.set_header("Content-Type", "text/javascript")
|
||||||
|
self.finish()
|
||||||
|
elif isinstance(result, tuple) and result[0] == 'redirect':
|
||||||
|
self.redirect(result[1])
|
||||||
|
else:
|
||||||
|
self.write(result)
|
||||||
|
self.finish()
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
log.error('Failed proper encode: %s', traceback.format_exc())
|
||||||
|
except:
|
||||||
|
log.debug('Failed doing request, probably already closed: %s', (traceback.format_exc()))
|
||||||
|
try: self.finish({'success': False, 'error': 'Failed returning results'})
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
api_locks[route].release()
|
||||||
|
|
||||||
|
|
||||||
def addApiView(route, func, static = False, docs = None, **kwargs):
|
def addApiView(route, func, static = False, docs = None, **kwargs):
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import webbrowser
|
|||||||
|
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.helpers.variable import cleanHost, md5
|
from couchpotato.core.helpers.variable import cleanHost, md5, isSubFolder
|
||||||
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
|
||||||
@@ -71,13 +71,14 @@ class Core(Plugin):
|
|||||||
return value if value and len(value) > 3 else uuid4().hex
|
return value if value and len(value) > 3 else uuid4().hex
|
||||||
|
|
||||||
def checkDataDir(self):
|
def checkDataDir(self):
|
||||||
if Env.get('app_dir') in Env.get('data_dir'):
|
if isSubFolder(Env.get('data_dir'), Env.get('app_dir')):
|
||||||
log.error('You should NOT use your CouchPotato directory to save your settings in. Files will get overwritten or be deleted.')
|
log.error('You should NOT use your CouchPotato directory to save your settings in. Files will get overwritten or be deleted.')
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def cleanUpFolders(self):
|
def cleanUpFolders(self):
|
||||||
self.deleteEmptyFolder(Env.get('app_dir'), show_error = False)
|
only_clean = ['couchpotato', 'libs', 'init']
|
||||||
|
self.deleteEmptyFolder(Env.get('app_dir'), show_error = False, only_clean = only_clean)
|
||||||
|
|
||||||
def available(self, **kwargs):
|
def available(self, **kwargs):
|
||||||
return {
|
return {
|
||||||
@@ -90,7 +91,11 @@ class Core(Plugin):
|
|||||||
|
|
||||||
def shutdown():
|
def shutdown():
|
||||||
self.initShutdown()
|
self.initShutdown()
|
||||||
IOLoop.current().add_callback(shutdown)
|
|
||||||
|
if IOLoop.current()._closing:
|
||||||
|
shutdown()
|
||||||
|
else:
|
||||||
|
IOLoop.current().add_callback(shutdown)
|
||||||
|
|
||||||
return 'shutdown'
|
return 'shutdown'
|
||||||
|
|
||||||
@@ -113,7 +118,7 @@ class Core(Plugin):
|
|||||||
|
|
||||||
self.shutdown_started = True
|
self.shutdown_started = True
|
||||||
|
|
||||||
fireEvent('app.do_shutdown')
|
fireEvent('app.do_shutdown', restart = restart)
|
||||||
log.debug('Every plugin got shutdown event')
|
log.debug('Every plugin got shutdown event')
|
||||||
|
|
||||||
loop = True
|
loop = True
|
||||||
@@ -138,8 +143,11 @@ class Core(Plugin):
|
|||||||
|
|
||||||
log.debug('Safe to shutdown/restart')
|
log.debug('Safe to shutdown/restart')
|
||||||
|
|
||||||
|
loop = IOLoop.current()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
IOLoop.current().stop()
|
if not loop._closing:
|
||||||
|
loop.stop()
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
pass
|
pass
|
||||||
except:
|
except:
|
||||||
@@ -278,13 +286,13 @@ config = [{
|
|||||||
'name': 'permission_folder',
|
'name': 'permission_folder',
|
||||||
'default': '0755',
|
'default': '0755',
|
||||||
'label': 'Folder CHMOD',
|
'label': 'Folder CHMOD',
|
||||||
'description': 'Can be either decimal (493) or octal (leading zero: 0755)',
|
'description': 'Can be either decimal (493) or octal (leading zero: 0755). <a target="_blank" href="http://permissions-calculator.org/">Calculate the correct value</a>',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'permission_file',
|
'name': 'permission_file',
|
||||||
'default': '0755',
|
'default': '0755',
|
||||||
'label': 'File CHMOD',
|
'label': 'File CHMOD',
|
||||||
'description': 'Same as Folder CHMOD but for files',
|
'description': 'See Folder CHMOD description, but for files',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class DownloaderBase(Provider):
|
|||||||
status_support = True
|
status_support = True
|
||||||
|
|
||||||
torrent_sources = [
|
torrent_sources = [
|
||||||
|
'https://zoink.it/torrent/%s.torrent',
|
||||||
'http://torrage.com/torrent/%s.torrent',
|
'http://torrage.com/torrent/%s.torrent',
|
||||||
'https://torcache.net/torrent/%s.torrent',
|
'https://torcache.net/torrent/%s.torrent',
|
||||||
]
|
]
|
||||||
@@ -72,6 +73,9 @@ class DownloaderBase(Provider):
|
|||||||
return
|
return
|
||||||
return self.download(data = data, media = media, filedata = filedata)
|
return self.download(data = data, media = media, filedata = filedata)
|
||||||
|
|
||||||
|
def download(self, *args, **kwargs):
|
||||||
|
return False
|
||||||
|
|
||||||
def _getAllDownloadStatus(self, download_ids):
|
def _getAllDownloadStatus(self, download_ids):
|
||||||
if self.isDisabled(manual = True, data = {}):
|
if self.isDisabled(manual = True, data = {}):
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -40,15 +40,16 @@ var DownloadersBase = new Class({
|
|||||||
|
|
||||||
button.set('text', button_name);
|
button.set('text', button_name);
|
||||||
|
|
||||||
|
var message;
|
||||||
if(json.success){
|
if(json.success){
|
||||||
var message = new Element('span.success', {
|
message = new Element('span.success', {
|
||||||
'text': 'Connection successful'
|
'text': 'Connection successful'
|
||||||
}).inject(button, 'after')
|
}).inject(button, 'after')
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var msg_text = 'Connection failed. Check logs for details.';
|
var msg_text = 'Connection failed. Check logs for details.';
|
||||||
if(json.hasOwnProperty('msg')) msg_text = json.msg;
|
if(json.hasOwnProperty('msg')) msg_text = json.msg;
|
||||||
var message = new Element('span.failed', {
|
message = new Element('span.failed', {
|
||||||
'text': msg_text
|
'text': msg_text
|
||||||
}).inject(button, 'after')
|
}).inject(button, 'after')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ class Scheduler(Plugin):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def doShutdown(self):
|
def doShutdown(self, *args, **kwargs):
|
||||||
self.stop()
|
self.stop()
|
||||||
return super(Scheduler, self).doShutdown()
|
return super(Scheduler, self).doShutdown(*args, **kwargs)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
if self.started:
|
if self.started:
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ from threading import RLock
|
|||||||
|
|
||||||
from couchpotato.api import addApiView
|
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 ss
|
from couchpotato.core.helpers.encoding import sp
|
||||||
|
from couchpotato.core.helpers.variable import removePyc
|
||||||
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 dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
from git.repository import LocalRepository
|
from git.repository import LocalRepository
|
||||||
from scandir import scandir
|
|
||||||
import version
|
import version
|
||||||
from six.moves import filter
|
from six.moves import filter
|
||||||
|
|
||||||
@@ -142,9 +142,11 @@ class Updater(Plugin):
|
|||||||
'success': success
|
'success': success
|
||||||
}
|
}
|
||||||
|
|
||||||
def doShutdown(self):
|
def doShutdown(self, *args, **kwargs):
|
||||||
self.updater.deletePyc(show_logs = False)
|
if not Env.get('dev') and not Env.get('desktop'):
|
||||||
return super(Updater, self).doShutdown()
|
removePyc(Env.get('app_dir'), show_logs = False)
|
||||||
|
|
||||||
|
return super(Updater, self).doShutdown(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class BaseUpdater(Plugin):
|
class BaseUpdater(Plugin):
|
||||||
@@ -180,30 +182,6 @@ class BaseUpdater(Plugin):
|
|||||||
def check(self):
|
def check(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def deletePyc(self, only_excess = True, show_logs = True):
|
|
||||||
|
|
||||||
for root, dirs, files in scandir.walk(ss(Env.get('app_dir'))):
|
|
||||||
|
|
||||||
pyc_files = filter(lambda filename: filename.endswith('.pyc'), files)
|
|
||||||
py_files = set(filter(lambda filename: filename.endswith('.py'), files))
|
|
||||||
excess_pyc_files = filter(lambda pyc_filename: pyc_filename[:-1] not in py_files, pyc_files) if only_excess else pyc_files
|
|
||||||
|
|
||||||
for excess_pyc_file in excess_pyc_files:
|
|
||||||
full_path = os.path.join(root, excess_pyc_file)
|
|
||||||
if show_logs: log.debug('Removing old PYC file: %s', full_path)
|
|
||||||
try:
|
|
||||||
os.remove(full_path)
|
|
||||||
except:
|
|
||||||
log.error('Couldn\'t remove %s: %s', (full_path, traceback.format_exc()))
|
|
||||||
|
|
||||||
for dir_name in dirs:
|
|
||||||
full_path = os.path.join(root, dir_name)
|
|
||||||
if len(os.listdir(full_path)) == 0:
|
|
||||||
try:
|
|
||||||
os.rmdir(full_path)
|
|
||||||
except:
|
|
||||||
log.error('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc()))
|
|
||||||
|
|
||||||
|
|
||||||
class GitUpdater(BaseUpdater):
|
class GitUpdater(BaseUpdater):
|
||||||
|
|
||||||
@@ -322,17 +300,18 @@ class SourceUpdater(BaseUpdater):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def replaceWith(self, path):
|
def replaceWith(self, path):
|
||||||
app_dir = ss(Env.get('app_dir'))
|
path = sp(path)
|
||||||
data_dir = ss(Env.get('data_dir'))
|
app_dir = Env.get('app_dir')
|
||||||
|
data_dir = Env.get('data_dir')
|
||||||
|
|
||||||
# Get list of files we want to overwrite
|
# Get list of files we want to overwrite
|
||||||
self.deletePyc()
|
removePyc(app_dir)
|
||||||
existing_files = []
|
existing_files = []
|
||||||
for root, subfiles, filenames in scandir.walk(app_dir):
|
for root, subfiles, filenames in os.walk(app_dir):
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
existing_files.append(os.path.join(root, filename))
|
existing_files.append(os.path.join(root, filename))
|
||||||
|
|
||||||
for root, subfiles, filenames in scandir.walk(path):
|
for root, subfiles, filenames in os.walk(path):
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
fromfile = os.path.join(root, filename)
|
fromfile = os.path.join(root, filename)
|
||||||
tofile = os.path.join(app_dir, fromfile.replace(path + os.path.sep, ''))
|
tofile = os.path.join(app_dir, fromfile.replace(path + os.path.sep, ''))
|
||||||
|
|||||||
+450
-295
@@ -2,12 +2,15 @@ import json
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
from sqlite3 import OperationalError
|
||||||
|
|
||||||
|
from CodernityDB.database import RecordNotFound
|
||||||
|
from CodernityDB.index import IndexException, IndexNotFoundException, IndexConflict
|
||||||
from couchpotato import CPLog
|
from couchpotato import CPLog
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode, sp
|
||||||
from couchpotato.core.helpers.variable import getImdb, tryInt
|
from couchpotato.core.helpers.variable import getImdb, tryInt, randomString
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -15,19 +18,25 @@ log = CPLog(__name__)
|
|||||||
|
|
||||||
class Database(object):
|
class Database(object):
|
||||||
|
|
||||||
indexes = []
|
indexes = None
|
||||||
db = None
|
db = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
|
self.indexes = {}
|
||||||
|
|
||||||
addApiView('database.list_documents', self.listDocuments)
|
addApiView('database.list_documents', self.listDocuments)
|
||||||
addApiView('database.reindex', self.reindex)
|
addApiView('database.reindex', self.reindex)
|
||||||
addApiView('database.compact', self.compact)
|
addApiView('database.compact', self.compact)
|
||||||
addApiView('database.document.update', self.updateDocument)
|
addApiView('database.document.update', self.updateDocument)
|
||||||
addApiView('database.document.delete', self.deleteDocument)
|
addApiView('database.document.delete', self.deleteDocument)
|
||||||
|
|
||||||
|
addEvent('database.setup.after', self.startup_compact)
|
||||||
addEvent('database.setup_index', self.setupIndex)
|
addEvent('database.setup_index', self.setupIndex)
|
||||||
|
addEvent('database.delete_corrupted', self.deleteCorrupted)
|
||||||
|
|
||||||
addEvent('app.migrate', self.migrate)
|
addEvent('app.migrate', self.migrate)
|
||||||
|
addEvent('app.after_shutdown', self.close)
|
||||||
|
|
||||||
def getDB(self):
|
def getDB(self):
|
||||||
|
|
||||||
@@ -37,28 +46,50 @@ class Database(object):
|
|||||||
|
|
||||||
return self.db
|
return self.db
|
||||||
|
|
||||||
|
def close(self, **kwargs):
|
||||||
|
self.getDB().close()
|
||||||
|
|
||||||
def setupIndex(self, index_name, klass):
|
def setupIndex(self, index_name, klass):
|
||||||
|
|
||||||
self.indexes.append(index_name)
|
self.indexes[index_name] = klass
|
||||||
|
|
||||||
db = self.getDB()
|
db = self.getDB()
|
||||||
|
|
||||||
# Category index
|
# Category index
|
||||||
index_instance = klass(db.path, index_name)
|
index_instance = klass(db.path, index_name)
|
||||||
try:
|
try:
|
||||||
db.add_index(index_instance)
|
|
||||||
db.reindex_index(index_name)
|
|
||||||
except:
|
|
||||||
previous = db.indexes_names[index_name]
|
|
||||||
previous_version = previous._version
|
|
||||||
current_version = klass._version
|
|
||||||
|
|
||||||
# Only edit index if versions are different
|
# Make sure store and bucket don't exist
|
||||||
if previous_version < current_version:
|
exists = []
|
||||||
log.debug('Index "%s" already exists, updating and reindexing', index_name)
|
for x in ['buck', 'stor']:
|
||||||
db.destroy_index(previous)
|
full_path = os.path.join(db.path, '%s_%s' % (index_name, x))
|
||||||
|
if os.path.exists(full_path):
|
||||||
|
exists.append(full_path)
|
||||||
|
|
||||||
|
if index_name not in db.indexes_names:
|
||||||
|
|
||||||
|
# Remove existing buckets if index isn't there
|
||||||
|
for x in exists:
|
||||||
|
os.unlink(x)
|
||||||
|
|
||||||
|
# Add index (will restore buckets)
|
||||||
db.add_index(index_instance)
|
db.add_index(index_instance)
|
||||||
db.reindex_index(index_name)
|
db.reindex_index(index_name)
|
||||||
|
else:
|
||||||
|
# Previous info
|
||||||
|
previous = db.indexes_names[index_name]
|
||||||
|
previous_version = previous._version
|
||||||
|
current_version = klass._version
|
||||||
|
|
||||||
|
# Only edit index if versions are different
|
||||||
|
if previous_version < current_version:
|
||||||
|
log.debug('Index "%s" already exists, updating and reindexing', index_name)
|
||||||
|
db.destroy_index(previous)
|
||||||
|
db.add_index(index_instance)
|
||||||
|
db.reindex_index(index_name)
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed adding index %s: %s', (index_name, traceback.format_exc()))
|
||||||
|
|
||||||
def deleteDocument(self, **kwargs):
|
def deleteDocument(self, **kwargs):
|
||||||
|
|
||||||
@@ -118,6 +149,17 @@ class Database(object):
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
def deleteCorrupted(self, _id, traceback_error = ''):
|
||||||
|
|
||||||
|
db = self.getDB()
|
||||||
|
|
||||||
|
try:
|
||||||
|
log.debug('Deleted corrupted document "%s": %s', (_id, traceback_error))
|
||||||
|
corrupted = db.get('id', _id, with_storage = False)
|
||||||
|
db._delete_id_index(corrupted.get('_id'), corrupted.get('_rev'), None)
|
||||||
|
except:
|
||||||
|
log.debug('Failed deleting corrupted: %s', traceback.format_exc())
|
||||||
|
|
||||||
def reindex(self, **kwargs):
|
def reindex(self, **kwargs):
|
||||||
|
|
||||||
success = True
|
success = True
|
||||||
@@ -132,20 +174,108 @@ class Database(object):
|
|||||||
'success': success
|
'success': success
|
||||||
}
|
}
|
||||||
|
|
||||||
def compact(self, **kwargs):
|
def compact(self, try_repair = True, **kwargs):
|
||||||
|
|
||||||
|
success = False
|
||||||
|
db = self.getDB()
|
||||||
|
|
||||||
|
# Removing left over compact files
|
||||||
|
db_path = sp(db.path)
|
||||||
|
for f in os.listdir(sp(db.path)):
|
||||||
|
for x in ['_compact_buck', '_compact_stor']:
|
||||||
|
if f[-len(x):] == x:
|
||||||
|
os.unlink(os.path.join(db_path, f))
|
||||||
|
|
||||||
success = True
|
|
||||||
try:
|
try:
|
||||||
db = self.getDB()
|
start = time.time()
|
||||||
|
size = float(db.get_db_details().get('size', 0))
|
||||||
|
log.debug('Compacting database, current size: %sMB', round(size/1048576, 2))
|
||||||
|
|
||||||
db.compact()
|
db.compact()
|
||||||
|
new_size = float(db.get_db_details().get('size', 0))
|
||||||
|
log.debug('Done compacting database in %ss, new size: %sMB, saved: %sMB', (round(time.time()-start, 2), round(new_size/1048576, 2), round((size-new_size)/1048576, 2)))
|
||||||
|
success = True
|
||||||
|
except (IndexException, AttributeError):
|
||||||
|
if try_repair:
|
||||||
|
log.error('Something wrong with indexes, trying repair')
|
||||||
|
|
||||||
|
# Remove all indexes
|
||||||
|
old_indexes = self.indexes.keys()
|
||||||
|
for index_name in old_indexes:
|
||||||
|
try:
|
||||||
|
db.destroy_index(index_name)
|
||||||
|
except IndexNotFoundException:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
log.error('Failed removing old index %s', index_name)
|
||||||
|
|
||||||
|
# Add them again
|
||||||
|
for index_name in self.indexes:
|
||||||
|
klass = self.indexes[index_name]
|
||||||
|
|
||||||
|
# Category index
|
||||||
|
index_instance = klass(db.path, index_name)
|
||||||
|
try:
|
||||||
|
db.add_index(index_instance)
|
||||||
|
db.reindex_index(index_name)
|
||||||
|
except IndexConflict:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
log.error('Failed adding index %s', index_name)
|
||||||
|
raise
|
||||||
|
|
||||||
|
self.compact(try_repair = False)
|
||||||
|
else:
|
||||||
|
log.error('Failed compact: %s', traceback.format_exc())
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed compact: %s', traceback.format_exc())
|
log.error('Failed compact: %s', traceback.format_exc())
|
||||||
success = False
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': success
|
'success': success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Compact on start
|
||||||
|
def startup_compact(self):
|
||||||
|
from couchpotato import Env
|
||||||
|
|
||||||
|
db = self.getDB()
|
||||||
|
|
||||||
|
# Try fix for migration failures on desktop
|
||||||
|
if Env.get('desktop'):
|
||||||
|
try:
|
||||||
|
list(db.all('profile', with_doc = True))
|
||||||
|
except RecordNotFound:
|
||||||
|
|
||||||
|
failed_location = '%s_failed' % db.path
|
||||||
|
old_db = os.path.join(Env.get('data_dir'), 'couchpotato.db.old')
|
||||||
|
|
||||||
|
if not os.path.isdir(failed_location) and os.path.isfile(old_db):
|
||||||
|
log.error('Corrupt database, trying migrate again')
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
# Rename database folder
|
||||||
|
os.rename(db.path, '%s_failed' % db.path)
|
||||||
|
|
||||||
|
# Rename .old database to try another migrate
|
||||||
|
os.rename(old_db, old_db[:-4])
|
||||||
|
|
||||||
|
fireEventAsync('app.restart')
|
||||||
|
else:
|
||||||
|
log.error('Migration failed and couldn\'t recover database. Please report on GitHub, with this message.')
|
||||||
|
db.reindex()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check size and compact if needed
|
||||||
|
size = db.get_db_details().get('size')
|
||||||
|
prop_name = 'last_db_compact'
|
||||||
|
last_check = int(Env.prop(prop_name, default = 0))
|
||||||
|
|
||||||
|
if size > 26214400 and last_check < time.time()-604800: # 25MB / 7 days
|
||||||
|
self.compact()
|
||||||
|
Env.prop(prop_name, value = int(time.time()))
|
||||||
|
|
||||||
def migrate(self):
|
def migrate(self):
|
||||||
|
|
||||||
from couchpotato import Env
|
from couchpotato import Env
|
||||||
@@ -182,301 +312,326 @@ class Database(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
migrate_data = {}
|
migrate_data = {}
|
||||||
|
rename_old = False
|
||||||
|
|
||||||
c = conn.cursor()
|
try:
|
||||||
|
|
||||||
for ml in migrate_list:
|
c = conn.cursor()
|
||||||
migrate_data[ml] = {}
|
|
||||||
rows = migrate_list[ml]
|
|
||||||
|
|
||||||
try:
|
for ml in migrate_list:
|
||||||
c.execute('SELECT %s FROM `%s`' % ('`' + '`,`'.join(rows) + '`', ml))
|
migrate_data[ml] = {}
|
||||||
except:
|
rows = migrate_list[ml]
|
||||||
# ignore faulty destination_id database
|
|
||||||
if ml == 'category':
|
try:
|
||||||
migrate_data[ml] = {}
|
c.execute('SELECT %s FROM `%s`' % ('`' + '`,`'.join(rows) + '`', ml))
|
||||||
|
except:
|
||||||
|
# ignore faulty destination_id database
|
||||||
|
if ml == 'category':
|
||||||
|
migrate_data[ml] = {}
|
||||||
|
else:
|
||||||
|
rename_old = True
|
||||||
|
raise
|
||||||
|
|
||||||
|
for p in c.fetchall():
|
||||||
|
columns = {}
|
||||||
|
for row in migrate_list[ml]:
|
||||||
|
columns[row] = p[rows.index(row)]
|
||||||
|
|
||||||
|
if not migrate_data[ml].get(p[0]):
|
||||||
|
migrate_data[ml][p[0]] = columns
|
||||||
|
else:
|
||||||
|
if not isinstance(migrate_data[ml][p[0]], list):
|
||||||
|
migrate_data[ml][p[0]] = [migrate_data[ml][p[0]]]
|
||||||
|
migrate_data[ml][p[0]].append(columns)
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
log.info('Getting data took %s', time.time() - migrate_start)
|
||||||
|
|
||||||
|
db = self.getDB()
|
||||||
|
if not db.opened:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Use properties
|
||||||
|
properties = migrate_data['properties']
|
||||||
|
log.info('Importing %s properties', len(properties))
|
||||||
|
for x in properties:
|
||||||
|
property = properties[x]
|
||||||
|
Env.prop(property.get('identifier'), property.get('value'))
|
||||||
|
|
||||||
|
# Categories
|
||||||
|
categories = migrate_data.get('category', [])
|
||||||
|
log.info('Importing %s categories', len(categories))
|
||||||
|
category_link = {}
|
||||||
|
for x in categories:
|
||||||
|
c = categories[x]
|
||||||
|
|
||||||
|
new_c = db.insert({
|
||||||
|
'_t': 'category',
|
||||||
|
'order': c.get('order', 999),
|
||||||
|
'label': toUnicode(c.get('label', '')),
|
||||||
|
'ignored': toUnicode(c.get('ignored', '')),
|
||||||
|
'preferred': toUnicode(c.get('preferred', '')),
|
||||||
|
'required': toUnicode(c.get('required', '')),
|
||||||
|
'destination': toUnicode(c.get('destination', '')),
|
||||||
|
})
|
||||||
|
|
||||||
|
category_link[x] = new_c.get('_id')
|
||||||
|
|
||||||
|
# Profiles
|
||||||
|
log.info('Importing profiles')
|
||||||
|
new_profiles = db.all('profile', with_doc = True)
|
||||||
|
new_profiles_by_label = {}
|
||||||
|
for x in new_profiles:
|
||||||
|
|
||||||
|
# Remove default non core profiles
|
||||||
|
if not x['doc'].get('core'):
|
||||||
|
db.delete(x['doc'])
|
||||||
else:
|
else:
|
||||||
raise
|
new_profiles_by_label[x['doc']['label']] = x['_id']
|
||||||
|
|
||||||
for p in c.fetchall():
|
profiles = migrate_data['profile']
|
||||||
columns = {}
|
profile_link = {}
|
||||||
for row in migrate_list[ml]:
|
for x in profiles:
|
||||||
columns[row] = p[rows.index(row)]
|
p = profiles[x]
|
||||||
|
|
||||||
if not migrate_data[ml].get(p[0]):
|
exists = new_profiles_by_label.get(p.get('label'))
|
||||||
migrate_data[ml][p[0]] = columns
|
|
||||||
|
# Update existing with order only
|
||||||
|
if exists and p.get('core'):
|
||||||
|
profile = db.get('id', exists)
|
||||||
|
profile['order'] = tryInt(p.get('order'))
|
||||||
|
profile['hide'] = p.get('hide') in [1, True, 'true', 'True']
|
||||||
|
db.update(profile)
|
||||||
|
|
||||||
|
profile_link[x] = profile.get('_id')
|
||||||
else:
|
else:
|
||||||
if not isinstance(migrate_data[ml][p[0]], list):
|
|
||||||
migrate_data[ml][p[0]] = [migrate_data[ml][p[0]]]
|
|
||||||
migrate_data[ml][p[0]].append(columns)
|
|
||||||
|
|
||||||
conn.close()
|
new_profile = {
|
||||||
|
'_t': 'profile',
|
||||||
log.info('Getting data took %s', time.time() - migrate_start)
|
'label': p.get('label'),
|
||||||
|
'order': int(p.get('order', 999)),
|
||||||
db = self.getDB()
|
'core': p.get('core', False),
|
||||||
|
'qualities': [],
|
||||||
# Use properties
|
'wait_for': [],
|
||||||
properties = migrate_data['properties']
|
'finish': []
|
||||||
log.info('Importing %s properties', len(properties))
|
|
||||||
for x in properties:
|
|
||||||
property = properties[x]
|
|
||||||
Env.prop(property.get('identifier'), property.get('value'))
|
|
||||||
|
|
||||||
# Categories
|
|
||||||
categories = migrate_data.get('category', [])
|
|
||||||
log.info('Importing %s categories', len(categories))
|
|
||||||
category_link = {}
|
|
||||||
for x in categories:
|
|
||||||
c = categories[x]
|
|
||||||
|
|
||||||
new_c = db.insert({
|
|
||||||
'_t': 'category',
|
|
||||||
'order': c.get('order', 999),
|
|
||||||
'label': toUnicode(c.get('label', '')),
|
|
||||||
'ignored': toUnicode(c.get('ignored', '')),
|
|
||||||
'preferred': toUnicode(c.get('preferred', '')),
|
|
||||||
'required': toUnicode(c.get('required', '')),
|
|
||||||
'destination': toUnicode(c.get('destination', '')),
|
|
||||||
})
|
|
||||||
|
|
||||||
category_link[x] = new_c.get('_id')
|
|
||||||
|
|
||||||
# Profiles
|
|
||||||
log.info('Importing profiles')
|
|
||||||
new_profiles = db.all('profile', with_doc = True)
|
|
||||||
new_profiles_by_label = {}
|
|
||||||
for x in new_profiles:
|
|
||||||
|
|
||||||
# Remove default non core profiles
|
|
||||||
if not x['doc'].get('core'):
|
|
||||||
db.delete(x['doc'])
|
|
||||||
else:
|
|
||||||
new_profiles_by_label[x['doc']['label']] = x['_id']
|
|
||||||
|
|
||||||
profiles = migrate_data['profile']
|
|
||||||
profile_link = {}
|
|
||||||
for x in profiles:
|
|
||||||
p = profiles[x]
|
|
||||||
|
|
||||||
exists = new_profiles_by_label.get(p.get('label'))
|
|
||||||
|
|
||||||
# Update existing with order only
|
|
||||||
if exists and p.get('core'):
|
|
||||||
profile = db.get('id', exists)
|
|
||||||
profile['order'] = tryInt(p.get('order'))
|
|
||||||
profile['hide'] = p.get('hide') in [1, True, 'true', 'True']
|
|
||||||
db.update(profile)
|
|
||||||
|
|
||||||
profile_link[x] = profile.get('_id')
|
|
||||||
else:
|
|
||||||
|
|
||||||
new_profile = {
|
|
||||||
'_t': 'profile',
|
|
||||||
'label': p.get('label'),
|
|
||||||
'order': int(p.get('order', 999)),
|
|
||||||
'core': p.get('core', False),
|
|
||||||
'qualities': [],
|
|
||||||
'wait_for': [],
|
|
||||||
'finish': []
|
|
||||||
}
|
|
||||||
|
|
||||||
types = migrate_data['profiletype']
|
|
||||||
for profile_type in types:
|
|
||||||
p_type = types[profile_type]
|
|
||||||
if types[profile_type]['profile_id'] == p['id']:
|
|
||||||
new_profile['finish'].append(p_type['finish'])
|
|
||||||
new_profile['wait_for'].append(p_type['wait_for'])
|
|
||||||
new_profile['qualities'].append(migrate_data['quality'][p_type['quality_id']]['identifier'])
|
|
||||||
|
|
||||||
new_profile.update(db.insert(new_profile))
|
|
||||||
|
|
||||||
profile_link[x] = new_profile.get('_id')
|
|
||||||
|
|
||||||
# Qualities
|
|
||||||
log.info('Importing quality sizes')
|
|
||||||
new_qualities = db.all('quality', with_doc = True)
|
|
||||||
new_qualities_by_identifier = {}
|
|
||||||
for x in new_qualities:
|
|
||||||
new_qualities_by_identifier[x['doc']['identifier']] = x['_id']
|
|
||||||
|
|
||||||
qualities = migrate_data['quality']
|
|
||||||
quality_link = {}
|
|
||||||
for x in qualities:
|
|
||||||
q = qualities[x]
|
|
||||||
q_id = new_qualities_by_identifier[q.get('identifier')]
|
|
||||||
|
|
||||||
quality = db.get('id', q_id)
|
|
||||||
quality['order'] = q.get('order')
|
|
||||||
quality['size_min'] = tryInt(q.get('size_min'))
|
|
||||||
quality['size_max'] = tryInt(q.get('size_max'))
|
|
||||||
db.update(quality)
|
|
||||||
|
|
||||||
quality_link[x] = quality
|
|
||||||
|
|
||||||
# Titles
|
|
||||||
titles = migrate_data['librarytitle']
|
|
||||||
titles_by_library = {}
|
|
||||||
for x in titles:
|
|
||||||
title = titles[x]
|
|
||||||
if title.get('default'):
|
|
||||||
titles_by_library[title.get('libraries_id')] = title.get('title')
|
|
||||||
|
|
||||||
# Releases
|
|
||||||
releaseinfos = migrate_data['releaseinfo']
|
|
||||||
for x in releaseinfos:
|
|
||||||
info = releaseinfos[x]
|
|
||||||
|
|
||||||
# Skip if release doesn't exist for this info
|
|
||||||
if not migrate_data['release'].get(info.get('release_id')):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not migrate_data['release'][info.get('release_id')].get('info'):
|
|
||||||
migrate_data['release'][info.get('release_id')]['info'] = {}
|
|
||||||
|
|
||||||
migrate_data['release'][info.get('release_id')]['info'][info.get('identifier')] = info.get('value')
|
|
||||||
|
|
||||||
releases = migrate_data['release']
|
|
||||||
releases_by_media = {}
|
|
||||||
for x in releases:
|
|
||||||
release = releases[x]
|
|
||||||
if not releases_by_media.get(release.get('movie_id')):
|
|
||||||
releases_by_media[release.get('movie_id')] = []
|
|
||||||
|
|
||||||
releases_by_media[release.get('movie_id')].append(release)
|
|
||||||
|
|
||||||
# Type ids
|
|
||||||
types = migrate_data['filetype']
|
|
||||||
type_by_id = {}
|
|
||||||
for t in types:
|
|
||||||
type = types[t]
|
|
||||||
type_by_id[type.get('id')] = type
|
|
||||||
|
|
||||||
# Media
|
|
||||||
log.info('Importing %s media items', len(migrate_data['movie']))
|
|
||||||
statuses = migrate_data['status']
|
|
||||||
libraries = migrate_data['library']
|
|
||||||
library_files = migrate_data['library_files__file_library']
|
|
||||||
releases_files = migrate_data['release_files__file_release']
|
|
||||||
all_files = migrate_data['file']
|
|
||||||
poster_type = migrate_data['filetype']['poster']
|
|
||||||
medias = migrate_data['movie']
|
|
||||||
for x in medias:
|
|
||||||
m = medias[x]
|
|
||||||
|
|
||||||
status = statuses.get(m['status_id']).get('identifier')
|
|
||||||
l = libraries[m['library_id']]
|
|
||||||
|
|
||||||
# Only migrate wanted movies, Skip if no identifier present
|
|
||||||
if not getImdb(l.get('identifier')): continue
|
|
||||||
|
|
||||||
profile_id = profile_link.get(m['profile_id'])
|
|
||||||
category_id = category_link.get(m['category_id'])
|
|
||||||
title = titles_by_library.get(m['library_id'])
|
|
||||||
releases = releases_by_media.get(x, [])
|
|
||||||
info = json.loads(l.get('info', ''))
|
|
||||||
|
|
||||||
files = library_files.get(m['library_id'], [])
|
|
||||||
if not isinstance(files, list):
|
|
||||||
files = [files]
|
|
||||||
|
|
||||||
added_media = fireEvent('movie.add', {
|
|
||||||
'info': info,
|
|
||||||
'identifier': l.get('identifier'),
|
|
||||||
'profile_id': profile_id,
|
|
||||||
'category_id': category_id,
|
|
||||||
'title': title
|
|
||||||
}, force_readd = False, search_after = False, update_after = False, notify_after = False, status = status, single = True)
|
|
||||||
|
|
||||||
if not added_media:
|
|
||||||
log.error('Failed adding media %s: %s', (l.get('identifier'), info))
|
|
||||||
continue
|
|
||||||
|
|
||||||
added_media['files'] = added_media.get('files', {})
|
|
||||||
for f in files:
|
|
||||||
ffile = all_files[f.get('file_id')]
|
|
||||||
|
|
||||||
# Only migrate posters
|
|
||||||
if ffile.get('type_id') == poster_type.get('id'):
|
|
||||||
if ffile.get('path') not in added_media['files'].get('image_poster', []) and os.path.isfile(ffile.get('path')):
|
|
||||||
added_media['files']['image_poster'] = [ffile.get('path')]
|
|
||||||
break
|
|
||||||
|
|
||||||
if 'image_poster' in added_media['files']:
|
|
||||||
db.update(added_media)
|
|
||||||
|
|
||||||
for rel in releases:
|
|
||||||
|
|
||||||
empty_info = False
|
|
||||||
if not rel.get('info'):
|
|
||||||
empty_info = True
|
|
||||||
rel['info'] = {}
|
|
||||||
|
|
||||||
quality = quality_link[rel.get('quality_id')]
|
|
||||||
release_status = statuses.get(rel.get('status_id')).get('identifier')
|
|
||||||
|
|
||||||
if rel['info'].get('download_id'):
|
|
||||||
status_support = rel['info'].get('download_status_support', False) in [True, 'true', 'True']
|
|
||||||
rel['info']['download_info'] = {
|
|
||||||
'id': rel['info'].get('download_id'),
|
|
||||||
'downloader': rel['info'].get('download_downloader'),
|
|
||||||
'status_support': status_support,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add status to keys
|
types = migrate_data['profiletype']
|
||||||
rel['info']['status'] = release_status
|
for profile_type in types:
|
||||||
if not empty_info:
|
p_type = types[profile_type]
|
||||||
fireEvent('release.create_from_search', [rel['info']], added_media, quality, single = True)
|
if types[profile_type]['profile_id'] == p['id']:
|
||||||
else:
|
if p_type['quality_id']:
|
||||||
release = {
|
new_profile['finish'].append(p_type['finish'])
|
||||||
'_t': 'release',
|
new_profile['wait_for'].append(p_type['wait_for'])
|
||||||
'identifier': rel.get('identifier'),
|
new_profile['qualities'].append(migrate_data['quality'][p_type['quality_id']]['identifier'])
|
||||||
'media_id': added_media.get('_id'),
|
|
||||||
'quality': quality.get('identifier'),
|
|
||||||
'status': release_status,
|
|
||||||
'last_edit': int(time.time()),
|
|
||||||
'files': {}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add downloader info if provided
|
if len(new_profile['qualities']) > 0:
|
||||||
try:
|
new_profile.update(db.insert(new_profile))
|
||||||
release['download_info'] = rel['info']['download_info']
|
profile_link[x] = new_profile.get('_id')
|
||||||
del rel['download_info']
|
else:
|
||||||
except:
|
log.error('Corrupt profile list for "%s", using default.', p.get('label'))
|
||||||
pass
|
|
||||||
|
|
||||||
# Add files
|
# Qualities
|
||||||
release_files = releases_files.get(rel.get('id'), [])
|
log.info('Importing quality sizes')
|
||||||
if not isinstance(release_files, list):
|
new_qualities = db.all('quality', with_doc = True)
|
||||||
release_files = [release_files]
|
new_qualities_by_identifier = {}
|
||||||
|
for x in new_qualities:
|
||||||
|
new_qualities_by_identifier[x['doc']['identifier']] = x['_id']
|
||||||
|
|
||||||
if len(release_files) == 0:
|
qualities = migrate_data['quality']
|
||||||
|
quality_link = {}
|
||||||
|
for x in qualities:
|
||||||
|
q = qualities[x]
|
||||||
|
q_id = new_qualities_by_identifier[q.get('identifier')]
|
||||||
|
|
||||||
|
quality = db.get('id', q_id)
|
||||||
|
quality['order'] = q.get('order')
|
||||||
|
quality['size_min'] = tryInt(q.get('size_min'))
|
||||||
|
quality['size_max'] = tryInt(q.get('size_max'))
|
||||||
|
db.update(quality)
|
||||||
|
|
||||||
|
quality_link[x] = quality
|
||||||
|
|
||||||
|
# Titles
|
||||||
|
titles = migrate_data['librarytitle']
|
||||||
|
titles_by_library = {}
|
||||||
|
for x in titles:
|
||||||
|
title = titles[x]
|
||||||
|
if title.get('default'):
|
||||||
|
titles_by_library[title.get('libraries_id')] = title.get('title')
|
||||||
|
|
||||||
|
# Releases
|
||||||
|
releaseinfos = migrate_data['releaseinfo']
|
||||||
|
for x in releaseinfos:
|
||||||
|
info = releaseinfos[x]
|
||||||
|
|
||||||
|
# Skip if release doesn't exist for this info
|
||||||
|
if not migrate_data['release'].get(info.get('release_id')):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not migrate_data['release'][info.get('release_id')].get('info'):
|
||||||
|
migrate_data['release'][info.get('release_id')]['info'] = {}
|
||||||
|
|
||||||
|
migrate_data['release'][info.get('release_id')]['info'][info.get('identifier')] = info.get('value')
|
||||||
|
|
||||||
|
releases = migrate_data['release']
|
||||||
|
releases_by_media = {}
|
||||||
|
for x in releases:
|
||||||
|
release = releases[x]
|
||||||
|
if not releases_by_media.get(release.get('movie_id')):
|
||||||
|
releases_by_media[release.get('movie_id')] = []
|
||||||
|
|
||||||
|
releases_by_media[release.get('movie_id')].append(release)
|
||||||
|
|
||||||
|
# Type ids
|
||||||
|
types = migrate_data['filetype']
|
||||||
|
type_by_id = {}
|
||||||
|
for t in types:
|
||||||
|
type = types[t]
|
||||||
|
type_by_id[type.get('id')] = type
|
||||||
|
|
||||||
|
# Media
|
||||||
|
log.info('Importing %s media items', len(migrate_data['movie']))
|
||||||
|
statuses = migrate_data['status']
|
||||||
|
libraries = migrate_data['library']
|
||||||
|
library_files = migrate_data['library_files__file_library']
|
||||||
|
releases_files = migrate_data['release_files__file_release']
|
||||||
|
all_files = migrate_data['file']
|
||||||
|
poster_type = migrate_data['filetype']['poster']
|
||||||
|
medias = migrate_data['movie']
|
||||||
|
for x in medias:
|
||||||
|
m = medias[x]
|
||||||
|
|
||||||
|
status = statuses.get(m['status_id']).get('identifier')
|
||||||
|
l = libraries.get(m['library_id'])
|
||||||
|
|
||||||
|
# Only migrate wanted movies, Skip if no identifier present
|
||||||
|
if not l or not getImdb(l.get('identifier')): continue
|
||||||
|
|
||||||
|
profile_id = profile_link.get(m['profile_id'])
|
||||||
|
category_id = category_link.get(m['category_id'])
|
||||||
|
title = titles_by_library.get(m['library_id'])
|
||||||
|
releases = releases_by_media.get(x, [])
|
||||||
|
info = json.loads(l.get('info', ''))
|
||||||
|
|
||||||
|
files = library_files.get(m['library_id'], [])
|
||||||
|
if not isinstance(files, list):
|
||||||
|
files = [files]
|
||||||
|
|
||||||
|
added_media = fireEvent('movie.add', {
|
||||||
|
'info': info,
|
||||||
|
'identifier': l.get('identifier'),
|
||||||
|
'profile_id': profile_id,
|
||||||
|
'category_id': category_id,
|
||||||
|
'title': title
|
||||||
|
}, force_readd = False, search_after = False, update_after = False, notify_after = False, status = status, single = True)
|
||||||
|
|
||||||
|
if not added_media:
|
||||||
|
log.error('Failed adding media %s: %s', (l.get('identifier'), info))
|
||||||
|
continue
|
||||||
|
|
||||||
|
added_media['files'] = added_media.get('files', {})
|
||||||
|
for f in files:
|
||||||
|
ffile = all_files[f.get('file_id')]
|
||||||
|
|
||||||
|
# Only migrate posters
|
||||||
|
if ffile.get('type_id') == poster_type.get('id'):
|
||||||
|
if ffile.get('path') not in added_media['files'].get('image_poster', []) and os.path.isfile(ffile.get('path')):
|
||||||
|
added_media['files']['image_poster'] = [ffile.get('path')]
|
||||||
|
break
|
||||||
|
|
||||||
|
if 'image_poster' in added_media['files']:
|
||||||
|
db.update(added_media)
|
||||||
|
|
||||||
|
for rel in releases:
|
||||||
|
|
||||||
|
empty_info = False
|
||||||
|
if not rel.get('info'):
|
||||||
|
empty_info = True
|
||||||
|
rel['info'] = {}
|
||||||
|
|
||||||
|
quality = quality_link.get(rel.get('quality_id'))
|
||||||
|
if not quality:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for f in release_files:
|
release_status = statuses.get(rel.get('status_id')).get('identifier')
|
||||||
rfile = all_files[f.get('file_id')]
|
|
||||||
file_type = type_by_id.get(rfile.get('type_id')).get('identifier')
|
|
||||||
|
|
||||||
if not release['files'].get(file_type):
|
if rel['info'].get('download_id'):
|
||||||
release['files'][file_type] = []
|
status_support = rel['info'].get('download_status_support', False) in [True, 'true', 'True']
|
||||||
|
rel['info']['download_info'] = {
|
||||||
|
'id': rel['info'].get('download_id'),
|
||||||
|
'downloader': rel['info'].get('download_downloader'),
|
||||||
|
'status_support': status_support,
|
||||||
|
}
|
||||||
|
|
||||||
release['files'][file_type].append(rfile.get('path'))
|
# Add status to keys
|
||||||
|
rel['info']['status'] = release_status
|
||||||
|
if not empty_info:
|
||||||
|
fireEvent('release.create_from_search', [rel['info']], added_media, quality, single = True)
|
||||||
|
else:
|
||||||
|
release = {
|
||||||
|
'_t': 'release',
|
||||||
|
'identifier': rel.get('identifier'),
|
||||||
|
'media_id': added_media.get('_id'),
|
||||||
|
'quality': quality.get('identifier'),
|
||||||
|
'status': release_status,
|
||||||
|
'last_edit': int(time.time()),
|
||||||
|
'files': {}
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
# Add downloader info if provided
|
||||||
rls = db.get('release_identifier', rel.get('identifier'), with_doc = True)['doc']
|
try:
|
||||||
rls.update(release)
|
release['download_info'] = rel['info']['download_info']
|
||||||
db.update(rls)
|
del rel['download_info']
|
||||||
except:
|
except:
|
||||||
db.insert(release)
|
pass
|
||||||
|
|
||||||
|
# Add files
|
||||||
|
release_files = releases_files.get(rel.get('id'), [])
|
||||||
|
if not isinstance(release_files, list):
|
||||||
|
release_files = [release_files]
|
||||||
|
|
||||||
|
if len(release_files) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for f in release_files:
|
||||||
|
rfile = all_files.get(f.get('file_id'))
|
||||||
|
if not rfile:
|
||||||
|
continue
|
||||||
|
|
||||||
|
file_type = type_by_id.get(rfile.get('type_id')).get('identifier')
|
||||||
|
|
||||||
|
if not release['files'].get(file_type):
|
||||||
|
release['files'][file_type] = []
|
||||||
|
|
||||||
|
release['files'][file_type].append(rfile.get('path'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
rls = db.get('release_identifier', rel.get('identifier'), with_doc = True)['doc']
|
||||||
|
rls.update(release)
|
||||||
|
db.update(rls)
|
||||||
|
except:
|
||||||
|
db.insert(release)
|
||||||
|
|
||||||
|
log.info('Total migration took %s', time.time() - migrate_start)
|
||||||
|
log.info('=' * 30)
|
||||||
|
|
||||||
|
rename_old = True
|
||||||
|
|
||||||
|
except OperationalError:
|
||||||
|
log.error('Migrating from faulty database, probably a (too) old version: %s', traceback.format_exc())
|
||||||
|
except:
|
||||||
|
log.error('Migration failed: %s', traceback.format_exc())
|
||||||
|
|
||||||
log.info('Total migration took %s', time.time() - migrate_start)
|
|
||||||
log.info('=' * 30)
|
|
||||||
|
|
||||||
# rename old database
|
# rename old database
|
||||||
log.info('Renaming old database to %s ', old_db + '.old')
|
if rename_old:
|
||||||
os.rename(old_db, old_db + '.old')
|
random = randomString()
|
||||||
|
log.info('Renaming old database to %s ', '%s.%s_old' % (old_db, random))
|
||||||
|
os.rename(old_db, '%s.%s_old' % (old_db, random))
|
||||||
|
|
||||||
if os.path.isfile(old_db + '-wal'):
|
if os.path.isfile(old_db + '-wal'):
|
||||||
os.rename(old_db + '-wal', old_db + '-wal.old')
|
os.rename(old_db + '-wal', '%s-wal.%s_old' % (old_db, random))
|
||||||
if os.path.isfile(old_db + '-shm'):
|
if os.path.isfile(old_db + '-shm'):
|
||||||
os.rename(old_db + '-shm', old_db + '-shm.old')
|
os.rename(old_db + '-shm', '%s-shm.%s_old' % (old_db, random))
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from urllib2 import URLError
|
import os
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
import hashlib
|
import hashlib
|
||||||
import httplib
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import ssl
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import traceback
|
import traceback
|
||||||
import urllib2
|
|
||||||
|
from requests import HTTPError
|
||||||
|
|
||||||
from couchpotato.core._base.downloader.main import DownloaderBase, ReleaseDownloadList
|
from couchpotato.core._base.downloader.main import DownloaderBase, ReleaseDownloadList
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, sp
|
from couchpotato.core.helpers.encoding import tryUrlencode, sp
|
||||||
@@ -35,13 +29,17 @@ class NZBVortex(DownloaderBase):
|
|||||||
|
|
||||||
# Send the nzb
|
# Send the nzb
|
||||||
try:
|
try:
|
||||||
nzb_filename = self.createFileName(data, filedata, media)
|
nzb_filename = self.createFileName(data, filedata, media, unique_tag = True)
|
||||||
self.call('nzb/add', files = {'file': (nzb_filename, filedata)})
|
response = self.call('nzb/add', files = {'file': (nzb_filename, filedata, 'application/octet-stream')}, parameters = {
|
||||||
|
'name': nzb_filename,
|
||||||
|
'groupname': self.conf('group')
|
||||||
|
})
|
||||||
|
|
||||||
time.sleep(10)
|
if response and response.get('result', '').lower() == 'ok':
|
||||||
raw_statuses = self.call('nzb')
|
return self.downloadReturnId(nzb_filename)
|
||||||
nzb_id = [nzb['id'] for nzb in raw_statuses.get('nzbs', []) if os.path.basename(nzb['nzbFileName']) == nzb_filename][0]
|
|
||||||
return self.downloadReturnId(nzb_id)
|
log.error('Something went wrong sending the NZB file. Response: %s', response)
|
||||||
|
return False
|
||||||
except:
|
except:
|
||||||
log.error('Something went wrong sending the NZB file: %s', traceback.format_exc())
|
log.error('Something went wrong sending the NZB file: %s', traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
@@ -60,7 +58,8 @@ class NZBVortex(DownloaderBase):
|
|||||||
|
|
||||||
release_downloads = ReleaseDownloadList(self)
|
release_downloads = ReleaseDownloadList(self)
|
||||||
for nzb in raw_statuses.get('nzbs', []):
|
for nzb in raw_statuses.get('nzbs', []):
|
||||||
if nzb['id'] in ids:
|
nzb_id = os.path.basename(nzb['nzbFileName'])
|
||||||
|
if nzb_id in ids:
|
||||||
|
|
||||||
# Check status
|
# Check status
|
||||||
status = 'busy'
|
status = 'busy'
|
||||||
@@ -70,7 +69,8 @@ class NZBVortex(DownloaderBase):
|
|||||||
status = 'failed'
|
status = 'failed'
|
||||||
|
|
||||||
release_downloads.append({
|
release_downloads.append({
|
||||||
'id': nzb['id'],
|
'temp_id': nzb['id'],
|
||||||
|
'id': nzb_id,
|
||||||
'name': nzb['uiTitle'],
|
'name': nzb['uiTitle'],
|
||||||
'status': status,
|
'status': status,
|
||||||
'original_status': nzb['state'],
|
'original_status': nzb['state'],
|
||||||
@@ -85,7 +85,7 @@ class NZBVortex(DownloaderBase):
|
|||||||
log.info('%s failed downloading, deleting...', release_download['name'])
|
log.info('%s failed downloading, deleting...', release_download['name'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.call('nzb/%s/cancel' % release_download['id'])
|
self.call('nzb/%s/cancel' % release_download['temp_id'])
|
||||||
except:
|
except:
|
||||||
log.error('Failed deleting: %s', traceback.format_exc(0))
|
log.error('Failed deleting: %s', traceback.format_exc(0))
|
||||||
return False
|
return False
|
||||||
@@ -114,7 +114,7 @@ class NZBVortex(DownloaderBase):
|
|||||||
log.error('Login failed, please check you api-key')
|
log.error('Login failed, please check you api-key')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def call(self, call, parameters = None, repeat = False, auth = True, *args, **kwargs):
|
def call(self, call, parameters = None, is_repeat = False, auth = True, *args, **kwargs):
|
||||||
|
|
||||||
# Login first
|
# Login first
|
||||||
if not parameters: parameters = {}
|
if not parameters: parameters = {}
|
||||||
@@ -127,19 +127,20 @@ class NZBVortex(DownloaderBase):
|
|||||||
|
|
||||||
params = tryUrlencode(parameters)
|
params = tryUrlencode(parameters)
|
||||||
|
|
||||||
url = cleanHost(self.conf('host'), ssl = self.conf('ssl')) + 'api/' + call
|
url = cleanHost(self.conf('host')) + 'api/' + call
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = self.urlopen('%s?%s' % (url, params), *args, **kwargs)
|
data = self.getJsonData('%s%s' % (url, '?' + params if params else ''), *args, cache_timeout = 0, show_error = False, **kwargs)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
return json.loads(data)
|
return data
|
||||||
except URLError as e:
|
except HTTPError as e:
|
||||||
if hasattr(e, 'code') and e.code == 403:
|
sc = e.response.status_code
|
||||||
|
if sc == 403:
|
||||||
# Try login and do again
|
# Try login and do again
|
||||||
if not repeat:
|
if not is_repeat:
|
||||||
self.login()
|
self.login()
|
||||||
return self.call(call, parameters = parameters, repeat = True, **kwargs)
|
return self.call(call, parameters = parameters, is_repeat = True, **kwargs)
|
||||||
|
|
||||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
except:
|
except:
|
||||||
@@ -151,13 +152,12 @@ class NZBVortex(DownloaderBase):
|
|||||||
|
|
||||||
if not self.api_level:
|
if not self.api_level:
|
||||||
|
|
||||||
url = cleanHost(self.conf('host')) + 'api/app/apilevel'
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = self.urlopen(url, show_error = False)
|
data = self.call('app/apilevel', auth = False)
|
||||||
self.api_level = float(json.loads(data).get('apilevel'))
|
self.api_level = float(data.get('apilevel'))
|
||||||
except URLError as e:
|
except HTTPError as e:
|
||||||
if hasattr(e, 'code') and e.code == 403:
|
sc = e.response.status_code
|
||||||
|
if sc == 403:
|
||||||
log.error('This version of NZBVortex isn\'t supported. Please update to 2.8.6 or higher')
|
log.error('This version of NZBVortex isn\'t supported. Please update to 2.8.6 or higher')
|
||||||
else:
|
else:
|
||||||
log.error('NZBVortex doesn\'t seem to be running or maybe the remote option isn\'t enabled yet: %s', traceback.format_exc(1))
|
log.error('NZBVortex doesn\'t seem to be running or maybe the remote option isn\'t enabled yet: %s', traceback.format_exc(1))
|
||||||
@@ -169,29 +169,6 @@ class NZBVortex(DownloaderBase):
|
|||||||
return super(NZBVortex, self).isEnabled(manual, data) and self.getApiLevel()
|
return super(NZBVortex, self).isEnabled(manual, data) and self.getApiLevel()
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
config = [{
|
config = [{
|
||||||
'name': 'nzbvortex',
|
'name': 'nzbvortex',
|
||||||
'groups': [
|
'groups': [
|
||||||
@@ -211,20 +188,18 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'host',
|
'name': 'host',
|
||||||
'default': 'localhost:4321',
|
'default': 'https://localhost:4321',
|
||||||
'description': 'Hostname with port. Usually <strong>localhost:4321</strong>',
|
'description': 'Hostname with port. Usually <strong>https://localhost:4321</strong>',
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'ssl',
|
|
||||||
'default': 1,
|
|
||||||
'type': 'bool',
|
|
||||||
'advanced': True,
|
|
||||||
'description': 'Use HyperText Transfer Protocol Secure, or <strong>https</strong>',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'api_key',
|
'name': 'api_key',
|
||||||
'label': 'Api Key',
|
'label': 'Api Key',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'group',
|
||||||
|
'label': 'Group',
|
||||||
|
'description': 'The group CP places the nzb in. Make sure to create it in NZBVortex.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'manual',
|
'name': 'manual',
|
||||||
'default': False,
|
'default': False,
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ from urlparse import urlparse
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from couchpotato.core._base.downloader.main import DownloaderBase, ReleaseDownloadList
|
from couchpotato.core._base.downloader.main import DownloaderBase, ReleaseDownloadList
|
||||||
|
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.encoding import sp
|
from couchpotato.core.helpers.encoding import sp
|
||||||
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 bencode import bencode, bdecode
|
from bencode import bencode, bdecode
|
||||||
from rtorrent import RTorrent
|
from rtorrent import RTorrent
|
||||||
from scandir import scandir
|
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -154,19 +152,13 @@ class rTorrent(DownloaderBase):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def getTorrentStatus(self, torrent):
|
def getTorrentStatus(self, torrent):
|
||||||
if torrent.hashing or torrent.hash_checking or torrent.message:
|
|
||||||
return 'busy'
|
|
||||||
|
|
||||||
if not torrent.complete:
|
if not torrent.complete:
|
||||||
return 'busy'
|
return 'busy'
|
||||||
|
|
||||||
if not torrent.open:
|
if torrent.open:
|
||||||
return 'completed'
|
|
||||||
|
|
||||||
if torrent.state and torrent.active:
|
|
||||||
return 'seeding'
|
return 'seeding'
|
||||||
|
|
||||||
return 'busy'
|
return 'completed'
|
||||||
|
|
||||||
def getAllDownloadStatus(self, ids):
|
def getAllDownloadStatus(self, ids):
|
||||||
log.debug('Checking rTorrent download status.')
|
log.debug('Checking rTorrent download status.')
|
||||||
@@ -244,7 +236,7 @@ class rTorrent(DownloaderBase):
|
|||||||
if torrent.is_multi_file() and torrent.directory.endswith(torrent.name):
|
if torrent.is_multi_file() and torrent.directory.endswith(torrent.name):
|
||||||
# Remove empty directories bottom up
|
# Remove empty directories bottom up
|
||||||
try:
|
try:
|
||||||
for path, _, _ in scandir.walk(torrent.directory, topdown = False):
|
for path, _, _ in os.walk(sp(torrent.directory), topdown = False):
|
||||||
os.rmdir(path)
|
os.rmdir(path)
|
||||||
except OSError:
|
except OSError:
|
||||||
log.info('Directory "%s" contains extra files, unable to remove', torrent.directory)
|
log.info('Directory "%s" contains extra files, unable to remove', torrent.directory)
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ class SynologyRPC(object):
|
|||||||
|
|
||||||
self.download_url = 'http://%s:%s/webapi/DownloadStation/task.cgi' % (host, port)
|
self.download_url = 'http://%s:%s/webapi/DownloadStation/task.cgi' % (host, port)
|
||||||
self.auth_url = 'http://%s:%s/webapi/auth.cgi' % (host, port)
|
self.auth_url = 'http://%s:%s/webapi/auth.cgi' % (host, port)
|
||||||
|
self.sid = None
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
self.destination = destination
|
self.destination = destination
|
||||||
|
|||||||
@@ -23,16 +23,14 @@ class Transmission(DownloaderBase):
|
|||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
trpc = None
|
trpc = None
|
||||||
|
|
||||||
def connect(self, reconnect = False):
|
def connect(self):
|
||||||
# Load host from config and split out port.
|
# Load host from config and split out port.
|
||||||
host = cleanHost(self.conf('host'), protocol = False).split(':')
|
host = cleanHost(self.conf('host')).rstrip('/').rsplit(':', 1)
|
||||||
if not isInt(host[1]):
|
if not isInt(host[1]):
|
||||||
log.error('Config properties are not filled in correctly, port is missing.')
|
log.error('Config properties are not filled in correctly, port is missing.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.trpc or reconnect:
|
self.trpc = TransmissionRPC(host[0], port = host[1], rpc_url = self.conf('rpc_url').strip('/ '), username = self.conf('username'), password = self.conf('password'))
|
||||||
self.trpc = TransmissionRPC(host[0], port = host[1], rpc_url = self.conf('rpc_url').strip('/ '), username = self.conf('username'), password = self.conf('password'))
|
|
||||||
|
|
||||||
return self.trpc
|
return self.trpc
|
||||||
|
|
||||||
def download(self, data = None, media = None, filedata = None):
|
def download(self, data = None, media = None, filedata = None):
|
||||||
@@ -88,7 +86,7 @@ class Transmission(DownloaderBase):
|
|||||||
return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
|
return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
if self.connect(True) and self.trpc.get_session():
|
if self.connect() and self.trpc.get_session():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -164,18 +162,18 @@ class Transmission(DownloaderBase):
|
|||||||
class TransmissionRPC(object):
|
class TransmissionRPC(object):
|
||||||
|
|
||||||
"""TransmissionRPC lite library"""
|
"""TransmissionRPC lite library"""
|
||||||
def __init__(self, host = 'localhost', port = 9091, rpc_url = 'transmission', username = None, password = None):
|
def __init__(self, host = 'http://localhost', port = 9091, rpc_url = 'transmission', username = None, password = None):
|
||||||
|
|
||||||
super(TransmissionRPC, self).__init__()
|
super(TransmissionRPC, self).__init__()
|
||||||
|
|
||||||
self.url = 'http://' + host + ':' + str(port) + '/' + rpc_url + '/rpc'
|
self.url = host + ':' + str(port) + '/' + rpc_url + '/rpc'
|
||||||
self.tag = 0
|
self.tag = 0
|
||||||
self.session_id = 0
|
self.session_id = 0
|
||||||
self.session = {}
|
self.session = {}
|
||||||
if username and password:
|
if username and password:
|
||||||
password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||||
password_manager.add_password(realm = None, uri = self.url, user = username, passwd = password)
|
password_manager.add_password(realm = 'Transmission', uri = self.url, user = username, passwd = password)
|
||||||
opener = urllib2.build_opener(urllib2.HTTPBasicAuthHandler(password_manager), urllib2.HTTPDigestAuthHandler(password_manager))
|
opener = urllib2.build_opener(urllib2.HTTPBasicAuthHandler(password_manager))
|
||||||
opener.addheaders = [('User-agent', 'couchpotato-transmission-client/1.0')]
|
opener.addheaders = [('User-agent', 'couchpotato-transmission-client/1.0')]
|
||||||
urllib2.install_opener(opener)
|
urllib2.install_opener(opener)
|
||||||
elif username or password:
|
elif username or password:
|
||||||
@@ -276,8 +274,8 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'host',
|
'name': 'host',
|
||||||
'default': 'localhost:9091',
|
'default': 'http://localhost:9091',
|
||||||
'description': 'Hostname with port. Usually <strong>localhost:9091</strong>',
|
'description': 'Hostname with port. Usually <strong>http://localhost:9091</strong>',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'rpc_url',
|
'name': 'rpc_url',
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ class uTorrent(DownloaderBase):
|
|||||||
status = 'busy'
|
status = 'busy'
|
||||||
if (torrent[1] & self.status_flags['STARTED'] or torrent[1] & self.status_flags['QUEUED']) and torrent[4] == 1000:
|
if (torrent[1] & self.status_flags['STARTED'] or torrent[1] & self.status_flags['QUEUED']) and torrent[4] == 1000:
|
||||||
status = 'seeding'
|
status = 'seeding'
|
||||||
elif (torrent[1] & self.status_flags['ERROR']):
|
elif torrent[1] & self.status_flags['ERROR']:
|
||||||
status = 'failed'
|
status = 'failed'
|
||||||
elif torrent[4] == 1000:
|
elif torrent[4] == 1000:
|
||||||
status = 'completed'
|
status = 'completed'
|
||||||
@@ -229,7 +229,6 @@ class uTorrentAPI(object):
|
|||||||
password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||||
password_manager.add_password(realm = None, uri = self.url, user = username, passwd = password)
|
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.HTTPBasicAuthHandler(password_manager))
|
||||||
self.opener.add_handler(urllib2.HTTPDigestAuthHandler(password_manager))
|
|
||||||
elif username or password:
|
elif username or password:
|
||||||
log.debug('User or password missing, not using authentication.')
|
log.debug('User or password missing, not using authentication.')
|
||||||
self.token = self.get_token()
|
self.token = self.get_token()
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ def fireEvent(name, *args, **kwargs):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
e = Event(name = name, threads = 10, exc_info = True, traceback = True, lock = threading.RLock())
|
e = Event(name = name, threads = 10, exc_info = True, traceback = True)
|
||||||
|
|
||||||
for event in events[name]:
|
for event in events[name]:
|
||||||
e.handle(event['handler'], priority = event['priority'])
|
e.handle(event['handler'], priority = event['priority'])
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import re
|
|||||||
import traceback
|
import traceback
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
|
from chardet import detect
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
import six
|
import six
|
||||||
|
|
||||||
@@ -35,6 +36,9 @@ def toUnicode(original, *args):
|
|||||||
return six.text_type(original, *args)
|
return six.text_type(original, *args)
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
|
detected = detect(original)
|
||||||
|
if detected.get('encoding') == 'utf-8':
|
||||||
|
return original.decode('utf-8')
|
||||||
return ek(original, *args)
|
return ek(original, *args)
|
||||||
except:
|
except:
|
||||||
raise
|
raise
|
||||||
@@ -43,6 +47,17 @@ def toUnicode(original, *args):
|
|||||||
ascii_text = str(original).encode('string_escape')
|
ascii_text = str(original).encode('string_escape')
|
||||||
return toUnicode(ascii_text)
|
return toUnicode(ascii_text)
|
||||||
|
|
||||||
|
def toUTF8(original):
|
||||||
|
try:
|
||||||
|
if isinstance(original, str) and len(original) > 0:
|
||||||
|
# Try to detect
|
||||||
|
detected = detect(original)
|
||||||
|
return original.decode(detected.get('encoding')).encode('utf-8')
|
||||||
|
else:
|
||||||
|
return original
|
||||||
|
except:
|
||||||
|
#log.error('Failed encoding to UTF8: %s', traceback.format_exc())
|
||||||
|
raise
|
||||||
|
|
||||||
def ss(original, *args):
|
def ss(original, *args):
|
||||||
|
|
||||||
@@ -52,7 +67,10 @@ def ss(original, *args):
|
|||||||
return u_original.encode(Env.get('encoding'))
|
return u_original.encode(Env.get('encoding'))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug('Failed ss encoding char, force UTF8: %s', e)
|
log.debug('Failed ss encoding char, force UTF8: %s', e)
|
||||||
return u_original.encode('UTF-8')
|
try:
|
||||||
|
return u_original.encode(Env.get('encoding'), 'replace')
|
||||||
|
except:
|
||||||
|
return u_original.encode('utf-8', 'replace')
|
||||||
|
|
||||||
|
|
||||||
def sp(path, *args):
|
def sp(path, *args):
|
||||||
@@ -78,7 +96,7 @@ def sp(path, *args):
|
|||||||
# Replace *NIX ambiguous '//' at the beginning of a path with '/' (crashes guessit)
|
# Replace *NIX ambiguous '//' at the beginning of a path with '/' (crashes guessit)
|
||||||
path = re.sub('^//', '/', path)
|
path = re.sub('^//', '/', path)
|
||||||
|
|
||||||
return toUnicode(path)
|
return path
|
||||||
|
|
||||||
|
|
||||||
def ek(original, *args):
|
def ek(original, *args):
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import os
|
||||||
|
from chardet import detect
|
||||||
|
from couchpotato import Env
|
||||||
|
|
||||||
|
fs_enc = Env.get('fs_encoding')
|
||||||
|
|
||||||
|
|
||||||
|
def list_dir(path, full_path = True):
|
||||||
|
"""
|
||||||
|
List directory don't error when it doesn't exist
|
||||||
|
"""
|
||||||
|
|
||||||
|
path = unicode_path(path)
|
||||||
|
|
||||||
|
if os.path.isdir(path):
|
||||||
|
for f in os.listdir(path):
|
||||||
|
if full_path:
|
||||||
|
yield join(path, f)
|
||||||
|
else:
|
||||||
|
yield f
|
||||||
|
|
||||||
|
|
||||||
|
def join(*args):
|
||||||
|
"""
|
||||||
|
Join path, encode properly before joining
|
||||||
|
"""
|
||||||
|
|
||||||
|
return os.path.join(*[safe(x) for x in args])
|
||||||
|
|
||||||
|
|
||||||
|
def unicode_path(path):
|
||||||
|
"""
|
||||||
|
Convert back to unicode
|
||||||
|
:param path: path string
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(path, str):
|
||||||
|
detected = detect(path)
|
||||||
|
print detected
|
||||||
|
path = path.decode(detected.get('encoding'))
|
||||||
|
path = path.decode('unicode_escape')
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def safe(path):
|
||||||
|
|
||||||
|
if isinstance(path, unicode):
|
||||||
|
return path.encode('unicode_escape')
|
||||||
|
|
||||||
|
return path
|
||||||
Regular → Executable
+108
-5
@@ -1,4 +1,5 @@
|
|||||||
import collections
|
import collections
|
||||||
|
import ctypes
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
@@ -6,8 +7,9 @@ import random
|
|||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
from couchpotato.core.helpers.encoding import simplifyString, toSafeString, ss
|
from couchpotato.core.helpers.encoding import simplifyString, toSafeString, ss, sp
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
import six
|
import six
|
||||||
from six.moves import map, zip, filter
|
from six.moves import map, zip, filter
|
||||||
@@ -39,11 +41,11 @@ def symlink(src, dst):
|
|||||||
def getUserDir():
|
def getUserDir():
|
||||||
try:
|
try:
|
||||||
import pwd
|
import pwd
|
||||||
os.environ['HOME'] = pwd.getpwuid(os.geteuid()).pw_dir
|
os.environ['HOME'] = sp(pwd.getpwuid(os.geteuid()).pw_dir)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return os.path.expanduser('~')
|
return sp(os.path.expanduser('~'))
|
||||||
|
|
||||||
|
|
||||||
def getDownloadDir():
|
def getDownloadDir():
|
||||||
@@ -290,9 +292,14 @@ def dictIsSubset(a, b):
|
|||||||
return all([k in b and b[k] == v for k, v in a.items()])
|
return all([k in b and b[k] == v for k, v in a.items()])
|
||||||
|
|
||||||
|
|
||||||
|
# Returns True if sub_folder is the same as or inside base_folder
|
||||||
def isSubFolder(sub_folder, base_folder):
|
def isSubFolder(sub_folder, base_folder):
|
||||||
# Returns True if sub_folder is the same as or inside base_folder
|
if base_folder and sub_folder:
|
||||||
return base_folder and sub_folder and ss(os.path.normpath(base_folder).rstrip(os.path.sep) + os.path.sep) in ss(os.path.normpath(sub_folder).rstrip(os.path.sep) + os.path.sep)
|
base = sp(os.path.realpath(base_folder)) + os.path.sep
|
||||||
|
subfolder = sp(os.path.realpath(sub_folder)) + os.path.sep
|
||||||
|
return os.path.commonprefix([subfolder, base]) == base
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# From SABNZBD
|
# From SABNZBD
|
||||||
@@ -307,3 +314,99 @@ def scanForPassword(name):
|
|||||||
|
|
||||||
if m:
|
if m:
|
||||||
return m.group(1).strip('. '), m.group(2).strip()
|
return m.group(1).strip('. '), m.group(2).strip()
|
||||||
|
|
||||||
|
|
||||||
|
under_pat = re.compile(r'_([a-z])')
|
||||||
|
|
||||||
|
def underscoreToCamel(name):
|
||||||
|
return under_pat.sub(lambda x: x.group(1).upper(), name)
|
||||||
|
|
||||||
|
|
||||||
|
def removePyc(folder, only_excess = True, show_logs = True):
|
||||||
|
|
||||||
|
folder = sp(folder)
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(folder):
|
||||||
|
|
||||||
|
pyc_files = filter(lambda filename: filename.endswith('.pyc'), files)
|
||||||
|
py_files = set(filter(lambda filename: filename.endswith('.py'), files))
|
||||||
|
excess_pyc_files = filter(lambda pyc_filename: pyc_filename[:-1] not in py_files, pyc_files) if only_excess else pyc_files
|
||||||
|
|
||||||
|
for excess_pyc_file in excess_pyc_files:
|
||||||
|
full_path = os.path.join(root, excess_pyc_file)
|
||||||
|
if show_logs: log.debug('Removing old PYC file: %s', full_path)
|
||||||
|
try:
|
||||||
|
os.remove(full_path)
|
||||||
|
except:
|
||||||
|
log.error('Couldn\'t remove %s: %s', (full_path, traceback.format_exc()))
|
||||||
|
|
||||||
|
for dir_name in dirs:
|
||||||
|
full_path = os.path.join(root, dir_name)
|
||||||
|
if len(os.listdir(full_path)) == 0:
|
||||||
|
try:
|
||||||
|
os.rmdir(full_path)
|
||||||
|
except:
|
||||||
|
log.error('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc()))
|
||||||
|
|
||||||
|
|
||||||
|
def getFreeSpace(directories):
|
||||||
|
|
||||||
|
single = not isinstance(directories, (tuple, list))
|
||||||
|
if single:
|
||||||
|
directories = [directories]
|
||||||
|
|
||||||
|
free_space = {}
|
||||||
|
for folder in directories:
|
||||||
|
|
||||||
|
size = None
|
||||||
|
if os.path.isdir(folder):
|
||||||
|
if os.name == 'nt':
|
||||||
|
_, total, free = ctypes.c_ulonglong(), ctypes.c_ulonglong(), \
|
||||||
|
ctypes.c_ulonglong()
|
||||||
|
if sys.version_info >= (3,) or isinstance(folder, unicode):
|
||||||
|
fun = ctypes.windll.kernel32.GetDiskFreeSpaceExW #@UndefinedVariable
|
||||||
|
else:
|
||||||
|
fun = ctypes.windll.kernel32.GetDiskFreeSpaceExA #@UndefinedVariable
|
||||||
|
ret = fun(folder, ctypes.byref(_), ctypes.byref(total), ctypes.byref(free))
|
||||||
|
if ret == 0:
|
||||||
|
raise ctypes.WinError()
|
||||||
|
return [total.value, free.value]
|
||||||
|
else:
|
||||||
|
s = os.statvfs(folder)
|
||||||
|
size = [s.f_blocks * s.f_frsize / (1024 * 1024), (s.f_bavail * s.f_frsize) / (1024 * 1024)]
|
||||||
|
|
||||||
|
if single: return size
|
||||||
|
|
||||||
|
free_space[folder] = size
|
||||||
|
|
||||||
|
return free_space
|
||||||
|
|
||||||
|
|
||||||
|
def getSize(paths):
|
||||||
|
|
||||||
|
single = not isinstance(paths, (tuple, list))
|
||||||
|
if single:
|
||||||
|
paths = [paths]
|
||||||
|
|
||||||
|
total_size = 0
|
||||||
|
for path in paths:
|
||||||
|
path = sp(path)
|
||||||
|
|
||||||
|
if os.path.isdir(path):
|
||||||
|
total_size = 0
|
||||||
|
for dirpath, _, filenames in os.walk(path):
|
||||||
|
for f in filenames:
|
||||||
|
total_size += os.path.getsize(sp(os.path.join(dirpath, f)))
|
||||||
|
|
||||||
|
elif os.path.isfile(path):
|
||||||
|
total_size += os.path.getsize(path)
|
||||||
|
|
||||||
|
return total_size / 1048576 # MB
|
||||||
|
|
||||||
|
|
||||||
|
def find(func, iterable):
|
||||||
|
for item in iterable:
|
||||||
|
if func(item):
|
||||||
|
return item
|
||||||
|
|
||||||
|
return None
|
||||||
|
|||||||
+17
-11
@@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
class CPLog(object):
|
class CPLog(object):
|
||||||
@@ -25,6 +26,12 @@ class CPLog(object):
|
|||||||
self.Env = Env
|
self.Env = Env
|
||||||
self.is_develop = Env.get('dev')
|
self.is_develop = Env.get('dev')
|
||||||
|
|
||||||
|
from couchpotato.core.event import addEvent
|
||||||
|
addEvent('app.after_shutdown', self.close)
|
||||||
|
|
||||||
|
def close(self, *args, **kwargs):
|
||||||
|
logging.shutdown()
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
@@ -48,20 +55,19 @@ class CPLog(object):
|
|||||||
|
|
||||||
def safeMessage(self, msg, replace_tuple = ()):
|
def safeMessage(self, msg, replace_tuple = ()):
|
||||||
|
|
||||||
from couchpotato.core.helpers.encoding import ss, toUnicode
|
from couchpotato.core.helpers.encoding import ss, toUTF8
|
||||||
|
|
||||||
msg = ss(msg)
|
msg = toUTF8(msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msg = msg % replace_tuple
|
if isinstance(replace_tuple, tuple):
|
||||||
|
msg = msg % tuple([toUTF8(x) for x in list(replace_tuple)])
|
||||||
|
elif isinstance(replace_tuple, dict):
|
||||||
|
msg = msg % dict((k, toUTF8(v)) for k, v in replace_tuple.iteritems())
|
||||||
|
else:
|
||||||
|
msg = msg % toUTF8(replace_tuple)
|
||||||
except:
|
except:
|
||||||
try:
|
self.logger.error('Failed encoding stuff to log "%s": %s' % (msg, traceback.format_exc()))
|
||||||
if isinstance(replace_tuple, tuple):
|
|
||||||
msg = msg % tuple([ss(x) for x in list(replace_tuple)])
|
|
||||||
else:
|
|
||||||
msg = msg % ss(replace_tuple)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error('Failed encoding stuff to log "%s": %s' % (msg, e))
|
|
||||||
|
|
||||||
self.setup()
|
self.setup()
|
||||||
if not self.is_develop:
|
if not self.is_develop:
|
||||||
@@ -78,4 +84,4 @@ class CPLog(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return toUnicode(msg)
|
return toUTF8(msg)
|
||||||
|
|||||||
Regular → Executable
+14
-12
@@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from couchpotato import get_db, CPLog
|
from couchpotato import CPLog
|
||||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
@@ -25,11 +25,10 @@ class MediaBase(Plugin):
|
|||||||
|
|
||||||
def onComplete():
|
def onComplete():
|
||||||
try:
|
try:
|
||||||
db = get_db()
|
|
||||||
media = fireEvent('media.get', media_id, single = True)
|
media = fireEvent('media.get', media_id, single = True)
|
||||||
event_name = '%s.searcher.single' % media.get('type')
|
if media:
|
||||||
|
event_name = '%s.searcher.single' % media.get('type')
|
||||||
fireEventAsync(event_name, media, on_complete = self.createNotifyFront(media_id))
|
fireEventAsync(event_name, media, on_complete = self.createNotifyFront(media_id), manual = True)
|
||||||
except:
|
except:
|
||||||
log.error('Failed creating onComplete: %s', traceback.format_exc())
|
log.error('Failed creating onComplete: %s', traceback.format_exc())
|
||||||
|
|
||||||
@@ -40,9 +39,9 @@ class MediaBase(Plugin):
|
|||||||
def notifyFront():
|
def notifyFront():
|
||||||
try:
|
try:
|
||||||
media = fireEvent('media.get', media_id, single = True)
|
media = fireEvent('media.get', media_id, single = True)
|
||||||
event_name = '%s.update' % media.get('type')
|
if media:
|
||||||
|
event_name = '%s.update' % media.get('type')
|
||||||
fireEvent('notify.frontend', type = event_name, data = media)
|
fireEvent('notify.frontend', type = event_name, data = media)
|
||||||
except:
|
except:
|
||||||
log.error('Failed creating onComplete: %s', traceback.format_exc())
|
log.error('Failed creating onComplete: %s', traceback.format_exc())
|
||||||
|
|
||||||
@@ -66,10 +65,13 @@ class MediaBase(Plugin):
|
|||||||
|
|
||||||
return def_title or 'UNKNOWN'
|
return def_title or 'UNKNOWN'
|
||||||
|
|
||||||
def getPoster(self, image_urls, existing_files):
|
def getPoster(self, media, image_urls):
|
||||||
image_type = 'poster'
|
if 'files' not in media:
|
||||||
|
media['files'] = {}
|
||||||
|
|
||||||
# Remove non-existing files
|
existing_files = media['files']
|
||||||
|
|
||||||
|
image_type = 'poster'
|
||||||
file_type = 'image_%s' % image_type
|
file_type = 'image_%s' % image_type
|
||||||
|
|
||||||
# Make existing unique
|
# Make existing unique
|
||||||
@@ -93,7 +95,7 @@ class MediaBase(Plugin):
|
|||||||
if file_type not in existing_files or len(existing_files.get(file_type, [])) == 0:
|
if file_type not in existing_files or len(existing_files.get(file_type, [])) == 0:
|
||||||
file_path = fireEvent('file.download', url = image, single = True)
|
file_path = fireEvent('file.download', url = image, single = True)
|
||||||
if file_path:
|
if file_path:
|
||||||
existing_files[file_type] = [file_path]
|
existing_files[file_type] = [toUnicode(file_path)]
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|||||||
Regular → Executable
+110
@@ -1,10 +1,47 @@
|
|||||||
|
from couchpotato import get_db
|
||||||
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media._base.library.base import LibraryBase
|
from couchpotato.core.media._base.library.base import LibraryBase
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Library(LibraryBase):
|
class Library(LibraryBase):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('library.title', self.title)
|
addEvent('library.title', self.title)
|
||||||
|
addEvent('library.related', self.related)
|
||||||
|
addEvent('library.tree', self.tree)
|
||||||
|
|
||||||
|
addEvent('library.root', self.root)
|
||||||
|
|
||||||
|
addApiView('library.query', self.queryView)
|
||||||
|
addApiView('library.related', self.relatedView)
|
||||||
|
addApiView('library.tree', self.treeView)
|
||||||
|
|
||||||
|
def queryView(self, media_id, **kwargs):
|
||||||
|
db = get_db()
|
||||||
|
media = db.get('id', media_id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'result': fireEvent('library.query', media, single = True)
|
||||||
|
}
|
||||||
|
|
||||||
|
def relatedView(self, media_id, **kwargs):
|
||||||
|
db = get_db()
|
||||||
|
media = db.get('id', media_id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'result': fireEvent('library.related', media, single = True)
|
||||||
|
}
|
||||||
|
|
||||||
|
def treeView(self, media_id, **kwargs):
|
||||||
|
db = get_db()
|
||||||
|
media = db.get('id', media_id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'result': fireEvent('library.tree', media, single = True)
|
||||||
|
}
|
||||||
|
|
||||||
def title(self, library):
|
def title(self, library):
|
||||||
return fireEvent(
|
return fireEvent(
|
||||||
@@ -16,3 +53,76 @@ class Library(LibraryBase):
|
|||||||
include_identifier = False,
|
include_identifier = False,
|
||||||
single = True
|
single = True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def related(self, media):
|
||||||
|
result = {self.key(media['type']): media}
|
||||||
|
|
||||||
|
db = get_db()
|
||||||
|
cur = media
|
||||||
|
|
||||||
|
while cur and cur.get('parent_id'):
|
||||||
|
cur = db.get('id', cur['parent_id'])
|
||||||
|
|
||||||
|
result[self.key(cur['type'])] = cur
|
||||||
|
|
||||||
|
children = db.get_many('media_children', media['_id'], with_doc = True)
|
||||||
|
|
||||||
|
for item in children:
|
||||||
|
key = self.key(item['doc']['type']) + 's'
|
||||||
|
|
||||||
|
if key not in result:
|
||||||
|
result[key] = []
|
||||||
|
|
||||||
|
result[key].append(item['doc'])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def root(self, media):
|
||||||
|
db = get_db()
|
||||||
|
cur = media
|
||||||
|
|
||||||
|
while cur and cur.get('parent_id'):
|
||||||
|
cur = db.get('id', cur['parent_id'])
|
||||||
|
|
||||||
|
return cur
|
||||||
|
|
||||||
|
def tree(self, media = None, media_id = None):
|
||||||
|
db = get_db()
|
||||||
|
|
||||||
|
if media:
|
||||||
|
result = media
|
||||||
|
elif media_id:
|
||||||
|
result = db.get('id', media_id, with_doc = True)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Find children
|
||||||
|
items = db.get_many('media_children', result['_id'], with_doc = True)
|
||||||
|
keys = []
|
||||||
|
|
||||||
|
# Build children arrays
|
||||||
|
for item in items:
|
||||||
|
key = self.key(item['doc']['type']) + 's'
|
||||||
|
|
||||||
|
if key not in result:
|
||||||
|
result[key] = {}
|
||||||
|
elif type(result[key]) is not dict:
|
||||||
|
result[key] = {}
|
||||||
|
|
||||||
|
if key not in keys:
|
||||||
|
keys.append(key)
|
||||||
|
|
||||||
|
result[key][item['_id']] = fireEvent('library.tree', item['doc'], single = True)
|
||||||
|
|
||||||
|
# Unique children
|
||||||
|
for key in keys:
|
||||||
|
result[key] = result[key].values()
|
||||||
|
|
||||||
|
# Include releases
|
||||||
|
result['releases'] = fireEvent('release.for_media', result['_id'], single = True)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def key(self, media_type):
|
||||||
|
parts = media_type.split('.')
|
||||||
|
return parts[-1]
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class Matcher(MatcherBase):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def correctTitle(self, chain, media):
|
def correctTitle(self, chain, media):
|
||||||
root_library = media['library']['root_library']
|
root = fireEvent('library.root', media, single = True)
|
||||||
|
|
||||||
if 'show_name' not in chain.info or not len(chain.info['show_name']):
|
if 'show_name' not in chain.info or not len(chain.info['show_name']):
|
||||||
log.info('Wrong: missing show name in parsed result')
|
log.info('Wrong: missing show name in parsed result')
|
||||||
@@ -50,10 +50,10 @@ class Matcher(MatcherBase):
|
|||||||
chain_words = [x.lower() for x in chain.info['show_name']]
|
chain_words = [x.lower() for x in chain.info['show_name']]
|
||||||
|
|
||||||
# Build a list of possible titles of the media we are searching for
|
# Build a list of possible titles of the media we are searching for
|
||||||
titles = root_library['info']['titles']
|
titles = root['info']['titles']
|
||||||
|
|
||||||
# Add year suffix titles (will result in ['<name_one>', '<name_one> <suffix_one>', '<name_two>', ...])
|
# Add year suffix titles (will result in ['<name_one>', '<name_one> <suffix_one>', '<name_two>', ...])
|
||||||
suffixes = [None, root_library['info']['year']]
|
suffixes = [None, root['info']['year']]
|
||||||
|
|
||||||
titles = [
|
titles = [
|
||||||
title + ((' %s' % suffix) if suffix else '')
|
title + ((' %s' % suffix) if suffix else '')
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ from couchpotato.core.helpers.encoding import simplifyString"""
|
|||||||
|
|
||||||
|
|
||||||
class TitleIndex(TreeBasedIndex):
|
class TitleIndex(TreeBasedIndex):
|
||||||
_version = 2
|
_version = 4
|
||||||
|
|
||||||
custom_header = """from CodernityDB.tree_index import TreeBasedIndex
|
custom_header = """from CodernityDB.tree_index import TreeBasedIndex
|
||||||
from string import ascii_letters
|
from string import ascii_letters
|
||||||
@@ -123,16 +123,16 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString"""
|
|||||||
nr_prefix = '' if title and len(title) > 0 and title[0] in ascii_letters else '#'
|
nr_prefix = '' if title and len(title) > 0 and title[0] in ascii_letters else '#'
|
||||||
title = simplifyString(title)
|
title = simplifyString(title)
|
||||||
|
|
||||||
for prefix in ['the ']:
|
for prefix in ['the ', 'an ', 'a ']:
|
||||||
if prefix == title[:len(prefix)]:
|
if prefix == title[:len(prefix)]:
|
||||||
title = title[len(prefix):]
|
title = title[len(prefix):]
|
||||||
break
|
break
|
||||||
|
|
||||||
return str(nr_prefix + title).ljust(32, '_')[:32]
|
return str(nr_prefix + title).ljust(32, ' ')[:32]
|
||||||
|
|
||||||
|
|
||||||
class StartsWithIndex(TreeBasedIndex):
|
class StartsWithIndex(TreeBasedIndex):
|
||||||
_version = 2
|
_version = 3
|
||||||
|
|
||||||
custom_header = """from CodernityDB.tree_index import TreeBasedIndex
|
custom_header = """from CodernityDB.tree_index import TreeBasedIndex
|
||||||
from string import ascii_letters
|
from string import ascii_letters
|
||||||
@@ -153,7 +153,7 @@ from couchpotato.core.helpers.encoding import toUnicode, simplifyString"""
|
|||||||
title = toUnicode(title)
|
title = toUnicode(title)
|
||||||
title = simplifyString(title)
|
title = simplifyString(title)
|
||||||
|
|
||||||
for prefix in ['the ']:
|
for prefix in ['the ', 'an ', 'a ']:
|
||||||
if prefix == title[:len(prefix)]:
|
if prefix == title[:len(prefix)]:
|
||||||
title = title[len(prefix):]
|
title = title[len(prefix):]
|
||||||
break
|
break
|
||||||
@@ -176,3 +176,24 @@ class MediaChildrenIndex(TreeBasedIndex):
|
|||||||
if data.get('_t') == 'media' and data.get('parent_id'):
|
if data.get('_t') == 'media' and data.get('parent_id'):
|
||||||
return data.get('parent_id'), None
|
return data.get('parent_id'), None
|
||||||
|
|
||||||
|
|
||||||
|
class MediaTagIndex(MultiTreeBasedIndex):
|
||||||
|
_version = 2
|
||||||
|
|
||||||
|
custom_header = """from CodernityDB.tree_index import MultiTreeBasedIndex"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs['key_format'] = '32s'
|
||||||
|
super(MediaTagIndex, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def make_key_value(self, data):
|
||||||
|
if data.get('_t') == 'media' and data.get('tags') and len(data.get('tags', [])) > 0:
|
||||||
|
|
||||||
|
tags = set()
|
||||||
|
for tag in data.get('tags', []):
|
||||||
|
tags.add(self.make_key(tag))
|
||||||
|
|
||||||
|
return list(tags), None
|
||||||
|
|
||||||
|
def make_key(self, key):
|
||||||
|
return md5(key).hexdigest()
|
||||||
|
|||||||
Regular → Executable
+183
-55
@@ -1,6 +1,9 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from string import ascii_lowercase
|
from string import ascii_lowercase
|
||||||
|
|
||||||
|
from CodernityDB.database import RecordNotFound, RecordDeleted
|
||||||
from couchpotato import tryInt, get_db
|
from couchpotato import tryInt, get_db
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
||||||
@@ -8,7 +11,7 @@ from couchpotato.core.helpers.encoding import toUnicode
|
|||||||
from couchpotato.core.helpers.variable import splitString, getImdb, getTitle
|
from couchpotato.core.helpers.variable import splitString, getImdb, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media import MediaBase
|
from couchpotato.core.media import MediaBase
|
||||||
from .index import MediaIndex, MediaStatusIndex, MediaTypeIndex, TitleSearchIndex, TitleIndex, StartsWithIndex, MediaChildrenIndex
|
from .index import MediaIndex, MediaStatusIndex, MediaTypeIndex, TitleSearchIndex, TitleIndex, StartsWithIndex, MediaChildrenIndex, MediaTagIndex
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -20,6 +23,7 @@ class MediaPlugin(MediaBase):
|
|||||||
'media': MediaIndex,
|
'media': MediaIndex,
|
||||||
'media_search_title': TitleSearchIndex,
|
'media_search_title': TitleSearchIndex,
|
||||||
'media_status': MediaStatusIndex,
|
'media_status': MediaStatusIndex,
|
||||||
|
'media_tag': MediaTagIndex,
|
||||||
'media_by_type': MediaTypeIndex,
|
'media_by_type': MediaTypeIndex,
|
||||||
'media_title': TitleIndex,
|
'media_title': TitleIndex,
|
||||||
'media_startswith': StartsWithIndex,
|
'media_startswith': StartsWithIndex,
|
||||||
@@ -39,15 +43,15 @@ class MediaPlugin(MediaBase):
|
|||||||
'desc': 'List media',
|
'desc': 'List media',
|
||||||
'params': {
|
'params': {
|
||||||
'type': {'type': 'string', 'desc': 'Media type to filter on.'},
|
'type': {'type': 'string', 'desc': 'Media type to filter on.'},
|
||||||
'status': {'type': 'array or csv', 'desc': 'Filter movie by status. Example:"active,done"'},
|
'status': {'type': 'array or csv', 'desc': 'Filter media by status. Example:"active,done"'},
|
||||||
'release_status': {'type': 'array or csv', 'desc': 'Filter movie by status of its releases. Example:"snatched,available"'},
|
'release_status': {'type': 'array or csv', 'desc': 'Filter media by status of its releases. Example:"snatched,available"'},
|
||||||
'limit_offset': {'desc': 'Limit and offset the movie list. Examples: "50" or "50,30"'},
|
'limit_offset': {'desc': 'Limit and offset the media list. Examples: "50" or "50,30"'},
|
||||||
'starts_with': {'desc': 'Starts with these characters. Example: "a" returns all movies starting with the letter "a"'},
|
'starts_with': {'desc': 'Starts with these characters. Example: "a" returns all media starting with the letter "a"'},
|
||||||
'search': {'desc': 'Search movie title'},
|
'search': {'desc': 'Search media title'},
|
||||||
},
|
},
|
||||||
'return': {'type': 'object', 'example': """{
|
'return': {'type': 'object', 'example': """{
|
||||||
'success': True,
|
'success': True,
|
||||||
'empty': bool, any movies returned or not,
|
'empty': bool, any media returned or not,
|
||||||
'media': array, media found,
|
'media': array, media found,
|
||||||
}"""}
|
}"""}
|
||||||
})
|
})
|
||||||
@@ -73,6 +77,7 @@ class MediaPlugin(MediaBase):
|
|||||||
addEvent('app.load', self.addSingleListView, priority = 100)
|
addEvent('app.load', self.addSingleListView, priority = 100)
|
||||||
addEvent('app.load', self.addSingleCharView, priority = 100)
|
addEvent('app.load', self.addSingleCharView, priority = 100)
|
||||||
addEvent('app.load', self.addSingleDeleteView, priority = 100)
|
addEvent('app.load', self.addSingleDeleteView, priority = 100)
|
||||||
|
addEvent('app.load', self.cleanupFaults)
|
||||||
|
|
||||||
addEvent('media.get', self.get)
|
addEvent('media.get', self.get)
|
||||||
addEvent('media.with_status', self.withStatus)
|
addEvent('media.with_status', self.withStatus)
|
||||||
@@ -80,6 +85,20 @@ class MediaPlugin(MediaBase):
|
|||||||
addEvent('media.list', self.list)
|
addEvent('media.list', self.list)
|
||||||
addEvent('media.delete', self.delete)
|
addEvent('media.delete', self.delete)
|
||||||
addEvent('media.restatus', self.restatus)
|
addEvent('media.restatus', self.restatus)
|
||||||
|
addEvent('media.tag', self.tag)
|
||||||
|
addEvent('media.untag', self.unTag)
|
||||||
|
|
||||||
|
# Wrongly tagged media files
|
||||||
|
def cleanupFaults(self):
|
||||||
|
medias = fireEvent('media.with_status', 'ignored', single = True) or []
|
||||||
|
|
||||||
|
db = get_db()
|
||||||
|
for media in medias:
|
||||||
|
try:
|
||||||
|
media['status'] = 'done'
|
||||||
|
db.update(media)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
def refresh(self, id = '', **kwargs):
|
def refresh(self, id = '', **kwargs):
|
||||||
handlers = []
|
handlers = []
|
||||||
@@ -102,13 +121,12 @@ class MediaPlugin(MediaBase):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
media = get_db().get('id', media_id)
|
media = get_db().get('id', media_id)
|
||||||
event = '%s.update_info' % media.get('type')
|
event = '%s.update' % media.get('type')
|
||||||
|
|
||||||
def handler():
|
def handler():
|
||||||
fireEvent(event, media_id = media_id, on_complete = self.createOnComplete(media_id))
|
fireEvent(event, media_id = media_id, on_complete = self.createOnComplete(media_id))
|
||||||
|
|
||||||
if handler:
|
return handler
|
||||||
return handler
|
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Refresh handler for non existing media: %s', traceback.format_exc())
|
log.error('Refresh handler for non existing media: %s', traceback.format_exc())
|
||||||
@@ -120,25 +138,30 @@ class MediaPlugin(MediaBase):
|
|||||||
|
|
||||||
def get(self, media_id):
|
def get(self, media_id):
|
||||||
|
|
||||||
db = get_db()
|
try:
|
||||||
|
db = get_db()
|
||||||
|
|
||||||
imdb_id = getImdb(str(media_id))
|
imdb_id = getImdb(str(media_id))
|
||||||
|
|
||||||
media = None
|
if imdb_id:
|
||||||
if imdb_id:
|
media = db.get('media', 'imdb-%s' % imdb_id, with_doc = True)['doc']
|
||||||
media = db.get('media', 'imdb-%s' % imdb_id, with_doc = True)['doc']
|
else:
|
||||||
else:
|
media = db.get('id', media_id)
|
||||||
media = db.get('id', media_id)
|
|
||||||
|
|
||||||
if media:
|
if media:
|
||||||
|
|
||||||
# Attach category
|
# Attach category
|
||||||
try: media['category'] = db.get('id', media.get('category_id'))
|
try: media['category'] = db.get('id', media.get('category_id'))
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
media['releases'] = fireEvent('release.for_media', media['_id'], single = True)
|
media['releases'] = fireEvent('release.for_media', media['_id'], single = True)
|
||||||
|
|
||||||
return media
|
return media
|
||||||
|
|
||||||
|
except (RecordNotFound, RecordDeleted):
|
||||||
|
log.error('Media with id "%s" not found', media_id)
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
|
||||||
def getView(self, id = None, **kwargs):
|
def getView(self, id = None, **kwargs):
|
||||||
|
|
||||||
@@ -149,30 +172,45 @@ class MediaPlugin(MediaBase):
|
|||||||
'media': media,
|
'media': media,
|
||||||
}
|
}
|
||||||
|
|
||||||
def withStatus(self, status, with_doc = True):
|
def withStatus(self, status, types = None, with_doc = True):
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
||||||
|
if types and not isinstance(types, (list, tuple)):
|
||||||
|
types = [types]
|
||||||
|
|
||||||
status = list(status if isinstance(status, (list, tuple)) else [status])
|
status = list(status if isinstance(status, (list, tuple)) else [status])
|
||||||
|
|
||||||
for s in status:
|
for s in status:
|
||||||
for ms in db.get_many('media_status', s, with_doc = with_doc):
|
for ms in db.get_many('media_status', s):
|
||||||
yield ms['doc'] if with_doc else ms
|
if with_doc:
|
||||||
|
try:
|
||||||
|
doc = db.get('id', ms['_id'])
|
||||||
|
|
||||||
|
if types and doc.get('type') not in types:
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield doc
|
||||||
|
except (RecordDeleted, RecordNotFound):
|
||||||
|
log.debug('Record not found, skipping: %s', ms['_id'])
|
||||||
|
except (ValueError, EOFError):
|
||||||
|
fireEvent('database.delete_corrupted', ms.get('_id'), traceback_error = traceback.format_exc(0))
|
||||||
|
else:
|
||||||
|
yield ms
|
||||||
|
|
||||||
def withIdentifiers(self, identifiers, with_doc = False):
|
def withIdentifiers(self, identifiers, with_doc = False):
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
||||||
for x in identifiers:
|
for x in identifiers:
|
||||||
try:
|
try:
|
||||||
media = db.get('media', '%s-%s' % (x, identifiers[x]), with_doc = with_doc)
|
return db.get('media', '%s-%s' % (x, identifiers[x]), with_doc = with_doc)
|
||||||
return media
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
log.debug('No media found with identifiers: %s', identifiers)
|
log.debug('No media found with identifiers: %s', identifiers)
|
||||||
|
return False
|
||||||
|
|
||||||
def list(self, types = None, status = None, release_status = None, status_or = False, limit_offset = None, starts_with = None, search = None):
|
def list(self, types = None, status = None, release_status = None, status_or = False, limit_offset = None, with_tags = None, starts_with = None, search = None):
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
||||||
@@ -183,6 +221,8 @@ class MediaPlugin(MediaBase):
|
|||||||
release_status = [release_status]
|
release_status = [release_status]
|
||||||
if types and not isinstance(types, (list, tuple)):
|
if types and not isinstance(types, (list, tuple)):
|
||||||
types = [types]
|
types = [types]
|
||||||
|
if with_tags and not isinstance(with_tags, (list, tuple)):
|
||||||
|
with_tags = [with_tags]
|
||||||
|
|
||||||
# query media ids
|
# query media ids
|
||||||
if types:
|
if types:
|
||||||
@@ -209,11 +249,17 @@ class MediaPlugin(MediaBase):
|
|||||||
|
|
||||||
# Add search filters
|
# Add search filters
|
||||||
if starts_with:
|
if starts_with:
|
||||||
filter_by['starts_with'] = set()
|
|
||||||
starts_with = toUnicode(starts_with.lower())[0]
|
starts_with = toUnicode(starts_with.lower())[0]
|
||||||
starts_with = starts_with if starts_with in ascii_lowercase else '#'
|
starts_with = starts_with if starts_with in ascii_lowercase else '#'
|
||||||
filter_by['starts_with'] = [x['_id'] for x in db.get_many('media_startswith', starts_with)]
|
filter_by['starts_with'] = [x['_id'] for x in db.get_many('media_startswith', starts_with)]
|
||||||
|
|
||||||
|
# Add tag filter
|
||||||
|
if with_tags:
|
||||||
|
filter_by['with_tags'] = set()
|
||||||
|
for tag in with_tags:
|
||||||
|
for x in db.get_many('media_tag', tag):
|
||||||
|
filter_by['with_tags'].add(x['_id'])
|
||||||
|
|
||||||
# Filter with search query
|
# Filter with search query
|
||||||
if search:
|
if search:
|
||||||
filter_by['search'] = [x['_id'] for x in db.get_many('media_search_title', search)]
|
filter_by['search'] = [x['_id'] for x in db.get_many('media_search_title', search)]
|
||||||
@@ -249,6 +295,10 @@ class MediaPlugin(MediaBase):
|
|||||||
|
|
||||||
media = fireEvent('media.get', media_id, single = True)
|
media = fireEvent('media.get', media_id, single = True)
|
||||||
|
|
||||||
|
# Skip if no media has been found
|
||||||
|
if not media:
|
||||||
|
continue
|
||||||
|
|
||||||
# Merge releases with movie dict
|
# Merge releases with movie dict
|
||||||
medias.append(media)
|
medias.append(media)
|
||||||
|
|
||||||
@@ -266,6 +316,7 @@ class MediaPlugin(MediaBase):
|
|||||||
release_status = splitString(kwargs.get('release_status')),
|
release_status = splitString(kwargs.get('release_status')),
|
||||||
status_or = kwargs.get('status_or') is not None,
|
status_or = kwargs.get('status_or') is not None,
|
||||||
limit_offset = kwargs.get('limit_offset'),
|
limit_offset = kwargs.get('limit_offset'),
|
||||||
|
with_tags = splitString(kwargs.get('with_tags')),
|
||||||
starts_with = kwargs.get('starts_with'),
|
starts_with = kwargs.get('starts_with'),
|
||||||
search = kwargs.get('search')
|
search = kwargs.get('search')
|
||||||
)
|
)
|
||||||
@@ -280,9 +331,22 @@ class MediaPlugin(MediaBase):
|
|||||||
def addSingleListView(self):
|
def addSingleListView(self):
|
||||||
|
|
||||||
for media_type in fireEvent('media.types', merge = True):
|
for media_type in fireEvent('media.types', merge = True):
|
||||||
def tempList(*args, **kwargs):
|
tempList = lambda *args, **kwargs : self.listView(type = media_type, **kwargs)
|
||||||
return self.listView(types = media_type, **kwargs)
|
addApiView('%s.list' % media_type, tempList, docs = {
|
||||||
addApiView('%s.list' % media_type, tempList)
|
'desc': 'List media',
|
||||||
|
'params': {
|
||||||
|
'status': {'type': 'array or csv', 'desc': 'Filter ' + media_type + ' by status. Example:"active,done"'},
|
||||||
|
'release_status': {'type': 'array or csv', 'desc': 'Filter ' + media_type + ' by status of its releases. Example:"snatched,available"'},
|
||||||
|
'limit_offset': {'desc': 'Limit and offset the ' + media_type + ' list. Examples: "50" or "50,30"'},
|
||||||
|
'starts_with': {'desc': 'Starts with these characters. Example: "a" returns all ' + media_type + 's starting with the letter "a"'},
|
||||||
|
'search': {'desc': 'Search ' + media_type + ' title'},
|
||||||
|
},
|
||||||
|
'return': {'type': 'object', 'example': """{
|
||||||
|
'success': True,
|
||||||
|
'empty': bool, any """ + media_type + """s returned or not,
|
||||||
|
'media': array, media found,
|
||||||
|
}"""}
|
||||||
|
})
|
||||||
|
|
||||||
def availableChars(self, types = None, status = None, release_status = None):
|
def availableChars(self, types = None, status = None, release_status = None):
|
||||||
|
|
||||||
@@ -328,7 +392,7 @@ class MediaPlugin(MediaBase):
|
|||||||
if x['_id'] in media_ids:
|
if x['_id'] in media_ids:
|
||||||
chars.add(x['key'])
|
chars.add(x['key'])
|
||||||
|
|
||||||
if len(chars) == 25:
|
if len(chars) == 27:
|
||||||
break
|
break
|
||||||
|
|
||||||
return list(chars)
|
return list(chars)
|
||||||
@@ -349,8 +413,7 @@ class MediaPlugin(MediaBase):
|
|||||||
def addSingleCharView(self):
|
def addSingleCharView(self):
|
||||||
|
|
||||||
for media_type in fireEvent('media.types', merge = True):
|
for media_type in fireEvent('media.types', merge = True):
|
||||||
def tempChar(*args, **kwargs):
|
tempChar = lambda *args, **kwargs : self.charView(type = media_type, **kwargs)
|
||||||
return self.charView(types = media_type, **kwargs)
|
|
||||||
addApiView('%s.available_chars' % media_type, tempChar)
|
addApiView('%s.available_chars' % media_type, tempChar)
|
||||||
|
|
||||||
def delete(self, media_id, delete_from = None):
|
def delete(self, media_id, delete_from = None):
|
||||||
@@ -361,13 +424,18 @@ class MediaPlugin(MediaBase):
|
|||||||
media = db.get('id', media_id)
|
media = db.get('id', media_id)
|
||||||
if media:
|
if media:
|
||||||
deleted = False
|
deleted = False
|
||||||
|
|
||||||
|
media_releases = fireEvent('release.for_media', media['_id'], single = True)
|
||||||
|
|
||||||
if delete_from == 'all':
|
if delete_from == 'all':
|
||||||
|
# Delete connected releases
|
||||||
|
for release in media_releases:
|
||||||
|
db.delete(release)
|
||||||
|
|
||||||
db.delete(media)
|
db.delete(media)
|
||||||
deleted = True
|
deleted = True
|
||||||
else:
|
else:
|
||||||
|
|
||||||
media_releases = fireEvent('release.for_media', media['_id'], single = True)
|
|
||||||
|
|
||||||
total_releases = len(media_releases)
|
total_releases = len(media_releases)
|
||||||
total_deleted = 0
|
total_deleted = 0
|
||||||
new_media_status = None
|
new_media_status = None
|
||||||
@@ -379,16 +447,18 @@ class MediaPlugin(MediaBase):
|
|||||||
total_deleted += 1
|
total_deleted += 1
|
||||||
new_media_status = 'done'
|
new_media_status = 'done'
|
||||||
elif delete_from == 'manage':
|
elif delete_from == 'manage':
|
||||||
if release.get('status') == 'done':
|
if release.get('status') == 'done' or media.get('status') == 'done':
|
||||||
db.delete(release)
|
db.delete(release)
|
||||||
total_deleted += 1
|
total_deleted += 1
|
||||||
|
|
||||||
if (total_releases == total_deleted and media['status'] != 'active') or (delete_from == 'wanted' and media['status'] == 'active'):
|
if (total_releases == total_deleted) or (total_releases == 0 and not new_media_status) or (not new_media_status and delete_from == 'late'):
|
||||||
db.delete(media)
|
db.delete(media)
|
||||||
deleted = True
|
deleted = True
|
||||||
elif new_media_status:
|
elif new_media_status:
|
||||||
media['status'] = new_media_status
|
media['status'] = new_media_status
|
||||||
db.update(media)
|
db.update(media)
|
||||||
|
|
||||||
|
fireEvent('media.untag', media['_id'], 'recent', single = True)
|
||||||
else:
|
else:
|
||||||
fireEvent('media.restatus', media.get('_id'), single = True)
|
fireEvent('media.restatus', media.get('_id'), single = True)
|
||||||
|
|
||||||
@@ -412,11 +482,16 @@ class MediaPlugin(MediaBase):
|
|||||||
def addSingleDeleteView(self):
|
def addSingleDeleteView(self):
|
||||||
|
|
||||||
for media_type in fireEvent('media.types', merge = True):
|
for media_type in fireEvent('media.types', merge = True):
|
||||||
def tempDelete(*args, **kwargs):
|
tempDelete = lambda *args, **kwargs : self.deleteView(type = media_type, **kwargs)
|
||||||
return self.deleteView(types = media_type, *args, **kwargs)
|
addApiView('%s.delete' % media_type, tempDelete, docs = {
|
||||||
addApiView('%s.delete' % media_type, tempDelete)
|
'desc': 'Delete a ' + media_type + ' from the wanted list',
|
||||||
|
'params': {
|
||||||
|
'id': {'desc': 'Media ID(s) you want to delete.', 'type': 'int (comma separated)'},
|
||||||
|
'delete_from': {'desc': 'Delete ' + media_type + ' from this page', 'type': 'string: all (default), wanted, manage'},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
def restatus(self, media_id):
|
def restatus(self, media_id, tag_recent = True):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db = get_db()
|
db = get_db()
|
||||||
@@ -428,24 +503,77 @@ class MediaPlugin(MediaBase):
|
|||||||
if not m['profile_id']:
|
if not m['profile_id']:
|
||||||
m['status'] = 'done'
|
m['status'] = 'done'
|
||||||
else:
|
else:
|
||||||
move_to_wanted = True
|
m['status'] = 'active'
|
||||||
|
|
||||||
profile = db.get('id', m['profile_id'])
|
try:
|
||||||
media_releases = fireEvent('release.for_media', m['_id'], single = True)
|
profile = db.get('id', m['profile_id'])
|
||||||
|
media_releases = fireEvent('release.for_media', m['_id'], single = True)
|
||||||
|
done_releases = [release for release in media_releases if release.get('status') == 'done']
|
||||||
|
|
||||||
for q_identifier in profile['qualities']:
|
if done_releases:
|
||||||
index = profile['qualities'].index(q_identifier)
|
|
||||||
|
|
||||||
for release in media_releases:
|
# Check if we are finished with the media
|
||||||
if q_identifier == release['quality'] and (release.get('status') == 'done' and profile['finish'][index]):
|
for release in done_releases:
|
||||||
move_to_wanted = False
|
if fireEvent('quality.isfinish', {'identifier': release['quality'], 'is_3d': release.get('is_3d', False)}, profile, timedelta(seconds = time.time() - release['last_edit']).days, single = True):
|
||||||
|
m['status'] = 'done'
|
||||||
|
break
|
||||||
|
|
||||||
m['status'] = 'active' if move_to_wanted else 'done'
|
elif previous_status == 'done':
|
||||||
|
m['status'] = 'done'
|
||||||
|
|
||||||
|
except RecordNotFound:
|
||||||
|
log.debug('Failed restatus, keeping previous: %s', traceback.format_exc())
|
||||||
|
m['status'] = previous_status
|
||||||
|
|
||||||
# Only update when status has changed
|
# Only update when status has changed
|
||||||
if previous_status != m['status']:
|
if previous_status != m['status']:
|
||||||
db.update(m)
|
db.update(m)
|
||||||
|
|
||||||
return True
|
# Tag media as recent
|
||||||
|
if tag_recent:
|
||||||
|
self.tag(media_id, 'recent', update_edited = True)
|
||||||
|
|
||||||
|
return m['status']
|
||||||
except:
|
except:
|
||||||
log.error('Failed restatus: %s', traceback.format_exc())
|
log.error('Failed restatus: %s', traceback.format_exc())
|
||||||
|
|
||||||
|
def tag(self, media_id, tag, update_edited = False):
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = get_db()
|
||||||
|
m = db.get('id', media_id)
|
||||||
|
|
||||||
|
if update_edited:
|
||||||
|
m['last_edit'] = int(time.time())
|
||||||
|
|
||||||
|
tags = m.get('tags') or []
|
||||||
|
if tag not in tags:
|
||||||
|
tags.append(tag)
|
||||||
|
m['tags'] = tags
|
||||||
|
db.update(m)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
log.error('Failed tagging: %s', traceback.format_exc())
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def unTag(self, media_id, tag):
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = get_db()
|
||||||
|
m = db.get('id', media_id)
|
||||||
|
|
||||||
|
tags = m.get('tags') or []
|
||||||
|
if tag in tags:
|
||||||
|
new_tags = list(set(tags))
|
||||||
|
new_tags.remove(tag)
|
||||||
|
|
||||||
|
m['tags'] = new_tags
|
||||||
|
db.update(m)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
log.error('Failed untagging: %s', traceback.format_exc())
|
||||||
|
|
||||||
|
return False
|
||||||
|
|||||||
@@ -88,10 +88,14 @@ class Provider(Plugin):
|
|||||||
|
|
||||||
if data and len(data) > 0:
|
if data and len(data) > 0:
|
||||||
try:
|
try:
|
||||||
data = XMLTree.fromstring(ss(data))
|
data = XMLTree.fromstring(data)
|
||||||
return self.getElements(data, item_path)
|
return self.getElements(data, item_path)
|
||||||
except:
|
except:
|
||||||
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
try:
|
||||||
|
data = XMLTree.fromstring(ss(data))
|
||||||
|
return self.getElements(data, item_path)
|
||||||
|
except:
|
||||||
|
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -125,6 +129,9 @@ class YarrProvider(Provider):
|
|||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def buildUrl(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
|
|
||||||
# Check if we are still logged in every hour
|
# Check if we are still logged in every hour
|
||||||
@@ -177,7 +184,7 @@ class YarrProvider(Provider):
|
|||||||
try:
|
try:
|
||||||
return self.urlopen(url, headers = {'User-Agent': Env.getIdentifier()}, show_error = False)
|
return self.urlopen(url, headers = {'User-Agent': Env.getIdentifier()}, show_error = False)
|
||||||
except:
|
except:
|
||||||
log.error('Failed getting nzb from %s: %s', (self.getName(), traceback.format_exc()))
|
log.error('Failed getting release from %s: %s', (self.getName(), traceback.format_exc()))
|
||||||
|
|
||||||
return 'try_next'
|
return 'try_next'
|
||||||
|
|
||||||
@@ -200,7 +207,7 @@ class YarrProvider(Provider):
|
|||||||
self._search(media, quality, results)
|
self._search(media, quality, results)
|
||||||
# Search possible titles
|
# Search possible titles
|
||||||
else:
|
else:
|
||||||
media_title = fireEvent('library.query', media, single = True)
|
media_title = fireEvent('library.query', media, include_year = False, single = True)
|
||||||
|
|
||||||
for title in possibleTitles(media_title):
|
for title in possibleTitles(media_title):
|
||||||
self._searchOnTitle(title, media, quality, results)
|
self._searchOnTitle(title, media, quality, results)
|
||||||
@@ -298,7 +305,7 @@ class ResultList(list):
|
|||||||
old_score = new_result['score']
|
old_score = new_result['score']
|
||||||
new_result['score'] = int(old_score * is_correct_weight)
|
new_result['score'] = int(old_score * is_correct_weight)
|
||||||
|
|
||||||
log.info('Found correct release with weight %.02f, old_score(%d) now scaled to score(%d)', (
|
log.info2('Found correct release with weight %.02f, old_score(%d) now scaled to score(%d)', (
|
||||||
is_correct_weight,
|
is_correct_weight,
|
||||||
old_score,
|
old_score,
|
||||||
new_result['score']
|
new_result['score']
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import re
|
|||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt, simplifyString
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media._base.providers.nzb.base import NZBProvider
|
from couchpotato.core.media._base.providers.nzb.base import NZBProvider
|
||||||
|
|
||||||
@@ -50,8 +50,8 @@ class Base(NZBProvider):
|
|||||||
|
|
||||||
def extra_check(item):
|
def extra_check(item):
|
||||||
parts = re.search('available:.(?P<parts>\d+)./.(?P<total>\d+)', info.text)
|
parts = re.search('available:.(?P<parts>\d+)./.(?P<total>\d+)', info.text)
|
||||||
total = tryInt(parts.group('total'))
|
total = float(tryInt(parts.group('total')))
|
||||||
parts = tryInt(parts.group('parts'))
|
parts = float(tryInt(parts.group('parts')))
|
||||||
|
|
||||||
if (total / parts) < 1 and ((total / parts) < 0.95 or ((total / parts) >= 0.95 and not ('par2' in info.text.lower() or 'pa3' in info.text.lower()))):
|
if (total / parts) < 1 and ((total / parts) < 0.95 or ((total / parts) >= 0.95 and not ('par2' in info.text.lower() or 'pa3' in info.text.lower()))):
|
||||||
log.info2('Wrong: \'%s\', not complete: %s out of %s', (item['name'], parts, total))
|
log.info2('Wrong: \'%s\', not complete: %s out of %s', (item['name'], parts, total))
|
||||||
@@ -65,7 +65,7 @@ class Base(NZBProvider):
|
|||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
'id': nzb_id,
|
'id': nzb_id,
|
||||||
'name': title.text,
|
'name': simplifyString(title.text),
|
||||||
'age': tryInt(age),
|
'age': tryInt(age),
|
||||||
'size': self.parseSize(size_match.group('size')),
|
'size': self.parseSize(size_match.group('size')),
|
||||||
'url': self.urls['download'] % nzb_id,
|
'url': self.urls['download'] % nzb_id,
|
||||||
@@ -100,6 +100,7 @@ config = [{
|
|||||||
'name': 'binsearch',
|
'name': 'binsearch',
|
||||||
'description': 'Free provider, less accurate. See <a href="https://www.binsearch.info/">BinSearch</a>',
|
'description': 'Free provider, less accurate. See <a href="https://www.binsearch.info/">BinSearch</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAATklEQVQY02NwQAMMWAXOnz+PKvD//3/CAvM//z+fgiwAAs+RBab4PP//vwbFjPlAffgEChzOo2r5fBuIfRAC5w8D+QUofkkp8MHjOWQAAM3Sbogztg2wAAAAAElFTkSuQmCC',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from urllib2 import HTTPError
|
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import urllib2
|
import re
|
||||||
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
@@ -12,6 +11,7 @@ from couchpotato.core.media._base.providers.base import ResultList
|
|||||||
from couchpotato.core.media._base.providers.nzb.base import NZBProvider
|
from couchpotato.core.media._base.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
|
||||||
|
from requests import HTTPError
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -20,10 +20,11 @@ log = CPLog(__name__)
|
|||||||
class Base(NZBProvider, RSS):
|
class Base(NZBProvider, RSS):
|
||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'detail': 'details&id=%s',
|
'detail': 'details/%s',
|
||||||
'download': 't=get&id=%s'
|
'download': 't=get&id=%s'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
passwords_regex = 'password|wachtwoord'
|
||||||
limits_reached = {}
|
limits_reached = {}
|
||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
http_time_between_calls = 1 # Seconds
|
||||||
@@ -43,10 +44,8 @@ class Base(NZBProvider, RSS):
|
|||||||
|
|
||||||
def _searchOnHost(self, host, media, quality, results):
|
def _searchOnHost(self, host, media, quality, results):
|
||||||
|
|
||||||
query = self.buildUrl(media, host['api_key'])
|
query = self.buildUrl(media, host)
|
||||||
|
url = '%s%s' % (self.getUrl(host['host']), query)
|
||||||
url = '%s&%s' % (self.getUrl(host['host']), query)
|
|
||||||
|
|
||||||
nzbs = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
nzbs = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
|
|
||||||
for nzb in nzbs:
|
for nzb in nzbs:
|
||||||
@@ -79,6 +78,23 @@ class Base(NZBProvider, RSS):
|
|||||||
if spotter:
|
if spotter:
|
||||||
name_extra = spotter
|
name_extra = spotter
|
||||||
|
|
||||||
|
description = ''
|
||||||
|
if "@spot.net" in nzb_id:
|
||||||
|
try:
|
||||||
|
# Get details for extended description to retrieve passwords
|
||||||
|
query = self.buildDetailsUrl(nzb_id, host['api_key'])
|
||||||
|
url = '%s%s' % (self.getUrl(host['host']), query)
|
||||||
|
nzb_details = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})[0]
|
||||||
|
|
||||||
|
description = self.getTextElement(nzb_details, 'description')
|
||||||
|
|
||||||
|
# Extract a password from the description
|
||||||
|
password = re.search('(?:' + self.passwords_regex + ')(?: *)(?:\:|\=)(?: *)(.*?)\<br\>|\n|$', description, flags = re.I).group(1)
|
||||||
|
if password:
|
||||||
|
name += ' {{%s}}' % password.strip()
|
||||||
|
except:
|
||||||
|
log.debug('Error getting details of "%s": %s', (name, traceback.format_exc()))
|
||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
'id': nzb_id,
|
'id': nzb_id,
|
||||||
'provider_extra': urlparse(host['host']).hostname or host['host'],
|
'provider_extra': urlparse(host['host']).hostname or host['host'],
|
||||||
@@ -87,8 +103,9 @@ class Base(NZBProvider, RSS):
|
|||||||
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
'age': self.calculateAge(int(time.mktime(parse(date).timetuple()))),
|
||||||
'size': int(self.getElement(nzb, 'enclosure').attrib['length']) / 1024 / 1024,
|
'size': int(self.getElement(nzb, 'enclosure').attrib['length']) / 1024 / 1024,
|
||||||
'url': ((self.getUrl(host['host']) + self.urls['download']) % tryUrlencode(nzb_id)) + self.getApiExt(host),
|
'url': ((self.getUrl(host['host']) + self.urls['download']) % tryUrlencode(nzb_id)) + self.getApiExt(host),
|
||||||
'detail_url': '%sdetails/%s' % (cleanHost(host['host']), tryUrlencode(nzb_id)),
|
'detail_url': (cleanHost(host['host']) + self.urls['detail']) % tryUrlencode(nzb_id),
|
||||||
'content': self.getTextElement(nzb, 'description'),
|
'content': self.getTextElement(nzb, 'description'),
|
||||||
|
'description': description,
|
||||||
'score': host['extra_score'],
|
'score': host['extra_score'],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -166,24 +183,16 @@ class Base(NZBProvider, RSS):
|
|||||||
return 'try_next'
|
return 'try_next'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get final redirected url
|
data = self.urlopen(url, show_error = False)
|
||||||
log.debug('Checking %s for redirects.', url)
|
|
||||||
req = urllib2.Request(url)
|
|
||||||
req.add_header('User-Agent', self.user_agent)
|
|
||||||
res = urllib2.urlopen(req)
|
|
||||||
finalurl = res.geturl()
|
|
||||||
if finalurl != url:
|
|
||||||
log.debug('Redirect url used: %s', finalurl)
|
|
||||||
|
|
||||||
data = self.urlopen(finalurl, show_error = False)
|
|
||||||
self.limits_reached[host] = False
|
self.limits_reached[host] = False
|
||||||
return data
|
return data
|
||||||
except HTTPError as e:
|
except HTTPError as e:
|
||||||
if e.code == 503:
|
sc = e.response.status_code
|
||||||
|
if sc in [503, 429]:
|
||||||
response = e.read().lower()
|
response = e.read().lower()
|
||||||
if 'maximum api' in response or 'download limit' in response:
|
if sc == 429 or 'maximum api' in response or 'download limit' in response:
|
||||||
if not self.limits_reached.get(host):
|
if not self.limits_reached.get(host):
|
||||||
log.error('Limit reached for newznab provider: %s', host)
|
log.error('Limit reached / to many requests for newznab provider: %s', host)
|
||||||
self.limits_reached[host] = time.time()
|
self.limits_reached[host] = time.time()
|
||||||
return 'try_next'
|
return 'try_next'
|
||||||
|
|
||||||
@@ -191,6 +200,15 @@ class Base(NZBProvider, RSS):
|
|||||||
|
|
||||||
return 'try_next'
|
return 'try_next'
|
||||||
|
|
||||||
|
def buildDetailsUrl(self, nzb_id, api_key):
|
||||||
|
query = tryUrlencode({
|
||||||
|
't': 'details',
|
||||||
|
'id': nzb_id,
|
||||||
|
'apikey': api_key,
|
||||||
|
})
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
config = [{
|
config = [{
|
||||||
'name': 'newznab',
|
'name': 'newznab',
|
||||||
@@ -203,8 +221,9 @@ config = [{
|
|||||||
'description': 'Enable <a href="http://newznab.com/" target="_blank">NewzNab</a> such as <a href="https://nzb.su" target="_blank">NZB.su</a>, \
|
'description': 'Enable <a href="http://newznab.com/" target="_blank">NewzNab</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://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>, <a href="https://nzbgeek.info/" target="_blank">NZBGeek</a>, \
|
<a href="https://github.com/spotweb/spotweb" target="_blank">Spotweb</a>, <a href="https://nzbgeek.info/" target="_blank">NZBGeek</a>, \
|
||||||
<a href="https://smackdownonyou.com" target="_blank">SmackDown</a>, <a href="https://www.nzbfinder.ws" target="_blank">NZBFinder</a>',
|
<a href="https://www.nzbfinder.ws" target="_blank">NZBFinder</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAACVBMVEVjhwD///86aRovd/sBAAAAMklEQVQI12NgAIPQUCCRmQkjssDEShiRuRIqwZqZGcDAGBrqANUhGgIkWAOABKMDxCAA24UK50b26SAAAAAASUVORK5CYII=',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
@@ -213,30 +232,30 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'use',
|
'name': 'use',
|
||||||
'default': '0,0,0,0,0,0'
|
'default': '0,0,0,0,0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'host',
|
'name': 'host',
|
||||||
'default': 'api.nzb.su,dognzb.cr,nzbs.org,https://index.nzbgeek.info, https://smackdownonyou.com, https://www.nzbfinder.ws',
|
'default': 'api.nzb.su,api.dognzb.cr,nzbs.org,https://api.nzbgeek.info,https://www.nzbfinder.ws',
|
||||||
'description': 'The hostname of your newznab provider',
|
'description': 'The hostname of your newznab provider',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'extra_score',
|
'name': 'extra_score',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'label': 'Extra Score',
|
'label': 'Extra Score',
|
||||||
'default': '0,0,0,0,0,0',
|
'default': '0,0,0,0,0',
|
||||||
'description': 'Starting score for each release found via this provider.',
|
'description': 'Starting score for each release found via this provider.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'custom_tag',
|
'name': 'custom_tag',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'label': 'Custom tag',
|
'label': 'Custom tag',
|
||||||
'default': ',,,,,',
|
'default': ',,,,',
|
||||||
'description': 'Add custom tags, for example add rls=1 to get only scene releases from nzbs.org',
|
'description': 'Add custom tags, for example add rls=1 to get only scene releases from nzbs.org',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'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',
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ config = [{
|
|||||||
'name': 'NZBClub',
|
'name': 'NZBClub',
|
||||||
'description': 'Free provider, less accurate. See <a href="https://www.nzbclub.com/">NZBClub</a>',
|
'description': 'Free provider, less accurate. See <a href="https://www.nzbclub.com/">NZBClub</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACEUlEQVQ4y3VSMWgUQRR9/8/s7OzeJSdnTsVGghLEYBNQjBpQiRBFhIB2EcHG1kbs0murhZAmVocExEZQ0c7CxkLINYcJJpoYj9wZcnu72fF21uJSXMzuhyne58/j/fcf4b+KokgBIOSU53lxP5b9oNVqDT36dH+5UjoiKvIwPFEEgWBshGZ3E7/NOupL9fMjx0e+ZhKsrq+c/FPZKJi0w4FsQXMBDEJsd7BNW9h2tuyP9vfTALIJkMIu1hYRtINM+dpzcWc0sbkreK4fUEogyraAmKGF3+7vcT/wtR9QwkCabSAzQQuvk0uglAo5YaQ5DASGYjfMXcHVOqKu6NmR7iehlKAdHWUqWPv1c3i+9uwVdRlEBGaGEAJCCrDo9ShhvF6qPq8tL57bp+DbRn2sHtUuCY9YphLMu5921VhrwYJ5tbt0tt6sjQP4vEfB2Ikz7/ytwbeR6ljHkXCUA6UcOLtPOg4MYhtH8ZcLw5er+xQMDAwEURRNl96X596Y6oxFwsw9fmtTOAr2Ik19nL365FZpsLSdnQPPM8aYewc+lDcX4rkHqbQMAGTJXulOLzycmr1bKBTi3DOGYagajcahiaOT89fbM0/dxEsUu3aidfPljWO3HzebzYNBELi5Z5RSJlrrHd/3w8lT114MrVTWOn875fHRiYVisRhorWMpZXdvNnLKGCOstb0AMlulVJI19w/+nceU4D0aCwAAAABJRU5ErkJggg==',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
import re
|
|
||||||
import time
|
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
|
||||||
from couchpotato.core.helpers.rss import RSS
|
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
|
||||||
from couchpotato.core.logger import CPLog
|
|
||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.media._base.providers.nzb.base import NZBProvider
|
|
||||||
from dateutil.parser import parse
|
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Base(NZBProvider, RSS):
|
|
||||||
|
|
||||||
urls = {
|
|
||||||
'download': 'https://www.nzbindex.com/download/',
|
|
||||||
'search': 'https://www.nzbindex.com/rss/?%s',
|
|
||||||
}
|
|
||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
|
||||||
|
|
||||||
def _search(self, media, quality, results):
|
|
||||||
|
|
||||||
nzbs = self.getRSSData(self.urls['search'] % self.buildUrl(media, quality))
|
|
||||||
|
|
||||||
for nzb in nzbs:
|
|
||||||
|
|
||||||
enclosure = self.getElement(nzb, 'enclosure').attrib
|
|
||||||
nzbindex_id = int(self.getTextElement(nzb, "link").split('/')[4])
|
|
||||||
|
|
||||||
title = self.getTextElement(nzb, "title")
|
|
||||||
|
|
||||||
match = fireEvent('matcher.parse', title, parser='usenet', single = True)
|
|
||||||
if not match.chains:
|
|
||||||
log.info('Unable to parse release with title "%s"', title)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# TODO should we consider other lower-weight chains here?
|
|
||||||
info = fireEvent('matcher.flatten_info', match.chains[0].info, single = True)
|
|
||||||
|
|
||||||
release_name = fireEvent('matcher.construct_from_raw', info.get('release_name'), single = True)
|
|
||||||
|
|
||||||
file_name = info.get('detail', {}).get('file_name')
|
|
||||||
file_name = file_name[0] if file_name else None
|
|
||||||
|
|
||||||
title = release_name or file_name
|
|
||||||
|
|
||||||
# Strip extension from parsed title (if one exists)
|
|
||||||
ext_pos = title.rfind('.')
|
|
||||||
|
|
||||||
# Assume extension if smaller than 4 characters
|
|
||||||
# TODO this should probably be done a better way
|
|
||||||
if len(title[ext_pos + 1:]) <= 4:
|
|
||||||
title = title[:ext_pos]
|
|
||||||
|
|
||||||
if not title:
|
|
||||||
log.info('Unable to find release name from match')
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
description = self.getTextElement(nzb, "description")
|
|
||||||
except:
|
|
||||||
description = ''
|
|
||||||
|
|
||||||
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': 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):
|
|
||||||
try:
|
|
||||||
if '/nfo/' in item['description'].lower():
|
|
||||||
nfo_url = re.search('href=\"(?P<nfo>.+)\" ', item['description']).group('nfo')
|
|
||||||
full_description = self.getCache('nzbindex.%s' % item['id'], url = nfo_url, cache_timeout = 25920000)
|
|
||||||
html = BeautifulSoup(full_description)
|
|
||||||
item['description'] = toUnicode(html.find('pre', attrs = {'id': 'nfo0'}).text)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
config = [{
|
|
||||||
'name': 'nzbindex',
|
|
||||||
'groups': [
|
|
||||||
{
|
|
||||||
'tab': 'searcher',
|
|
||||||
'list': 'nzb_providers',
|
|
||||||
'name': 'nzbindex',
|
|
||||||
'description': 'Free provider, less accurate. See <a href="https://www.nzbindex.com/">NZBIndex</a>',
|
|
||||||
'wizard': True,
|
|
||||||
'options': [
|
|
||||||
{
|
|
||||||
'name': 'enabled',
|
|
||||||
'type': 'enabler',
|
|
||||||
'default': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'extra_score',
|
|
||||||
'advanced': True,
|
|
||||||
'label': 'Extra Score',
|
|
||||||
'type': 'int',
|
|
||||||
'default': 0,
|
|
||||||
'description': 'Starting score for each release found via this provider.',
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}]
|
|
||||||
@@ -74,6 +74,7 @@ config = [{
|
|||||||
'name': 'OMGWTFNZBs',
|
'name': 'OMGWTFNZBs',
|
||||||
'description': 'See <a href="http://omgwtfnzbs.org/">OMGWTFNZBs</a>',
|
'description': 'See <a href="http://omgwtfnzbs.org/">OMGWTFNZBs</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAIAAADAAbR1AAADbElEQVR4AZ2UW0ybZRiAy/OvdHaLYvB0YTRIFi7GkM44zRLmIfNixkWdiRMyYoxRE8/TC7MYvXCGEBmr3mxLwVMwY0wYA7e6Wso4lB6h/U9taSlMGIfBXLYlJMyo0S///2dJI5lxN8/F2/f9nu9737e/jYmXr6KTbN9BGG9HE/NotQ76UWziNzrXFiETk/5ARUNH+7+0kW7fSgTl0VKGOLZzidOkmuuIo7q2oTArNLPIzhdIkqXkerFOm2CaD/5bcKrjIL2c3fkhPxOq93Kcb91v46fV9TQKF4TgV/TbUsQtzfCaK6jMOd5DJrguSIIhexmqqVxN0FXbRR8/ND/LYTTj6J7nl2gnL47OkDW4KJhnQHCa6JpKVNJGA3OC58nwBJoZ//ebbIyKpBxjrr0o1q1FMRkrKXZnHWF85VvxMrJxibwhGyd0f5bLnKzqJs1k0Sfo+EU8hdAUvkbcwKEgs2D0OiV4jmmD1zb+Tp6er0JMMvDxPo5xev9zTBF683NS+N56n1YiB95B5crr93KRuKhKI0tb0Kw2mgLLqTjLEWO8424i9IvURaYeOckwf3+/yCC9e3bQQ/MuD+Monk0k+XFXMUfx7z5EEP+XlXi5tLlMxH8zLppw7idJrugcus30kC86gc7UrQqjLIukM8zWHOACeU+TiMxXN6ExVOkgz4lvPEzice1GIVhxhG4CrZvpl6TH55giKWqXGLy9hZh5aUtgDSew/msSyCKpl+DDNfxJc8NBIsxUxUnz14O/oONu+IIIvso9TLBQ1SY5rUhuSzUhAqJ2mRXBLDOCeUtgUZXsaObT8BffhUJPqWgiV+3zKKzYH0ClvTRLhD77HIqVkyh5jThnivehoG+qJctIRSPn6bxvO4FCgTl9c1DmbpjLajbQFE8aW5SU3rg+zOPGUjTUF9NFpLEbH2c/KmGYlY69/GQJVtGMSUcEp9eCbB1nctbxHTLRdTUkGDf+B02uGWRG3OvpJ/zSMwzif+oxVBID3cQKBavLCiPmB2PM2UuSCUPgrX4VDb97AwEG67bh4+KTOlncvu3M31BwA5rLHbCfEjwkNDky9e/SSbSxnD46Pg0RJtpXRvhmBSZHpRjWtKwFybjuQeXaKxto4WjLZZZvVmC17pZLJFkwxm5++PS2Mrwc7nyIMYZe/IzoP5d6QgEybqTXAAAAAElFTkSuQmCC',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class Base(TorrentProvider):
|
|||||||
'name': re.sub('[^A-Za-z0-9\-_ \(\).]+', '', '%s (%s) %s' % (name, year, torrent_desc)),
|
'name': re.sub('[^A-Za-z0-9\-_ \(\).]+', '', '%s (%s) %s' % (name, year, torrent_desc)),
|
||||||
'url': self.urls['download'] % (torrent_id, authkey, self.conf('passkey')),
|
'url': self.urls['download'] % (torrent_id, authkey, self.conf('passkey')),
|
||||||
'detail_url': self.urls['detail'] % torrent_id,
|
'detail_url': self.urls['detail'] % torrent_id,
|
||||||
'size': self.parseSize(entry.find('size').get_text()),
|
'size': tryInt(entry.find('size').get_text()) / 1048576,
|
||||||
'seeders': tryInt(entry.find('seeders').get_text()),
|
'seeders': tryInt(entry.find('seeders').get_text()),
|
||||||
'leechers': tryInt(entry.find('leechers').get_text()),
|
'leechers': tryInt(entry.find('leechers').get_text()),
|
||||||
'score': torrentscore
|
'score': torrentscore
|
||||||
@@ -78,8 +78,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'Awesome-HD',
|
'name': 'Awesome-HD',
|
||||||
'description': 'See <a href="https://awesome-hd.net">AHD</a>',
|
'description': '<a href="https://awesome-hd.net">AHD</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC+UlEQVR4AV1SO0y6dxQ9H4g8CoIoohZ5NA0aR2UgkYpNB5uocTSaLlrDblMH09Gt8d90r3YpJkanxjA4GGkbO7RNxSABq8jDGnkpD+UD5NV7Bxvbk9wvv+/3uPece66A/yEWi42FQqHVfD7/cbPZtIEglUpjOp3uZHR0dBvAn3gDIRqNgjE4OKj0+Xzf3NzcfD4wMCCjf5TLZbTbbajVatzf3+Pu7q5uNpt35ufnvwBQAScQRREEldfr9RWLxan+/n5YrVa+jFarhVfQQyQSCU4EhULhX15engEgSrjC0dHRVqlUmjQYDBgaGgKtuTqz4mTgIoVCASaTCX19fajVapOHh4dbFJBks9mxcDi8qtFoJEajkfVyJWi1WkxMTMDhcIAT8x6D7/Dd6+vr1fHx8TGp2+3+iqo5+YCzBwIBToK5ubl/mQwPDyMSibAs2Gw2UHNRrValz8/PDUk8Hv9EqVRCr9fj4uICTNflcqFer+Pg4AB7e3uoVCq8x9Rxfn6O7u5uqFQq8FspZXxHTekggByA3W4Hr9PpNDeRL3I1cMhkMrBrnZ2dyGQyvNYIs7OzVbJNPjIyAraLwYdcjR8wXl5eIJfLwRIFQQDLYkm3t7c1CdGPPT4+cpOImp4PODMeaK+n10As2jBbrHifHOjS6qAguVFimkqlwAMmIQnHV1dX4NDQhVwuhyZTV6pgIktzDzkkk0lEwhEEzs7ASQr5Ai4vL1nuccfCwsLO/v6+p9FoyJhF6ekJro/cPCzIZLNQa7rQoK77/SdgWWpKkCaJ5EB9aWnpe6nH40nRMBnJV4f5gw+FX3/5GX/8/htXRZdOzzqhJWn6nl6YbTZqqhrhULD16fT0d8FgcFtYW1vD5uamfGVl5cd4IjldKhZACdkJvKfWUANrxEaJV4hiGVaL1b+7653hXzwRZQr2X76xsfG1xWIRaZzbNPv/CdrjEL9cX/+WXFBSgEPgzxuwG3Yans9OT0+naBZMIJDNfzudzp8WFxd/APAX3uAf9WOTxOPLdosAAAAASUVORK5CYII=',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ class TorrentProvider(YarrProvider):
|
|||||||
|
|
||||||
prop_name = 'proxy.%s' % proxy
|
prop_name = 'proxy.%s' % proxy
|
||||||
last_check = float(Env.prop(prop_name, default = 0))
|
last_check = float(Env.prop(prop_name, default = 0))
|
||||||
if last_check > time.time() - 1209600:
|
|
||||||
|
if last_check > time.time() - 86400:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
data = ''
|
data = ''
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class Base(TorrentProvider):
|
|||||||
|
|
||||||
def _search(self, media, quality, results):
|
def _search(self, media, quality, results):
|
||||||
|
|
||||||
query = self.buildUrl(media)
|
query = self.buildUrl(media, quality)
|
||||||
|
|
||||||
url = "%s&%s" % (self.urls['search'], query)
|
url = "%s&%s" % (self.urls['search'], query)
|
||||||
|
|
||||||
@@ -93,8 +93,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'BiT-HDTV',
|
'name': 'BiT-HDTV',
|
||||||
'description': 'See <a href="http://bit-hdtv.com">BiT-HDTV</a>',
|
'description': '<a href="http://bit-hdtv.com">BiT-HDTV</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABMklEQVR4AZ3Qu0ojcQCF8W9MJcQbJNgEEQUbQVIqWgnaWfkIvoCgggixEAmIhRtY2GV3w7KwU61B0EYIxmiw0YCik84ipaCuc0nmP5dcjIUgOjqDvxf4OAdf9mnMLcUJyPyGSCP+YRdC+Kp8iagJKhuS+InYRhTGgDbeV2uEMand4ZRxizjXHQEimxhraAnUr73BNqQxMiNeV2SwcjTLEVtb4Zl10mXutvOWm2otw5Sxz6TGTbdd6ncuYvVLXAXrvM+ruyBpy1S3JLGDfUQ1O6jn5vTsrJXvqSt4UNfj6vxTRPxBHER5QeSirhLGk/5rWN+ffB1XZuxjnDy1q87m7TS+xOGA+Iv4gfkbaw+nOMXHDHnITGEk0VfRFnn4Po4vNYm6RGukmggR0L08+l+e4HMeASo/i6AJUjLgAAAAAElFTkSuQmCC',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup, SoupStrainer
|
||||||
from couchpotato.core.helpers.encoding import simplifyString, tryUrlencode
|
|
||||||
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
|
||||||
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
|
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
|
||||||
@@ -16,25 +15,23 @@ class Base(TorrentProvider):
|
|||||||
'test': 'https://www.bitsoup.me/',
|
'test': 'https://www.bitsoup.me/',
|
||||||
'login': 'https://www.bitsoup.me/takelogin.php',
|
'login': 'https://www.bitsoup.me/takelogin.php',
|
||||||
'login_check': 'https://www.bitsoup.me/my.php',
|
'login_check': 'https://www.bitsoup.me/my.php',
|
||||||
'search': 'https://www.bitsoup.me/browse.php?',
|
'search': 'https://www.bitsoup.me/browse.php?%s',
|
||||||
'baseurl': 'https://www.bitsoup.me/%s',
|
'baseurl': 'https://www.bitsoup.me/%s',
|
||||||
}
|
}
|
||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
http_time_between_calls = 1 # Seconds
|
||||||
|
only_tables_tags = SoupStrainer('table')
|
||||||
|
|
||||||
|
torrent_name_cell = 1
|
||||||
|
torrent_download_cell = 2
|
||||||
|
|
||||||
def _searchOnTitle(self, title, movie, quality, results):
|
def _searchOnTitle(self, title, movie, quality, results):
|
||||||
|
|
||||||
q = '"%s" %s' % (simplifyString(title), movie['info']['year'])
|
url = self.urls['search'] % self.buildUrl(title, movie, quality)
|
||||||
arguments = tryUrlencode({
|
|
||||||
'search': q,
|
|
||||||
})
|
|
||||||
url = "%s&%s" % (self.urls['search'], arguments)
|
|
||||||
|
|
||||||
url = self.urls['search'] % self.buildUrl(movie, quality)
|
|
||||||
data = self.getHTMLData(url)
|
data = self.getHTMLData(url)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
html = BeautifulSoup(data)
|
html = BeautifulSoup(data, 'html.parser', parse_only = self.only_tables_tags)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result_table = html.find('table', attrs = {'class': 'koptekst'})
|
result_table = html.find('table', attrs = {'class': 'koptekst'})
|
||||||
@@ -46,8 +43,8 @@ class Base(TorrentProvider):
|
|||||||
|
|
||||||
all_cells = result.find_all('td')
|
all_cells = result.find_all('td')
|
||||||
|
|
||||||
torrent = all_cells[1].find('a')
|
torrent = all_cells[self.torrent_name_cell].find('a')
|
||||||
download = all_cells[3].find('a')
|
download = all_cells[self.torrent_download_cell].find('a')
|
||||||
|
|
||||||
torrent_id = torrent['href']
|
torrent_id = torrent['href']
|
||||||
torrent_id = torrent_id.replace('details.php?id=', '')
|
torrent_id = torrent_id.replace('details.php?id=', '')
|
||||||
@@ -55,9 +52,9 @@ class Base(TorrentProvider):
|
|||||||
|
|
||||||
torrent_name = torrent.getText()
|
torrent_name = torrent.getText()
|
||||||
|
|
||||||
torrent_size = self.parseSize(all_cells[7].getText())
|
torrent_size = self.parseSize(all_cells[8].getText())
|
||||||
torrent_seeders = tryInt(all_cells[9].getText())
|
torrent_seeders = tryInt(all_cells[10].getText())
|
||||||
torrent_leechers = tryInt(all_cells[10].getText())
|
torrent_leechers = tryInt(all_cells[11].getText())
|
||||||
torrent_url = self.urls['baseurl'] % download['href']
|
torrent_url = self.urls['baseurl'] % download['href']
|
||||||
torrent_detail_url = self.urls['baseurl'] % torrent['href']
|
torrent_detail_url = self.urls['baseurl'] % torrent['href']
|
||||||
|
|
||||||
@@ -94,8 +91,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'Bitsoup',
|
'name': 'Bitsoup',
|
||||||
'description': 'See <a href="https://bitsoup.me">Bitsoup</a>',
|
'description': '<a href="https://bitsoup.me">Bitsoup</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAB8ElEQVR4AbWSS2sTURiGz3euk0mswaE37HhNhIrajQheFgF3rgR/lAt/gOBCXNZlo6AbqfUWRVCxi04wqUnTRibpJLaJzdzOOZ6WUumyC5/VHOb9eN/FA91uFx0FjI4IPfgiGLTWH73tn348GKmN7ijD0d2b41fO5qJEaX24AWNIUrVQCTTJ3Llx6vbV6Vtzk7Gi9+ebi996guFDDYAQAVj4FExP5qdOZB49W62t/zH3hECcwsPnbWeMXz6Xi2K1f0ApeK3hMCHHbP5gvvoriBgFAAQJEAxhjJ4u+YWTNsVI6b1JgtPWZkoIefKy4fcii2OTw2BABs7wj3bYDlLL4rvjGWOdTser1j5Xf7c3Q/MbHQYApxItvnm31mhQQ71eX2vUB76/vsWB2hg0QuogrMwLIG8P3InM2/eVGXeDViqVwWB79vRU2lgJYmdHcgXCTAXQFJTN5HguvDCR2Hxsxe8EvT54nlcul5vNpqDIEgwRQanAhAAABgRIyiQcjpIkkTOuWyqVoN/vSylX67XXH74uV1vHRUyxxFqbLBCSmBpiXSq6xcL5QrGYzWZ3XQIAwdlOJB+/aL764ucdmncYs0WsCI7kvTnn+qyDMEnTVCn1Tz5KsBFg6fvWcmsUAcnYNC/g2hnromvvqbHvxv+39S+MX+bWkFXwAgAAAABJRU5ErkJggg==',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -71,7 +71,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'HDBits',
|
'name': 'HDBits',
|
||||||
'description': 'See <a href="http://hdbits.org">HDBits</a>',
|
'wizard': True,
|
||||||
|
'description': '<a href="http://hdbits.org">HDBits</a>',
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABi0lEQVR4AZWSzUsbQRjGdyabTcvSNPTSHlpQQeMHJApC8CJRvHgQQU969+LJP8G7f4N3DwpeFRQvRr0EKaUl0ATSpkigUNFsMl/r9NmZLCEHA/nNO5PfvMPDm0DI6fV3ZxiolEICe1oZCBVCCmBPKwOh2ErKBHGE4KYEXBpSLkUlqO4LcM7f+6nVhRnOhSkOz/hexk+tL+YL0yPF2YmN4tynD++4gTLGkNNac9YFLoREBR1+cnF3dFY6v/m6PD+FaXiNJtgA4xYbABxiGrz6+6HWaI5/+Qh37YS0/3Znc8UxwNGBIIBX22z+/ZdJ+4wzyjpR4PEpODg8tgUXBv2iWUzSpa12B0IR6n6lvt8Aek2lZHb084+fdRNgrwY8z81PjhVy2d2ttUrtV/lbBa+JXGEpDMPnoF2tN1QYRqVUtf6nFbThb7wk7le395elcqhASLb39okDiHY00VCtCTEHwSiH4AI0lkOiT1dwMeSfT3SRxiQWNO7Zwj1egkoVIQFMKvSiC3bcjXq9Jf8DcDIRT3hh10kAAAAASUVORK5CYII=',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import traceback
|
|||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt, splitString
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
|
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ class Base(TorrentProvider):
|
|||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'download': 'https://www.ilovetorrents.me/%s',
|
'download': 'https://www.ilovetorrents.me/%s',
|
||||||
'detail': 'https//www.ilovetorrents.me/%s',
|
'detail': 'https://www.ilovetorrents.me/%s',
|
||||||
'search': 'https://www.ilovetorrents.me/browse.php?search=%s&page=%s&cat=%s',
|
'search': 'https://www.ilovetorrents.me/browse.php?search=%s&page=%s&cat=%s',
|
||||||
'test': 'https://www.ilovetorrents.me/',
|
'test': 'https://www.ilovetorrents.me/',
|
||||||
'login': 'https://www.ilovetorrents.me/takelogin.php',
|
'login': 'https://www.ilovetorrents.me/takelogin.php',
|
||||||
@@ -47,17 +47,24 @@ class Base(TorrentProvider):
|
|||||||
data = self.getHTMLData(search_url)
|
data = self.getHTMLData(search_url)
|
||||||
if data:
|
if data:
|
||||||
try:
|
try:
|
||||||
soup = BeautifulSoup(data)
|
|
||||||
|
|
||||||
results_table = soup.find('table', attrs = {'class': 'koptekst'})
|
results_table = None
|
||||||
|
|
||||||
|
data_split = splitString(data, '<table')
|
||||||
|
soup = None
|
||||||
|
for x in data_split:
|
||||||
|
soup = BeautifulSoup(x)
|
||||||
|
results_table = soup.find('table', attrs = {'class': 'koptekst'})
|
||||||
|
if results_table:
|
||||||
|
break
|
||||||
|
|
||||||
if not results_table:
|
if not results_table:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pagelinks = soup.findAll(href = re.compile('page'))
|
pagelinks = soup.findAll(href = re.compile('page'))
|
||||||
pageNumbers = [int(re.search('page=(?P<pageNumber>.+'')', i['href']).group('pageNumber')) for i in pagelinks]
|
page_numbers = [int(re.search('page=(?P<page_number>.+'')', i['href']).group('page_number')) for i in pagelinks]
|
||||||
total_pages = max(pageNumbers)
|
total_pages = max(page_numbers)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -139,6 +146,7 @@ config = [{
|
|||||||
'name': 'ILoveTorrents',
|
'name': 'ILoveTorrents',
|
||||||
'description': 'Where the Love of Torrents is Born',
|
'description': 'Where the Love of Torrents is Born',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACPUlEQVR4AYWM0U9SbxjH3+v266I/oNvWZTfd2J1d0ZqbZEFwWrUImOKs4YwtumFKZvvlJJADR2TCQQlMPKg5NmpREgaekAPnBATKgmK1LqQlx6awHnZWF1Tr2Xfvvs+7z+dB0mlO7StpAh+M4S/2jbo3w8+xvJvlnSneEt+10zwer5ujNUOoChjALWFw5XOwdCAk/P57cGvPl+Oht0W7VJHN5NC1uW1BON4hGjXbwpVWMZhsy9v7sEIXAsDNYBXgdkEoIKyWD2CF8ut/aOXTZc/fBSgLWw1BgA4BDHOV0GkT90cBQpXahU5TFomsb38XhJC5/Tbh1P8c6rJlBeGfAeyMhUFwNVcs9lxV9Ot0dwmyd+mrNvRtbJ2fSPC6Z3Vsvub2z3sDFACAAYzk0+kUyxEkyfN7PopqNBro55A+P6yPKIrL5zF1HwjdeBJJCObIsZO79bo3sHhWhglo5WMV3mazuVPb4fLvSL8/FAkB1hK6rXQPwYhMyROK8VK5LAiH/jsMt0HQjxiN4/ePdoilllcqDyt3Mkg8mRBNbIhMb8RERkowQA/p76g0/UDDdCoNmDminM0qSK5vlpE5kugCHhNPxntwWmJPYTMZtYcFR6ABHQsVRlYLukVORaaULvqKI46keFSCv77kSPS6kxrPptLNDHgz16fWBtyxe6v5h08LUy+KI8ushqTPWWIX8Sg6b45IrGtyW6zXFb/hpQf9m3oqfWuB0fpSw0uZ4WB69En69uOk2rmO2V52PXj+A/mI4ESKpb2HAAAAAElFTkSuQmCC',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -120,8 +120,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'IPTorrents',
|
'name': 'IPTorrents',
|
||||||
'description': 'See <a href="http://www.iptorrents.com">IPTorrents</a>',
|
'description': '<a href="http://www.iptorrents.com">IPTorrents</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABRklEQVR42qWQO0vDUBiG8zeKY3EqQUtNO7g0J6ZJ1+ifKIIFQXAqDYKCyaaYxM3udrZLHdRFhXrZ6liCW6mubfk874EESgqaeOCF7/Y8hEh41aq6yZi2nyZgBGya9XKtZs4No05pAkZV2YbEmyMMsoSxLQeC46wCTdPPY4HruPQyGIhF97qLWsS78Miydn4XdK46NJ9OsQAYBzMIMf8MQ9wtCnTdWCaIDx/u7uljOIQEe0hiIWPamSTLay3+RxOCSPI9+RJAo7Er9r2bnqjBFAqyK+VyK4f5/Cr5ni8OFKVCz49PFI5GdNvvU7ttE1M1zMU+8AMqFksEhrMnQsBDzqmDAwzx2ehRLwT7yyCI+vSC99c3mozH1NxrJgWWtR1BOECfEJSVCm6WCzJGCA7+IWhBsM4zywDPwEp4vCjx2DzBH2ODAfsDb33Ps6dQwJgAAAAASUVORK5CYII=',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -32,8 +32,11 @@ class Base(TorrentMagnetProvider):
|
|||||||
proxy_list = [
|
proxy_list = [
|
||||||
'https://kickass.to',
|
'https://kickass.to',
|
||||||
'http://kickass.pw',
|
'http://kickass.pw',
|
||||||
'http://www.kickassunblock.info',
|
'http://kickassto.come.in',
|
||||||
'http://www.kickassproxy.info',
|
'http://katproxy.ws',
|
||||||
|
'http://kickass.bitproxy.eu',
|
||||||
|
'http://katph.eu',
|
||||||
|
'http://kickassto.come.in',
|
||||||
]
|
]
|
||||||
|
|
||||||
def _search(self, media, quality, results):
|
def _search(self, media, quality, results):
|
||||||
@@ -65,12 +68,13 @@ class Base(TorrentMagnetProvider):
|
|||||||
if column_name:
|
if column_name:
|
||||||
|
|
||||||
if column_name == 'name':
|
if column_name == 'name':
|
||||||
link = td.find('div', {'class': 'torrentname'}).find_all('a')[1]
|
link = td.find('div', {'class': 'torrentname'}).find_all('a')[2]
|
||||||
new['id'] = temp.get('id')[-8:]
|
new['id'] = temp.get('id')[-7:]
|
||||||
new['name'] = link.text
|
new['name'] = link.text
|
||||||
new['url'] = td.find('a', 'imagnet')['href']
|
new['url'] = td.find('a', 'imagnet')['href']
|
||||||
new['detail_url'] = self.urls['detail'] % (self.getDomain(), link['href'][1:])
|
new['detail_url'] = self.urls['detail'] % (self.getDomain(), link['href'][1:])
|
||||||
new['score'] = 20 if td.find('a', 'iverif') else 0
|
new['verified'] = True if td.find('a', 'iverify') else False
|
||||||
|
new['score'] = 100 if new['verified'] else 0
|
||||||
elif column_name is 'size':
|
elif column_name is 'size':
|
||||||
new['size'] = self.parseSize(td.text)
|
new['size'] = self.parseSize(td.text)
|
||||||
elif column_name is 'age':
|
elif column_name is 'age':
|
||||||
@@ -82,6 +86,10 @@ class Base(TorrentMagnetProvider):
|
|||||||
|
|
||||||
nr += 1
|
nr += 1
|
||||||
|
|
||||||
|
# Only store verified torrents
|
||||||
|
if self.conf('only_verified') and not new['verified']:
|
||||||
|
continue
|
||||||
|
|
||||||
results.append(new)
|
results.append(new)
|
||||||
except:
|
except:
|
||||||
log.error('Failed parsing KickAssTorrents: %s', traceback.format_exc())
|
log.error('Failed parsing KickAssTorrents: %s', traceback.format_exc())
|
||||||
@@ -123,8 +131,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'KickAssTorrents',
|
'name': 'KickAssTorrents',
|
||||||
'description': 'See <a href="https://kat.ph/">KickAssTorrents</a>',
|
'description': '<a href="https://kat.ph/">KickAssTorrents</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACD0lEQVR42pXK20uTcRjA8d/fsJsuap0orBuFlm3hir3JJvQOVmuwllN20Lb2isI2nVHKjBqrCWYaNnNuBrkSWxglhDVJOkBdSWUOq5FgoiOrMdRJ2xPPxW+8OUf1ge/FcyCUSVe2qedK5U/OxNTTXRNXEQ52Glb4O6dNEfK1auJkvRY7+/zxnQbA/D596laXcY3OWOiaIX2393SGznUmxkUo/YkDgqHemuzobQ7+NV+reo5Q1mqp68GABdY3+/EloO+JeN4tEqiFU8f3CwhyWo9E7wfMgI0ELTDx0AvjIxcgvZoC9P7NMN7yMmrFeoKa68rfDfmrARsNN0Ihr55cx59ctZWSiwS5bLKpwW4dYJH+M/B6/CYszE0BFZ+egG+Ln+HRoBN/cpl1pV6COIMkOnBVA/w+fXgGKJVM4LxhumMleoL06hJ3wKcCfl+/TAKKx17gnFePRwkqxR4BQSpFkbCrrQJueI7mWpyfATQ9OQY43+uv/+PutBycJ3y2qn2x7jY50GJvnwLKZjOwspyE5I8F4N+1yr1uwqcs3ym63Hwo29EiAyzUWQVr6WVAS4lZCPutQG/2GtES2YiW3d3XflYKtL72kzAcdEDHeSa3czeIMyyz/TApRKvcFfE0isHbJMnrHCf6xTLb1ORvWNlWo91cvHrJUQo0o6ZoRi7dIiT/g2WEDi27Iyov21xMCvgNfXvtwIACfHwAAAAASUVORK5CYII=',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
@@ -151,6 +160,13 @@ config = [{
|
|||||||
'default': 40,
|
'default': 40,
|
||||||
'description': 'Will not be (re)moved until this seed time (in hours) is met.',
|
'description': 'Will not be (re)moved until this seed time (in hours) is met.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'only_verified',
|
||||||
|
'advanced': True,
|
||||||
|
'type': 'bool',
|
||||||
|
'default': False,
|
||||||
|
'description': 'Only search for verified releases.'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'extra_score',
|
'name': 'extra_score',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ class Base(TorrentProvider):
|
|||||||
torrentdesc += ' HQ'
|
torrentdesc += ' HQ'
|
||||||
if self.conf('prefer_golden'):
|
if self.conf('prefer_golden'):
|
||||||
torrentscore += 5000
|
torrentscore += 5000
|
||||||
|
if 'FreeleechType' in torrent:
|
||||||
|
torrentdesc += ' Freeleech'
|
||||||
|
if self.conf('prefer_freeleech'):
|
||||||
|
torrentscore += 7000
|
||||||
if 'Scene' in torrent and torrent['Scene']:
|
if 'Scene' in torrent and torrent['Scene']:
|
||||||
torrentdesc += ' Scene'
|
torrentdesc += ' Scene'
|
||||||
if self.conf('prefer_scene'):
|
if self.conf('prefer_scene'):
|
||||||
@@ -187,8 +191,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'PassThePopcorn',
|
'name': 'PassThePopcorn',
|
||||||
'description': 'See <a href="https://passthepopcorn.me">PassThePopcorn.me</a>',
|
'description': '<a href="https://passthepopcorn.me">PassThePopcorn.me</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAARklEQVQoz2NgIAP8BwMiGWRpIN1JNWn/t6T9f532+W8GkNt7vzz9UkfarZVpb68BuWlbnqW1nU7L2DMx7eCoBlpqGOppCQB83zIgIg+wWQAAAABJRU5ErkJggg==',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
@@ -222,6 +227,14 @@ config = [{
|
|||||||
'default': 1,
|
'default': 1,
|
||||||
'description': 'Favors Golden Popcorn-releases over all other releases.'
|
'description': 'Favors Golden Popcorn-releases over all other releases.'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'prefer_freeleech',
|
||||||
|
'advanced': True,
|
||||||
|
'type': 'bool',
|
||||||
|
'label': 'Prefer Freeleech',
|
||||||
|
'default': 1,
|
||||||
|
'description': 'Favors torrents marked as freeleech over all other releases.'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'prefer_scene',
|
'name': 'prefer_scene',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
from urlparse import parse_qs
|
|
||||||
import re
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode, toUnicode
|
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
|
||||||
from couchpotato.core.logger import CPLog
|
|
||||||
from couchpotato.core.media._base.providers.torrent.base import TorrentMagnetProvider
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Base(TorrentMagnetProvider):
|
|
||||||
|
|
||||||
urls = {
|
|
||||||
'test': 'https://publichd.se',
|
|
||||||
'detail': 'https://publichd.se/index.php?page=torrent-details&id=%s',
|
|
||||||
'search': 'https://publichd.se/index.php',
|
|
||||||
}
|
|
||||||
http_time_between_calls = 0
|
|
||||||
|
|
||||||
def search(self, movie, quality):
|
|
||||||
|
|
||||||
if not quality.get('hd', False):
|
|
||||||
return []
|
|
||||||
|
|
||||||
return super(Base, self).search(movie, quality)
|
|
||||||
|
|
||||||
def _search(self, media, quality, results):
|
|
||||||
|
|
||||||
query = self.buildUrl(media)
|
|
||||||
|
|
||||||
params = tryUrlencode({
|
|
||||||
'page': 'torrents',
|
|
||||||
'search': query,
|
|
||||||
'active': 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
data = self.getHTMLData('%s?%s' % (self.urls['search'], params))
|
|
||||||
|
|
||||||
if data:
|
|
||||||
|
|
||||||
try:
|
|
||||||
soup = BeautifulSoup(data)
|
|
||||||
|
|
||||||
results_table = soup.find('table', attrs = {'id': 'bgtorrlist2'})
|
|
||||||
entries = results_table.find_all('tr')
|
|
||||||
|
|
||||||
for result in entries[2:len(entries) - 1]:
|
|
||||||
info_url = result.find(href = re.compile('torrent-details'))
|
|
||||||
download = result.find(href = re.compile('magnet:'))
|
|
||||||
|
|
||||||
if info_url and download:
|
|
||||||
|
|
||||||
url = parse_qs(info_url['href'])
|
|
||||||
|
|
||||||
results.append({
|
|
||||||
'id': url['id'][0],
|
|
||||||
'name': six.text_type(info_url.string),
|
|
||||||
'url': download['href'],
|
|
||||||
'detail_url': self.urls['detail'] % url['id'][0],
|
|
||||||
'size': self.parseSize(result.find_all('td')[7].string),
|
|
||||||
'seeders': tryInt(result.find_all('td')[4].string),
|
|
||||||
'leechers': tryInt(result.find_all('td')[5].string),
|
|
||||||
'get_more_info': self.getMoreInfo
|
|
||||||
})
|
|
||||||
|
|
||||||
except:
|
|
||||||
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
|
|
||||||
|
|
||||||
def getMoreInfo(self, item):
|
|
||||||
|
|
||||||
cache_key = 'publichd.%s' % item['id']
|
|
||||||
description = self.getCache(cache_key)
|
|
||||||
|
|
||||||
if not description:
|
|
||||||
|
|
||||||
try:
|
|
||||||
full_description = self.urlopen(item['detail_url'])
|
|
||||||
html = BeautifulSoup(full_description)
|
|
||||||
nfo_pre = html.find('div', attrs = {'id': 'torrmain'})
|
|
||||||
description = toUnicode(nfo_pre.text) if nfo_pre else ''
|
|
||||||
except:
|
|
||||||
log.error('Failed getting more info for %s', item['name'])
|
|
||||||
description = ''
|
|
||||||
|
|
||||||
self.setCache(cache_key, description, timeout = 25920000)
|
|
||||||
|
|
||||||
item['description'] = description
|
|
||||||
return item
|
|
||||||
|
|
||||||
|
|
||||||
config = [{
|
|
||||||
'name': 'publichd',
|
|
||||||
'groups': [
|
|
||||||
{
|
|
||||||
'tab': 'searcher',
|
|
||||||
'list': 'torrent_providers',
|
|
||||||
'name': 'PublicHD',
|
|
||||||
'description': 'Public Torrent site with only HD content. See <a href="https://publichd.se/">PublicHD</a>',
|
|
||||||
'wizard': True,
|
|
||||||
'options': [
|
|
||||||
{
|
|
||||||
'name': 'enabled',
|
|
||||||
'type': 'enabler',
|
|
||||||
'default': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'seed_ratio',
|
|
||||||
'label': 'Seed ratio',
|
|
||||||
'type': 'float',
|
|
||||||
'default': 1,
|
|
||||||
'description': 'Will not be (re)moved until this seed ratio is met.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'seed_time',
|
|
||||||
'label': 'Seed time',
|
|
||||||
'type': 'int',
|
|
||||||
'default': 40,
|
|
||||||
'description': 'Will not be (re)moved until this seed time (in hours) is met.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'extra_score',
|
|
||||||
'advanced': True,
|
|
||||||
'label': 'Extra Score',
|
|
||||||
'type': 'int',
|
|
||||||
'default': 0,
|
|
||||||
'description': 'Starting score for each release found via this provider.',
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}]
|
|
||||||
@@ -24,9 +24,9 @@ class Base(TorrentProvider):
|
|||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
http_time_between_calls = 1 # Seconds
|
||||||
|
|
||||||
def _search(self, media, quality, results):
|
def _searchOnTitle(self, title, media, quality, results):
|
||||||
|
|
||||||
url = self.buildUrl(media, quality)
|
url = self.buildUrl(title, media, quality)
|
||||||
data = self.getHTMLData(url)
|
data = self.getHTMLData(url)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
@@ -89,8 +89,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'SceneAccess',
|
'name': 'SceneAccess',
|
||||||
'description': 'See <a href="https://sceneaccess.eu/">SceneAccess</a>',
|
'description': '<a href="https://sceneaccess.eu/">SceneAccess</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAACT0lEQVR4AYVQS0sbURidO3OTmajJ5FElTTOkPmZ01GhHrIq0aoWAj1Vc+A/cuRMXbl24V9SlCGqrLhVFCrooEhCp2BAx0mobTY2kaR7qmOm87EXL1EWxh29xL+c7nPMdgGHYO5bF/gdbefnr6WlbWRnxluMwAB4Z0uEgXa7nwaDL7+/RNPzxbYvb/XJ0FBYVfd/ayh0fQ4qCGEHcm0KLRZUk7Pb2YRJPRwcsKMidnKD3t9VVT3s7BDh+z5FOZ3Vfn3h+Hltfx00mRRSRWFcUmmVNhYVqPn8dj3va2oh+txvcQRVF9ebm1fi4k+dRFbosY5rm4Hk7xxULQnJnx93S4g0EIEEQRoDLo6PrWEw8Pc0eHLwYGopMTDirqlJ7eyhYYGHhfgfHCcKYksZGVB/NcXI2mw6HhZERqrjYTNPHi4tFPh8aJIYIhgPlcCRDoZLW1s75+Z/7+59nZ/OJhLWigqAoKZX6Mjf3dXkZ3pydGYLc4aEoCCkInzQ1fRobS2xuvllaonkedfArnY5OTdGVldBkOADgqq2Nr6z8CIWaJietDHOhKB+HhwFKC6Gnq4ukKJvP9zcSbjYDXbeVlkKzuZBhnnV3e3t6UOmaJO0ODibW1hB1GYkg8R/gup7Z3TVZLJ5AILW9LcZiVpYtYBhw16O3t7cauckyeF9Tgz0ATpL2+nopmWycmbnY2LiKRjFk6/d7+/vRJfl4HGzV1T0UIM43MGBvaIBWK/YvwM5w+IMgGH8tkyEgvIpE7M3Nt6qqZrNyOq1kMmouh455Ggz+BhKY4GEc2CfwAAAAAElFTkSuQmCC',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -24,15 +24,18 @@ class Base(TorrentMagnetProvider):
|
|||||||
http_time_between_calls = 0
|
http_time_between_calls = 0
|
||||||
|
|
||||||
proxy_list = [
|
proxy_list = [
|
||||||
'https://tpb.ipredator.se',
|
'https://dieroschtibay.org',
|
||||||
|
'https://thebay.al',
|
||||||
'https://thepiratebay.se',
|
'https://thepiratebay.se',
|
||||||
'http://pirateproxy.ca',
|
'http://thepiratebay.se.net',
|
||||||
'http://tpb.al',
|
'http://thebootlegbay.com',
|
||||||
'http://www.tpb.gr',
|
'http://tpb.ninja.so',
|
||||||
'http://bayproxy.me',
|
'http://proxybay.fr',
|
||||||
'http://proxybay.eu',
|
'http://pirateproxy.in',
|
||||||
'http://www.getpirate.com',
|
'http://piratebay.skey.sk',
|
||||||
'http://piratebay.io',
|
'http://pirateproxy.be',
|
||||||
|
'http://bayproxy.li',
|
||||||
|
'http://proxybay.pw',
|
||||||
]
|
]
|
||||||
|
|
||||||
def _search(self, media, quality, results):
|
def _search(self, media, quality, results):
|
||||||
@@ -65,7 +68,7 @@ class Base(TorrentMagnetProvider):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
entries = results_table.find_all('tr')
|
entries = results_table.find_all('tr')
|
||||||
for result in entries[2:]:
|
for result in entries[1:]:
|
||||||
link = result.find(href = re.compile('torrent\/\d+\/'))
|
link = result.find(href = re.compile('torrent\/\d+\/'))
|
||||||
download = result.find(href = re.compile('magnet:'))
|
download = result.find(href = re.compile('magnet:'))
|
||||||
|
|
||||||
@@ -109,7 +112,11 @@ class Base(TorrentMagnetProvider):
|
|||||||
full_description = self.getCache('tpb.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
full_description = self.getCache('tpb.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
|
||||||
html = BeautifulSoup(full_description)
|
html = BeautifulSoup(full_description)
|
||||||
nfo_pre = html.find('div', attrs = {'class': 'nfo'})
|
nfo_pre = html.find('div', attrs = {'class': 'nfo'})
|
||||||
description = toUnicode(nfo_pre.text) if nfo_pre else ''
|
description = ''
|
||||||
|
try:
|
||||||
|
description = toUnicode(nfo_pre.text)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
item['description'] = description
|
item['description'] = description
|
||||||
return item
|
return item
|
||||||
@@ -122,8 +129,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'ThePirateBay',
|
'name': 'ThePirateBay',
|
||||||
'description': 'The world\'s largest bittorrent tracker. See <a href="http://fucktimkuik.org/">ThePirateBay</a>',
|
'description': 'The world\'s largest bittorrent tracker. <a href="http://fucktimkuik.org/">ThePirateBay</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAA3UlEQVQY02P4DwT/YADIZvj//7qnozMYODmtAAusZoCDELDAegYGViZhAWZmRoYoqIDupfhNN1M3dTBEggXWMZg9jZRXV77YxhAOFpjDwMAPMoCXmcHsF1SAQZ6bQY2VgUEbKHClcAYzg3mINEO8jSCD478/DPsZmvqWblu1bOmStes3Pp0ezVDF4Gif0Hfx9///74/ObRZ2YNiZ47C8XIRBxFJR0jbSSUud4f9zAQWn8NTuziAt2zy5xIMM/z8LFX0E+fD/x0MRDCeA1v7Z++Y/FDzyvAtyBxIA+h8A8ZKLeT+lJroAAAAASUVORK5CYII=',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -90,8 +90,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'TorrentBytes',
|
'name': 'TorrentBytes',
|
||||||
'description': 'See <a href="http://torrentbytes.net">TorrentBytes</a>',
|
'description': '<a href="http://torrentbytes.net">TorrentBytes</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAeFBMVEUAAAAAAEQAA1QAEmEAKnQALHYAMoEAOokAQpIASYsASZgAS5UATZwATosATpgAVJ0AWZwAYZ4AZKAAaZ8Ab7IAcbMAfccAgcQAgcsAhM4AiscAjMkAmt0AoOIApecAp/EAqvQAs+kAt+wA3P8A4f8A//8VAAAfDbiaAl08AAAAjUlEQVQYGQXBO04DQRAFwHqz7Z8sECIl5f73ISRD5GBs7UxTlWfg9vYXnvJRQJqOL88D6BAwJtMMumHUVCl60aa6H93IrIv0b+157f1lpk+fm87lMWrZH0vncKbXdRUQrRmrh9C6Iwkq6rg4PXZcyXmbizzeV/g+rDra0rGve8jPKLSOJNi2AQAwAGjwD7ApPkEHdtPQAAAAAElFTkSuQmCC',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ class Base(TorrentProvider):
|
|||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
http_time_between_calls = 1 # Seconds
|
||||||
|
|
||||||
def _search(self, media, quality, results):
|
def _searchOnTitle(self, title, media, quality, results):
|
||||||
|
|
||||||
query = self.buildUrl(media)
|
query = '"%s" %s' % (title, media['info']['year'])
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'/browse.php?': None,
|
'/browse.php?': None,
|
||||||
@@ -68,8 +68,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'TorrentDay',
|
'name': 'TorrentDay',
|
||||||
'description': 'See <a href="http://www.td.af/">TorrentDay</a>',
|
'description': '<a href="http://www.td.af/">TorrentDay</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC5ElEQVQ4y12TXUgUURTH//fO7Di7foeQJH6gEEEIZZllVohfSG/6UA+RSFAQQj74VA8+Bj30lmAlRVSEvZRfhNhaka5ZUG1paKaW39tq5O6Ou+PM3M4o6m6X+XPPzD3zm/+dcy574r515WfIW8CZBM4YAA5Gc/aQC3yd7oXYEONcsISE5dTDh91HS0t7FEWhBUAeN9ynV/d9qJAgE4AECURAcVsGlCCnly26LMA0IQwTa52dje3d3e3hcPi8qqrrMjcVYI3EHCQZlkFOHBwR2QHh2ASAAIJxWGAQEDxjePhs3527XjJwnb37OHBq0T+Tyyjh+9KnEzNJ7nouc1Q/3A3HGsOvnJy+PSUlj81w2Lny9WuJ6+3AmTjD4HOcrdR2dWXLRQePvyaSLfQOPMPC8mC9iHCsOxSyzJCelzdSXlNzD5ujpb25Wbfc/XXJemTXF4+nnCNq+AMLe50uFfEJTiw4GXSFtiHL0SnIq66+p0kSArqO+eH3RdsAv9+f5vW7L7GICq6rmM8XBCAXlBw90rOyxibn5yzfkg/L09M52/jxqdESaIrBXHYZZbB1GX8cEpySxKIB8S5XcOnvqpli1zuwmrTtoLjw5LOK/eeuWsE4JH5IRPaPZKiKigmPp+5pa+u1aEjIMhEgrRkmi9mgxGUhM7LNJSzOzsE3+cOeExovXOjdytE0LV4zqNZUtV0uZzAGoGkhDH/2YHZiErmv4uyWQnZZWc+hoqL3WzlTExN5hhA8IEwkZWZOxwB++30YG/9GkYCPvqAaHAW5uWPROW86OmqCprUR7z1yZDAGQNuCvkoB/baIKUBWMTYymv+gra3eJNvjXu+B562tFyXqTJ6YuHK8rKwvBmC3vR7cOCPQLWFz8LnfXWUrJo9U19BwMyUlJRjTSMJ2ENxUiGxq9KXQfwqYlnWstvbR5aamG9g0uzM8Q4OFt++3NNixQ2NgYmeN03FOTUv7XVpV9aKisvLl1vN/WVhNc/Fi1NEAAAAASUVORK5CYII=',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -13,20 +13,20 @@ log = CPLog(__name__)
|
|||||||
class Base(TorrentProvider):
|
class Base(TorrentProvider):
|
||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'test': 'http://www.torrentleech.org/',
|
'test': 'https://www.torrentleech.org/',
|
||||||
'login': 'http://www.torrentleech.org/user/account/login/',
|
'login': 'https://www.torrentleech.org/user/account/login/',
|
||||||
'login_check': 'http://torrentleech.org/user/messages',
|
'login_check': 'https://torrentleech.org/user/messages',
|
||||||
'detail': 'http://www.torrentleech.org/torrent/%s',
|
'detail': 'https://www.torrentleech.org/torrent/%s',
|
||||||
'search': 'http://www.torrentleech.org/torrents/browse/index/query/%s/categories/%d',
|
'search': 'https://www.torrentleech.org/torrents/browse/index/query/%s/categories/%d',
|
||||||
'download': 'http://www.torrentleech.org%s',
|
'download': 'https://www.torrentleech.org%s',
|
||||||
}
|
}
|
||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
http_time_between_calls = 1 # Seconds
|
||||||
cat_backup_id = None
|
cat_backup_id = None
|
||||||
|
|
||||||
def _search(self, media, quality, results):
|
def _searchOnTitle(self, title, media, quality, results):
|
||||||
|
|
||||||
url = self.urls['search'] % self.buildUrl(media, quality)
|
url = self.urls['search'] % self.buildUrl(title, media, quality)
|
||||||
|
|
||||||
data = self.getHTMLData(url)
|
data = self.getHTMLData(url)
|
||||||
|
|
||||||
@@ -80,8 +80,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'TorrentLeech',
|
'name': 'TorrentLeech',
|
||||||
'description': 'See <a href="http://torrentleech.org">TorrentLeech</a>',
|
'description': '<a href="http://torrentleech.org">TorrentLeech</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACHUlEQVR4AZVSO48SYRSdGTCBEMKzILLAWiybkKAGMZRUUJEoDZX7B9zsbuQPYEEjNLTQkYgJDwsoSaxspEBsCITXjjNAIKi8AkzceXgmbHQ1NJ5iMufmO9/9zrmXlCSJ+B8o75J8Pp/NZj0eTzweBy0Wi4PBYD6f12o1r9ebTCZx+22HcrnMsuxms7m6urTZ7LPZDMVYLBZ8ZV3yo8aq9Pq0wzCMTqe77dDv9y8uLyAWBH6xWOyL0K/56fcb+rrPgPZ6PZfLRe1fsl6vCUmGKIqoqNXqdDr9Dbjps9znUV0uTqdTjuPkDoVCIfcuJ4gizjMMm8u9vW+1nr04czqdK56c37CbKY9j2+1WEARZ0Gq1RFHAz2q1qlQqXxoN69HRcDjUarW8ZD6QUigUOnY8uKYH8N1sNkul9yiGw+F6vS4Rxn8EsodEIqHRaOSnq9T7ajQazWQycEIR1AEBYDabSZJyHDucJyegwWBQr9ebTCaKvHd4cCQANUU9evwQ1Ofz4YvUKUI43GE8HouSiFiNRhOowWBIpVLyHITJkuW3PwgAEf3pgIwxF5r+OplMEsk3CPT5szCMnY7EwUdhwUh/CXiej0Qi3idPz89fdrpdbsfBzH7S3Q9K5pP4c0sAKpVKoVAQGO1ut+t0OoFAQHkH2Da/3/+but3uarWK0ZMQoNdyucRutdttmqZxMTzY7XaYxsrgtUjEZrNhkSwWyy/0NCatZumrNQAAAABJRU5ErkJggg==',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ config = [{
|
|||||||
'order': 10,
|
'order': 10,
|
||||||
'description': 'CouchPotato torrent provider. Checkout <a href="https://github.com/RuudBurger/CouchPotatoServer/wiki/CouchPotato-Torrent-Provider">the wiki page about this provider</a> for more info.',
|
'description': 'CouchPotato torrent provider. Checkout <a href="https://github.com/RuudBurger/CouchPotatoServer/wiki/CouchPotato-Torrent-Provider">the wiki page about this provider</a> for more info.',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABSElEQVR4AZ2Nz0oCURTGv8t1YMpqUxt9ARFxoQ/gQtppgvUKcu/sxB5iBJkogspaBC6iVUplEC6kv+oiiKDNhAtt16roP0HQgdsMLgaxfvy4nHP4Pi48qE2g4v91JOqT1CH/UnA7w7icUlLawyEdj+ZI/7h6YluWbRiddHonHh9M70aj7VTKzuXuikUMci/EO/ACnAI15599oAk8AR/AgxBQNCzreD7bmpl+FOIVuAHqQDUcJo+AK+CZFKLt95/MpSmMt0TiW9POxse6UvYZ6zB2wFgjFiNpOGesR0rZ0PVPXf8KhUCl22CwClz4eN8weoZBb9c0bdPsOWvHx/cYu9Y0CoNoZTJrwAbn5DrnZc6XOV+igVbnsgo0IxEomlJuA1vUIYGyq3PZBChwmExCUSmVZgMBDIUCK4UCFIv5vHIhm/XUDeAf/ADbcpd5+aXSWQAAAABJRU5ErkJggg==',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ log = CPLog(__name__)
|
|||||||
class Base(TorrentProvider):
|
class Base(TorrentProvider):
|
||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'test': 'https://torrentshack.net/',
|
'test': 'http://torrentshack.eu/',
|
||||||
'login': 'https://torrentshack.net/login.php',
|
'login': 'http://torrentshack.eu/login.php',
|
||||||
'login_check': 'https://torrentshack.net/inbox.php',
|
'login_check': 'http://torrentshack.eu/inbox.php',
|
||||||
'detail': 'https://torrentshack.net/torrent/%s',
|
'detail': 'http://torrentshack.eu/torrent/%s',
|
||||||
'search': 'https://torrentshack.net/torrents.php?action=advanced&searchstr=%s&scene=%s&filter_cat[%d]=1',
|
'search': 'http://torrentshack.eu/torrents.php?action=advanced&searchstr=%s&scene=%s&filter_cat[%d]=1',
|
||||||
'download': 'https://torrentshack.net/%s',
|
'download': 'http://torrentshack.eu/%s',
|
||||||
}
|
}
|
||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
http_time_between_calls = 1 # Seconds
|
||||||
@@ -42,15 +42,16 @@ class Base(TorrentProvider):
|
|||||||
|
|
||||||
link = result.find('span', attrs = {'class': 'torrent_name_link'}).parent
|
link = result.find('span', attrs = {'class': 'torrent_name_link'}).parent
|
||||||
url = result.find('td', attrs = {'class': 'torrent_td'}).find('a')
|
url = result.find('td', attrs = {'class': 'torrent_td'}).find('a')
|
||||||
|
tds = result.find_all('td')
|
||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
'id': link['href'].replace('torrents.php?torrentid=', ''),
|
'id': link['href'].replace('torrents.php?torrentid=', ''),
|
||||||
'name': six.text_type(link.span.string).translate({ord(six.u('\xad')): None}),
|
'name': six.text_type(link.span.string).translate({ord(six.u('\xad')): None}),
|
||||||
'url': self.urls['download'] % url['href'],
|
'url': self.urls['download'] % url['href'],
|
||||||
'detail_url': self.urls['download'] % link['href'],
|
'detail_url': self.urls['download'] % link['href'],
|
||||||
'size': self.parseSize(result.find_all('td')[4].string),
|
'size': self.parseSize(result.find_all('td')[5].string),
|
||||||
'seeders': tryInt(result.find_all('td')[6].string),
|
'seeders': tryInt(tds[len(tds)-2].string),
|
||||||
'leechers': tryInt(result.find_all('td')[7].string),
|
'leechers': tryInt(tds[len(tds)-1].string),
|
||||||
})
|
})
|
||||||
|
|
||||||
except:
|
except:
|
||||||
@@ -80,7 +81,9 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'TorrentShack',
|
'name': 'TorrentShack',
|
||||||
'description': 'See <a href="https://www.torrentshack.net/">TorrentShack</a>',
|
'description': '<a href="http://torrentshack.eu/">TorrentShack</a>',
|
||||||
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABmElEQVQoFQXBzY2cVRiE0afqvd84CQiAnxWWtyxsS6ThINBYg2Dc7mZBMEjE4mzs6e9WcY5+ePNuVFJJodQAoLo+SaWCy9rcV8cmjah3CI6iYu7oRU30kE5xxELRfamklY3k1NL19sSm7vPzP/ZdNZzKVDaY2sPZJBh9fv5ITrmG2+Vp4e1sPchVqTCQZJnVXi+/L4uuAJGly1+Pw8CprLbi8Om7tbT19/XRqJUk11JP9uHj9ulxhXbvJbI9qJvr5YkGXFG2IBT8tXczt+sfzDZCp3765f3t9tHEHGEDACma77+8o4oATKk+/PfW9YmHruRFjWoVSFsVsGu1YSKq6Oc37+n98unPZSRlY7vsKDqN+92X3yR9+PdXee3iJNKMStqdcZqoTJbUSi5JOkpfRlhSI0mSpEmCFKoU7FqSNOLAk54uGwCStMUCgLrVic62g7oDoFmmdI+P3S0pDe1xvDqb6XrZqbtzShWNoh9fv/XQHaDdM9OqrZi2M7M3UrB2vlkPS1IbdEBk7UiSoD6VlZ6aKWer4aH4f/AvKoHUTjuyAAAAAElFTkSuQmCC',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -80,11 +80,12 @@ config = [{
|
|||||||
'name': 'Torrentz',
|
'name': 'Torrentz',
|
||||||
'description': 'Torrentz is a free, fast and powerful meta-search engine. <a href="https://torrentz.eu/">Torrentz</a>',
|
'description': 'Torrentz is a free, fast and powerful meta-search engine. <a href="https://torrentz.eu/">Torrentz</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAQklEQVQ4y2NgAALjtJn/ycEMlGiGG0IVAxiwAKzOxaKGARcgxgC8YNSAwWoAzuRMjgsIugqfAUR5CZcBRIcHsWEAADSA96Ig020yAAAAAElFTkSuQmCC',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
'type': 'enabler',
|
'type': 'enabler',
|
||||||
'default': False
|
'default': True
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'verified_only',
|
'name': 'verified_only',
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import traceback
|
|||||||
|
|
||||||
from couchpotato.core.helpers.variable import tryInt, getIdentifier
|
from couchpotato.core.helpers.variable import tryInt, getIdentifier
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media._base.providers.torrent.base import TorrentProvider
|
from couchpotato.core.media._base.providers.torrent.base import TorrentMagnetProvider
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Base(TorrentProvider):
|
class Base(TorrentMagnetProvider):
|
||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'test': '%s/api',
|
'test': '%s/api',
|
||||||
@@ -35,7 +35,11 @@ class Base(TorrentProvider):
|
|||||||
|
|
||||||
def _search(self, movie, quality, results):
|
def _search(self, movie, quality, results):
|
||||||
|
|
||||||
search_url = self.urls['search'] % (self.getDomain(), getIdentifier(movie), quality['identifier'])
|
domain = self.getDomain()
|
||||||
|
if not domain:
|
||||||
|
return
|
||||||
|
|
||||||
|
search_url = self.urls['search'] % (domain, getIdentifier(movie), quality['identifier'])
|
||||||
|
|
||||||
data = self.getJsonData(search_url)
|
data = self.getJsonData(search_url)
|
||||||
|
|
||||||
@@ -43,21 +47,19 @@ class Base(TorrentProvider):
|
|||||||
try:
|
try:
|
||||||
for result in data.get('MovieList'):
|
for result in data.get('MovieList'):
|
||||||
|
|
||||||
try:
|
if result['Quality'] and result['Quality'] not in result['MovieTitle']:
|
||||||
title = result['TorrentUrl'].split('/')[-1][:-8].replace('_', '.').strip('._')
|
title = result['MovieTitle'] + ' BrRip ' + result['Quality']
|
||||||
title = title.replace('.-.', '-')
|
else:
|
||||||
title = title.replace('..', '.')
|
title = result['MovieTitle'] + ' BrRip'
|
||||||
except:
|
|
||||||
continue
|
|
||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
'id': result['MovieID'],
|
'id': result['MovieID'],
|
||||||
'name': title,
|
'name': title,
|
||||||
'url': result['TorrentMagnetUrl'],
|
'url': result['TorrentMagnetUrl'],
|
||||||
'detail_url': self.urls['detail'] % (self.getDomain(), result['MovieID']),
|
'detail_url': self.urls['detail'] % (domain, result['MovieID']),
|
||||||
'size': self.parseSize(result['Size']),
|
'size': self.parseSize(result['Size']),
|
||||||
'seeders': tryInt(result['TorrentSeeds']),
|
'seeders': tryInt(result['TorrentSeeds']),
|
||||||
'leechers': tryInt(result['TorrentPeers'])
|
'leechers': tryInt(result['TorrentPeers']),
|
||||||
})
|
})
|
||||||
|
|
||||||
except:
|
except:
|
||||||
@@ -77,6 +79,7 @@ config = [{
|
|||||||
'name': 'Yify',
|
'name': 'Yify',
|
||||||
'description': 'Free provider, less accurate. Small HD movies, encoded by <a href="https://yify-torrents.com/">Yify</a>.',
|
'description': 'Free provider, less accurate. Small HD movies, encoded by <a href="https://yify-torrents.com/">Yify</a>.',
|
||||||
'wizard': False,
|
'wizard': False,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACL0lEQVR4AS1SPW/UQBAd23fxne/Ld2dvzvHuzPocEBAKokCBqGiQ6IgACYmvUKRBFEQgKKGg4BAlUoggggYUEQpSHOI7CIEoQs/fYcbLaU/efTvvvZlnA1qydoxU5kcxX0CkgmQZtPy0hCUjvK+WgEByOZ5dns1O5bzna8fRVkgsxH8B0YouIvBhdD5T11NiVOoKrsttyUcpRW0InUrFnwe9HzuP2uaQZYhF2LQ76TTXw2RVMTK8mYYbjfh+zNquMVCrqn93aArLSixPxnafdGDLaz1tjY5rmNa8z5BczEQOxQfCl1GyoqoWxYRN1bkh7ELw3q/vhP6HIL4TG9KumpjgvwuyM7OsjSj98E/vszMfZ7xvPtMaWxGO5crwIumKCR5HxDtJ0AWKGG204RfUd/3smJYqwem/Q7BTS1ZGfM4LNpVwuKAz6cMeROst0S2EwNE7GjTehO2H3dxqIpdkydat15G3F8SXBi4GlpBNlSz012L/k2+W0CLLk/jbcf13rf41yJeMQ8QWUZiHCfCA9ad+81nEKPtoS9mJOf9v0NmMJHgUT6xayheK9EIK7JJeU/AF4scDF7Y5SPlJrRcxJ+um4ibNEdObxLiIwJim+eT2AL5D9CIcnZ5zvSJi9eIlNHVVtZ831dk5svPgvjPWTq+ktWkd/kD0qtm71x+sDQe3kt6DXnM7Ct+GajmTxKlkAokWljyAKSm5oWa2w+BH4P2UuVub7eTyiGOQYapY/wEztHduSDYz5gAAAABJRU5ErkJggg==',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, addEvent
|
from couchpotato.core.event import fireEvent, addEvent
|
||||||
from couchpotato.core.helpers.variable import mergeDicts
|
from couchpotato.core.helpers.variable import mergeDicts, getImdb
|
||||||
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
|
||||||
|
|
||||||
@@ -35,12 +35,21 @@ class Search(Plugin):
|
|||||||
elif isinstance(types, (list, tuple, set)):
|
elif isinstance(types, (list, tuple, set)):
|
||||||
types = list(types)
|
types = list(types)
|
||||||
|
|
||||||
|
imdb_identifier = getImdb(q)
|
||||||
|
|
||||||
if not types:
|
if not types:
|
||||||
result = fireEvent('info.search', q = q, merge = True)
|
if imdb_identifier:
|
||||||
|
result = fireEvent('movie.info', identifier = imdb_identifier, merge = True)
|
||||||
|
result = {result['type']: [result]}
|
||||||
|
else:
|
||||||
|
result = fireEvent('info.search', q = q, merge = True)
|
||||||
else:
|
else:
|
||||||
result = {}
|
result = {}
|
||||||
for media_type in types:
|
for media_type in types:
|
||||||
result[media_type] = fireEvent('%s.search' % media_type)
|
if imdb_identifier:
|
||||||
|
result[media_type] = fireEvent('%s.info' % media_type, identifier = imdb_identifier)
|
||||||
|
else:
|
||||||
|
result[media_type] = fireEvent('%s.search' % media_type, q = q)
|
||||||
|
|
||||||
return mergeDicts({
|
return mergeDicts({
|
||||||
'success': True,
|
'success': True,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
transition: all .4s cubic-bezier(0.9,0,0.1,1);
|
transition: all .4s cubic-bezier(0.9,0,0.1,1);
|
||||||
position: absolute;
|
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
border: 0 solid transparent;
|
border: 0 solid transparent;
|
||||||
border-bottom-width: 4px;
|
border-bottom-width: 4px;
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ Block.Search = new Class({
|
|||||||
self.input = new Element('input', {
|
self.input = new Element('input', {
|
||||||
'placeholder': 'Search & add a new media',
|
'placeholder': 'Search & add a new media',
|
||||||
'events': {
|
'events': {
|
||||||
|
'input': self.keyup.bind(self),
|
||||||
|
'paste': self.keyup.bind(self),
|
||||||
|
'change': self.keyup.bind(self),
|
||||||
'keyup': self.keyup.bind(self),
|
'keyup': self.keyup.bind(self),
|
||||||
'focus': function(){
|
'focus': function(){
|
||||||
if(focus_timer) clearTimeout(focus_timer);
|
if(focus_timer) clearTimeout(focus_timer);
|
||||||
|
|||||||
@@ -73,4 +73,24 @@ config = [{
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
}, {
|
||||||
|
'name': 'torrent',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'searcher',
|
||||||
|
'name': 'searcher',
|
||||||
|
'wizard': True,
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'minimum_seeders',
|
||||||
|
'advanced': True,
|
||||||
|
'label': 'Minimum seeders',
|
||||||
|
'description': 'Ignore torrents with seeders below this number',
|
||||||
|
'default': 1,
|
||||||
|
'type': 'int',
|
||||||
|
'unit': 'seeders'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
}]
|
}]
|
||||||
|
|||||||
@@ -87,31 +87,23 @@ class Searcher(SearcherBase):
|
|||||||
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = None):
|
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = None):
|
||||||
if not preferred_quality: preferred_quality = {}
|
if not preferred_quality: preferred_quality = {}
|
||||||
|
|
||||||
name = nzb['name']
|
|
||||||
size = nzb.get('size', 0)
|
|
||||||
nzb_words = re.split('\W+', simplifyString(name))
|
|
||||||
|
|
||||||
qualities = fireEvent('quality.all', single = True)
|
|
||||||
|
|
||||||
found = {}
|
found = {}
|
||||||
for quality in qualities:
|
|
||||||
# Main in words
|
|
||||||
if quality['identifier'] in nzb_words:
|
|
||||||
found[quality['identifier']] = True
|
|
||||||
|
|
||||||
# Alt in words
|
|
||||||
if list(set(nzb_words) & set(quality['alternative'])):
|
|
||||||
found[quality['identifier']] = True
|
|
||||||
|
|
||||||
# Try guessing via quality tags
|
# Try guessing via quality tags
|
||||||
guess = fireEvent('quality.guess', [nzb.get('name')], single = True)
|
guess = fireEvent('quality.guess', files = [nzb.get('name')], size = nzb.get('size', None), single = True)
|
||||||
if guess:
|
if guess:
|
||||||
found[guess['identifier']] = True
|
found[guess['identifier']] = True
|
||||||
|
|
||||||
# Hack for older movies that don't contain quality tag
|
# Hack for older movies that don't contain quality tag
|
||||||
|
name = nzb['name']
|
||||||
|
size = nzb.get('size', 0)
|
||||||
|
|
||||||
year_name = fireEvent('scanner.name_year', name, single = True)
|
year_name = fireEvent('scanner.name_year', name, single = True)
|
||||||
if len(found) == 0 and 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 > 20000: # Assume bd50
|
||||||
|
log.info('Quality was missing in name, assuming it\'s a BR-Disk based on the size: %s', size)
|
||||||
|
found['bd50'] = True
|
||||||
|
elif 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
|
||||||
else: # Assume dvdrip
|
else: # Assume dvdrip
|
||||||
@@ -123,7 +115,10 @@ class Searcher(SearcherBase):
|
|||||||
if found.get(allowed):
|
if found.get(allowed):
|
||||||
del found[allowed]
|
del found[allowed]
|
||||||
|
|
||||||
return not (found.get(preferred_quality['identifier']) and len(found) == 1)
|
if found.get(preferred_quality['identifier']) and len(found) == 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return found
|
||||||
|
|
||||||
def correct3D(self, nzb, preferred_quality = None):
|
def correct3D(self, nzb, preferred_quality = None):
|
||||||
if not preferred_quality: preferred_quality = {}
|
if not preferred_quality: preferred_quality = {}
|
||||||
@@ -134,7 +129,11 @@ class Searcher(SearcherBase):
|
|||||||
# Try guessing via quality tags
|
# Try guessing via quality tags
|
||||||
guess = fireEvent('quality.guess', [nzb.get('name')], single = True)
|
guess = fireEvent('quality.guess', [nzb.get('name')], single = True)
|
||||||
|
|
||||||
return threed == guess.get('is_3d')
|
if guess:
|
||||||
|
return threed == guess.get('is_3d')
|
||||||
|
# If no quality guess, assume not 3d
|
||||||
|
else:
|
||||||
|
return threed == False
|
||||||
|
|
||||||
def correctYear(self, haystack, year, year_range):
|
def correctYear(self, haystack, year, year_range):
|
||||||
|
|
||||||
@@ -179,6 +178,25 @@ class Searcher(SearcherBase):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def containsWords(self, rel_name, rel_words, conf, media):
|
||||||
|
|
||||||
|
# Make sure it has required words
|
||||||
|
words = splitString(self.conf('%s_words' % conf, section = 'searcher').lower())
|
||||||
|
try: words = removeDuplicate(words + splitString(media['category'][conf].lower()))
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
req_match = 0
|
||||||
|
for req_set in words:
|
||||||
|
if len(req_set) >= 2 and (req_set[:1] + req_set[-1:]) == '//':
|
||||||
|
if re.search(req_set[1:-1], rel_name):
|
||||||
|
log.debug('Regex match: %s', req_set[1:-1])
|
||||||
|
req_match += 1
|
||||||
|
else:
|
||||||
|
req = splitString(req_set, '&')
|
||||||
|
req_match += len(list(set(rel_words) & set(req))) == len(req)
|
||||||
|
|
||||||
|
return words, req_match > 0
|
||||||
|
|
||||||
def correctWords(self, rel_name, media):
|
def correctWords(self, rel_name, media):
|
||||||
media_title = fireEvent('searcher.get_search_title', media, single = True)
|
media_title = fireEvent('searcher.get_search_title', media, single = True)
|
||||||
media_words = re.split('\W+', simplifyString(media_title))
|
media_words = re.split('\W+', simplifyString(media_title))
|
||||||
@@ -186,31 +204,13 @@ class Searcher(SearcherBase):
|
|||||||
rel_name = simplifyString(rel_name)
|
rel_name = simplifyString(rel_name)
|
||||||
rel_words = re.split('\W+', rel_name)
|
rel_words = re.split('\W+', rel_name)
|
||||||
|
|
||||||
# Make sure it has required words
|
required_words, contains_required = self.containsWords(rel_name, rel_words, 'required', media)
|
||||||
required_words = splitString(self.conf('required_words', section = 'searcher').lower())
|
if len(required_words) > 0 and not contains_required:
|
||||||
try: required_words = removeDuplicate(required_words + splitString(media['category']['required'].lower()))
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
req_match = 0
|
|
||||||
for req_set in required_words:
|
|
||||||
req = splitString(req_set, '&')
|
|
||||||
req_match += len(list(set(rel_words) & set(req))) == len(req)
|
|
||||||
|
|
||||||
if len(required_words) > 0 and req_match == 0:
|
|
||||||
log.info2('Wrong: Required word missing: %s', rel_name)
|
log.info2('Wrong: Required word missing: %s', rel_name)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Ignore releases
|
ignored_words, contains_ignored = self.containsWords(rel_name, rel_words, 'ignored', media)
|
||||||
ignored_words = splitString(self.conf('ignored_words', section = 'searcher').lower())
|
if len(ignored_words) > 0 and contains_ignored:
|
||||||
try: ignored_words = removeDuplicate(ignored_words + splitString(media['category']['ignored'].lower()))
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
ignored_match = 0
|
|
||||||
for ignored_set in ignored_words:
|
|
||||||
ignored = splitString(ignored_set, '&')
|
|
||||||
ignored_match += len(list(set(rel_words) & set(ignored))) == len(ignored)
|
|
||||||
|
|
||||||
if len(ignored_words) > 0 and ignored_match:
|
|
||||||
log.info2("Wrong: '%s' contains 'ignored words'", rel_name)
|
log.info2("Wrong: '%s' contains 'ignored words'", rel_name)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
Regular → Executable
+36
-42
@@ -1,7 +1,7 @@
|
|||||||
import os
|
|
||||||
import traceback
|
import traceback
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from CodernityDB.database import RecordNotFound
|
||||||
from couchpotato import get_db
|
from couchpotato import get_db
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
||||||
@@ -27,6 +27,10 @@ class MovieBase(MovieTypeBase):
|
|||||||
|
|
||||||
addApiView('movie.add', self.addView, docs = {
|
addApiView('movie.add', self.addView, docs = {
|
||||||
'desc': 'Add new movie to the wanted list',
|
'desc': 'Add new movie to the wanted list',
|
||||||
|
'return': {'type': 'object', 'example': """{
|
||||||
|
'success': True,
|
||||||
|
'movie': object
|
||||||
|
}"""},
|
||||||
'params': {
|
'params': {
|
||||||
'identifier': {'desc': 'IMDB id of the movie your want to add.'},
|
'identifier': {'desc': 'IMDB id of the movie your want to add.'},
|
||||||
'profile_id': {'desc': 'ID of quality profile you want the add the movie in. If empty will use the default profile.'},
|
'profile_id': {'desc': 'ID of quality profile you want the add the movie in. If empty will use the default profile.'},
|
||||||
@@ -45,7 +49,7 @@ class MovieBase(MovieTypeBase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
addEvent('movie.add', self.add)
|
addEvent('movie.add', self.add)
|
||||||
addEvent('movie.update_info', self.updateInfo)
|
addEvent('movie.update', self.update)
|
||||||
addEvent('movie.update_release_dates', self.updateReleaseDate)
|
addEvent('movie.update_release_dates', self.updateReleaseDate)
|
||||||
|
|
||||||
def add(self, params = None, force_readd = True, search_after = True, update_after = True, notify_after = True, status = None):
|
def add(self, params = None, force_readd = True, search_after = True, update_after = True, notify_after = True, status = None):
|
||||||
@@ -90,7 +94,7 @@ class MovieBase(MovieTypeBase):
|
|||||||
|
|
||||||
# Default profile and category
|
# Default profile and category
|
||||||
default_profile = {}
|
default_profile = {}
|
||||||
if not params.get('profile_id'):
|
if (not params.get('profile_id') and status != 'done') or params.get('ignore_previous', False):
|
||||||
default_profile = fireEvent('profile.default', single = True)
|
default_profile = fireEvent('profile.default', single = True)
|
||||||
cat_id = params.get('category_id')
|
cat_id = params.get('category_id')
|
||||||
|
|
||||||
@@ -105,7 +109,7 @@ class MovieBase(MovieTypeBase):
|
|||||||
'imdb': params.get('identifier')
|
'imdb': params.get('identifier')
|
||||||
},
|
},
|
||||||
'status': status if status else 'active',
|
'status': status if status else 'active',
|
||||||
'profile_id': params.get('profile_id', default_profile.get('_id')),
|
'profile_id': params.get('profile_id') or default_profile.get('_id'),
|
||||||
'category_id': cat_id if cat_id is not None and len(cat_id) > 0 and cat_id != '-1' else None,
|
'category_id': cat_id if cat_id is not None and len(cat_id) > 0 and cat_id != '-1' else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,8 +121,17 @@ class MovieBase(MovieTypeBase):
|
|||||||
media['info'] = info
|
media['info'] = info
|
||||||
|
|
||||||
new = False
|
new = False
|
||||||
|
previous_profile = None
|
||||||
try:
|
try:
|
||||||
m = db.get('media', 'imdb-%s' % params.get('identifier'), with_doc = True)['doc']
|
m = db.get('media', 'imdb-%s' % params.get('identifier'), with_doc = True)['doc']
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.get('id', m.get('profile_id'))
|
||||||
|
previous_profile = m.get('profile_id')
|
||||||
|
except RecordNotFound:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
log.error('Failed getting previous profile: %s', traceback.format_exc())
|
||||||
except:
|
except:
|
||||||
new = True
|
new = True
|
||||||
m = db.insert(media)
|
m = db.insert(media)
|
||||||
@@ -139,16 +152,16 @@ class MovieBase(MovieTypeBase):
|
|||||||
|
|
||||||
# Clean snatched history
|
# Clean snatched history
|
||||||
for release in fireEvent('release.for_media', m['_id'], single = True):
|
for release in fireEvent('release.for_media', m['_id'], single = True):
|
||||||
if release.get('status') in ['downloaded', 'snatched', 'done']:
|
if release.get('status') in ['downloaded', 'snatched', 'seeding', 'done']:
|
||||||
if params.get('ignore_previous', False):
|
if params.get('ignore_previous', False):
|
||||||
release['status'] = 'ignored'
|
fireEvent('release.update_status', release['_id'], status = 'ignored')
|
||||||
db.update(release)
|
|
||||||
else:
|
else:
|
||||||
fireEvent('release.delete', release['_id'], single = True)
|
fireEvent('release.delete', release['_id'], single = True)
|
||||||
|
|
||||||
m['profile_id'] = params.get('profile_id', default_profile.get('id'))
|
m['profile_id'] = (params.get('profile_id') or default_profile.get('_id')) if not previous_profile else previous_profile
|
||||||
m['category_id'] = cat_id if cat_id is not None and len(cat_id) > 0 else (m.get('category_id') or None)
|
m['category_id'] = cat_id if cat_id is not None and len(cat_id) > 0 else (m.get('category_id') or None)
|
||||||
m['last_edit'] = int(time.time())
|
m['last_edit'] = int(time.time())
|
||||||
|
m['tags'] = []
|
||||||
|
|
||||||
do_search = True
|
do_search = True
|
||||||
db.update(m)
|
db.update(m)
|
||||||
@@ -161,7 +174,7 @@ class MovieBase(MovieTypeBase):
|
|||||||
# Trigger update info
|
# Trigger update info
|
||||||
if added and update_after:
|
if added and update_after:
|
||||||
# Do full update to get images etc
|
# Do full update to get images etc
|
||||||
fireEventAsync('movie.update_info', m['_id'], default_title = params.get('title'), on_complete = onComplete)
|
fireEventAsync('movie.update', m['_id'], default_title = params.get('title'), on_complete = onComplete)
|
||||||
|
|
||||||
# Remove releases
|
# Remove releases
|
||||||
for rel in fireEvent('release.for_media', m['_id'], single = True):
|
for rel in fireEvent('release.for_media', m['_id'], single = True):
|
||||||
@@ -169,6 +182,9 @@ class MovieBase(MovieTypeBase):
|
|||||||
db.delete(rel)
|
db.delete(rel)
|
||||||
|
|
||||||
movie_dict = fireEvent('media.get', m['_id'], single = True)
|
movie_dict = fireEvent('media.get', m['_id'], single = True)
|
||||||
|
if not movie_dict:
|
||||||
|
log.debug('Failed adding media, can\'t find it anymore')
|
||||||
|
return False
|
||||||
|
|
||||||
if do_search and search_after:
|
if do_search and search_after:
|
||||||
onComplete = self.createOnComplete(m['_id'])
|
onComplete = self.createOnComplete(m['_id'])
|
||||||
@@ -225,7 +241,7 @@ class MovieBase(MovieTypeBase):
|
|||||||
|
|
||||||
db.update(m)
|
db.update(m)
|
||||||
|
|
||||||
fireEvent('media.restatus', m['_id'])
|
fireEvent('media.restatus', m['_id'], single = True)
|
||||||
|
|
||||||
m = db.get('id', media_id)
|
m = db.get('id', media_id)
|
||||||
|
|
||||||
@@ -245,7 +261,7 @@ class MovieBase(MovieTypeBase):
|
|||||||
'success': False,
|
'success': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateInfo(self, media_id = None, identifier = None, default_title = None, extended = False):
|
def update(self, media_id = None, identifier = None, default_title = None, extended = False):
|
||||||
"""
|
"""
|
||||||
Update movie information inside media['doc']['info']
|
Update movie information inside media['doc']['info']
|
||||||
|
|
||||||
@@ -258,6 +274,10 @@ class MovieBase(MovieTypeBase):
|
|||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
lock_key = 'media.get.%s' % media_id if media_id else identifier
|
||||||
|
self.acquireLock(lock_key)
|
||||||
|
|
||||||
|
media = {}
|
||||||
try:
|
try:
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
||||||
@@ -301,42 +321,16 @@ class MovieBase(MovieTypeBase):
|
|||||||
media['title'] = def_title
|
media['title'] = def_title
|
||||||
|
|
||||||
# Files
|
# Files
|
||||||
images = info.get('images', [])
|
image_urls = info.get('images', [])
|
||||||
media['files'] = media.get('files', {})
|
|
||||||
for image_type in ['poster']:
|
|
||||||
|
|
||||||
# Remove non-existing files
|
self.getPoster(media, image_urls)
|
||||||
file_type = 'image_%s' % image_type
|
|
||||||
existing_files = list(set(media['files'].get(file_type, [])))
|
|
||||||
for ef in media['files'].get(file_type, []):
|
|
||||||
if not os.path.isfile(ef):
|
|
||||||
existing_files.remove(ef)
|
|
||||||
|
|
||||||
# Replace new files list
|
|
||||||
media['files'][file_type] = existing_files
|
|
||||||
if len(existing_files) == 0:
|
|
||||||
del media['files'][file_type]
|
|
||||||
|
|
||||||
# Loop over type
|
|
||||||
for image in images.get(image_type, []):
|
|
||||||
if not isinstance(image, (str, unicode)):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if file_type not in media['files'] or len(media['files'].get(file_type, [])) == 0:
|
|
||||||
file_path = fireEvent('file.download', url = image, single = True)
|
|
||||||
if file_path:
|
|
||||||
media['files'][file_type] = [file_path]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
db.update(media)
|
db.update(media)
|
||||||
|
|
||||||
return media
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed update media: %s', traceback.format_exc())
|
log.error('Failed update media: %s', traceback.format_exc())
|
||||||
|
|
||||||
return {}
|
self.releaseLock(lock_key)
|
||||||
|
return media
|
||||||
|
|
||||||
def updateReleaseDate(self, media_id):
|
def updateReleaseDate(self, media_id):
|
||||||
"""
|
"""
|
||||||
@@ -352,7 +346,7 @@ class MovieBase(MovieTypeBase):
|
|||||||
media = db.get('id', media_id)
|
media = db.get('id', media_id)
|
||||||
|
|
||||||
if not media.get('info'):
|
if not media.get('info'):
|
||||||
media = self.updateInfo(media_id)
|
media = self.update(media_id)
|
||||||
dates = media.get('info', {}).get('release_date')
|
dates = media.get('info', {}).get('release_date')
|
||||||
else:
|
else:
|
||||||
dates = media.get('info').get('release_date')
|
dates = media.get('info').get('release_date')
|
||||||
|
|||||||
@@ -373,6 +373,7 @@ var MovieList = new Class({
|
|||||||
(e).preventDefault();
|
(e).preventDefault();
|
||||||
this.set('text', 'Deleting..');
|
this.set('text', 'Deleting..');
|
||||||
Api.request('media.delete', {
|
Api.request('media.delete', {
|
||||||
|
'method': 'post',
|
||||||
'data': {
|
'data': {
|
||||||
'id': ids.join(','),
|
'id': ids.join(','),
|
||||||
'delete_from': self.options.identifier
|
'delete_from': self.options.identifier
|
||||||
@@ -413,6 +414,7 @@ var MovieList = new Class({
|
|||||||
var ids = self.getSelectedMovies();
|
var ids = self.getSelectedMovies();
|
||||||
|
|
||||||
Api.request('movie.edit', {
|
Api.request('movie.edit', {
|
||||||
|
'method': 'post',
|
||||||
'data': {
|
'data': {
|
||||||
'id': ids.join(','),
|
'id': ids.join(','),
|
||||||
'profile_id': self.mass_edit_quality.get('value')
|
'profile_id': self.mass_edit_quality.get('value')
|
||||||
@@ -426,6 +428,7 @@ var MovieList = new Class({
|
|||||||
var ids = self.getSelectedMovies();
|
var ids = self.getSelectedMovies();
|
||||||
|
|
||||||
Api.request('media.refresh', {
|
Api.request('media.refresh', {
|
||||||
|
'method': 'post',
|
||||||
'data': {
|
'data': {
|
||||||
'id': ids.join(',')
|
'id': ids.join(',')
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -2,6 +2,7 @@ Page.Manage = new Class({
|
|||||||
|
|
||||||
Extends: PageBase,
|
Extends: PageBase,
|
||||||
|
|
||||||
|
order: 20,
|
||||||
name: 'manage',
|
name: 'manage',
|
||||||
title: 'Do stuff to your existing movies!',
|
title: 'Do stuff to your existing movies!',
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ MA.IMDB = new Class({
|
|||||||
create: function(){
|
create: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.id = self.movie.get('imdb') || self.movie.get('identifier');
|
self.id = self.movie.getIdentifier ? self.movie.getIdentifier() : self.get('imdb');
|
||||||
|
|
||||||
self.el = new Element('a.imdb', {
|
self.el = new Element('a.imdb', {
|
||||||
'title': 'Go to the IMDB page of ' + self.getTitle(),
|
'title': 'Go to the IMDB page of ' + self.getTitle(),
|
||||||
@@ -115,8 +115,15 @@ MA.Release = new Class({
|
|||||||
|
|
||||||
self.releases = null;
|
self.releases = null;
|
||||||
if(self.options_container){
|
if(self.options_container){
|
||||||
self.options_container.destroy();
|
// Releases are currently displayed
|
||||||
self.options_container = null;
|
if(self.options_container.isDisplayed()){
|
||||||
|
self.options_container.destroy();
|
||||||
|
self.createReleases();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.options_container.destroy();
|
||||||
|
self.options_container = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -131,10 +138,10 @@ MA.Release = new Class({
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createReleases: function(){
|
createReleases: function(refresh){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if(!self.options_container){
|
if(!self.options_container || refresh){
|
||||||
self.options_container = new Element('div.options').grab(
|
self.options_container = new Element('div.options').grab(
|
||||||
self.release_container = new Element('div.releases.table')
|
self.release_container = new Element('div.releases.table')
|
||||||
);
|
);
|
||||||
@@ -252,7 +259,7 @@ MA.Release = new Class({
|
|||||||
|
|
||||||
self.trynext_container.adopt(
|
self.trynext_container.adopt(
|
||||||
new Element('span.or', {
|
new Element('span.or', {
|
||||||
'text': 'This movie is snatched, if anything went wrong, download'
|
'text': 'If anything went wrong, download'
|
||||||
}),
|
}),
|
||||||
lr ? new Element('a.button.orange', {
|
lr ? new Element('a.button.orange', {
|
||||||
'text': 'the same release again',
|
'text': 'the same release again',
|
||||||
@@ -302,7 +309,7 @@ MA.Release = new Class({
|
|||||||
self.movie.data.releases.each(function(release){
|
self.movie.data.releases.each(function(release){
|
||||||
if(has_available && has_snatched) return;
|
if(has_available && has_snatched) return;
|
||||||
|
|
||||||
if(['snatched', 'downloaded', 'seeding'].contains(release.status))
|
if(['snatched', 'downloaded', 'seeding', 'done'].contains(release.status))
|
||||||
has_snatched = true;
|
has_snatched = true;
|
||||||
|
|
||||||
if(['available'].contains(release.status))
|
if(['available'].contains(release.status))
|
||||||
@@ -684,7 +691,7 @@ MA.Readd = new Class({
|
|||||||
var movie_done = self.movie.data.status == 'done';
|
var movie_done = self.movie.data.status == 'done';
|
||||||
if(self.movie.data.releases && !movie_done)
|
if(self.movie.data.releases && !movie_done)
|
||||||
var snatched = self.movie.data.releases.filter(function(release){
|
var snatched = self.movie.data.releases.filter(function(release){
|
||||||
return release.status && (release.status == 'snatched' || release.status == 'downloaded' || release.status == 'done');
|
return release.status && (release.status == 'snatched' || release.status == 'seeding' || release.status == 'downloaded' || release.status == 'done');
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
if(movie_done || snatched && snatched > 0)
|
if(movie_done || snatched && snatched > 0)
|
||||||
@@ -703,7 +710,7 @@ MA.Readd = new Class({
|
|||||||
|
|
||||||
Api.request('movie.add', {
|
Api.request('movie.add', {
|
||||||
'data': {
|
'data': {
|
||||||
'identifier': self.movie.get('identifier'),
|
'identifier': self.movie.getIdentifier(),
|
||||||
'ignore_previous': 1
|
'ignore_previous': 1
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
.movies.thumbs_list .movie {
|
.movies.thumbs_list .movie {
|
||||||
width: 16.66667%;
|
width: 16.66667%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
min-height: 200px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -133,6 +134,7 @@
|
|||||||
@media all and (max-width: 800px) {
|
@media all and (max-width: 800px) {
|
||||||
.movies.thumbs_list .movie {
|
.movies.thumbs_list .movie {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
|
min-height: 100px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,18 +357,40 @@
|
|||||||
top: 30px;
|
top: 30px;
|
||||||
clear: both;
|
clear: both;
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
overflow: hidden;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
.movies .data:hover .description {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
.movies.list_list .movie:not(.details_view) .info .description,
|
.movies.list_list .movie:not(.details_view) .info .description,
|
||||||
.movies.mass_edit_list .info .description,
|
.movies.mass_edit_list .info .description,
|
||||||
.movies.thumbs_list .info .description {
|
.movies.thumbs_list .info .description {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movies .data .eta {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.details_list .data .eta {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
display: block;
|
||||||
|
min-height: 20px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: italic;
|
||||||
|
opacity: .8;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.details_list .movie:hover .data .eta {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movies.thumbs_list .data .eta {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
.movies .data .quality {
|
.movies .data .quality {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 2px;
|
bottom: 2px;
|
||||||
|
|||||||
@@ -54,13 +54,21 @@ var Movie = new Class({
|
|||||||
// Reload when releases have updated
|
// Reload when releases have updated
|
||||||
self.global_events['release.update_status'] = function(notification){
|
self.global_events['release.update_status'] = function(notification){
|
||||||
var data = notification.data;
|
var data = notification.data;
|
||||||
if(data && self.data._id == data.movie_id){
|
if(data && self.data._id == data.media_id){
|
||||||
|
|
||||||
if(!self.data.releases)
|
if(!self.data.releases)
|
||||||
self.data.releases = [];
|
self.data.releases = [];
|
||||||
|
|
||||||
self.data.releases.push({'quality': data.quality, 'status': data.status});
|
var updated = false;
|
||||||
self.updateReleases();
|
self.data.releases.each(function(release){
|
||||||
|
if(release._id == data._id){
|
||||||
|
release['status'] = data.status;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(updated)
|
||||||
|
self.updateReleases();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -136,6 +144,21 @@ var Movie = new Class({
|
|||||||
|
|
||||||
self.el.addClass('status_'+self.get('status'));
|
self.el.addClass('status_'+self.get('status'));
|
||||||
|
|
||||||
|
var eta = null,
|
||||||
|
eta_date = null,
|
||||||
|
now = Math.round(+new Date()/1000);
|
||||||
|
|
||||||
|
if(self.data.info.release_date)
|
||||||
|
[self.data.info.release_date.dvd, self.data.info.release_date.theater].each(function(timestamp){
|
||||||
|
if (timestamp > 0 && (eta == null || Math.abs(timestamp - now) < Math.abs(eta - now)))
|
||||||
|
eta = timestamp;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(eta){
|
||||||
|
eta_date = new Date(eta * 1000);
|
||||||
|
eta_date = eta_date.toLocaleString('en-us', { month: "long" }) + ' ' + eta_date.getFullYear();
|
||||||
|
}
|
||||||
|
|
||||||
self.el.adopt(
|
self.el.adopt(
|
||||||
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
self.select_checkbox = new Element('input[type=checkbox].inlay', {
|
||||||
'events': {
|
'events': {
|
||||||
@@ -144,7 +167,7 @@ var Movie = new Class({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
self.thumbnail = (self.data.files && self.data.files.image_poster) ? new Element('img', {
|
self.thumbnail = (self.data.files && self.data.files.image_poster && self.data.files.image_poster.length > 0) ? new Element('img', {
|
||||||
'class': 'type_image poster',
|
'class': 'type_image poster',
|
||||||
'src': Api.createUrl('file.cache') + self.data.files.image_poster[0].split(Api.getOption('path_sep')).pop()
|
'src': Api.createUrl('file.cache') + self.data.files.image_poster[0].split(Api.getOption('path_sep')).pop()
|
||||||
}): null,
|
}): null,
|
||||||
@@ -158,9 +181,13 @@ var Movie = new Class({
|
|||||||
'text': self.data.info.year || 'n/a'
|
'text': self.data.info.year || 'n/a'
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
self.description = new Element('div.description', {
|
self.description = new Element('div.description.tiny_scroll', {
|
||||||
'text': self.data.info.plot
|
'text': self.data.info.plot
|
||||||
}),
|
}),
|
||||||
|
self.eta = eta_date && (now+8035200 > eta) ? new Element('div.eta', {
|
||||||
|
'text': eta_date,
|
||||||
|
'title': 'ETA'
|
||||||
|
}) : null,
|
||||||
self.quality = new Element('div.quality', {
|
self.quality = new Element('div.quality', {
|
||||||
'events': {
|
'events': {
|
||||||
'click': function(e){
|
'click': function(e){
|
||||||
@@ -250,6 +277,10 @@ var Movie = new Class({
|
|||||||
getUnprefixedTitle: function(t){
|
getUnprefixedTitle: function(t){
|
||||||
if(t.substr(0, 4).toLowerCase() == 'the ')
|
if(t.substr(0, 4).toLowerCase() == 'the ')
|
||||||
t = t.substr(4) + ', The';
|
t = t.substr(4) + ', The';
|
||||||
|
else if(t.substr(0, 3).toLowerCase() == 'an ')
|
||||||
|
t = t.substr(3) + ', An';
|
||||||
|
else if(t.substr(0, 2).toLowerCase() == 'a ')
|
||||||
|
t = t.substr(2) + ', A';
|
||||||
return t;
|
return t;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -296,6 +327,17 @@ var Movie = new Class({
|
|||||||
self.el.removeClass(self.view+'_view')
|
self.el.removeClass(self.view+'_view')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getIdentifier: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return self.get('identifiers').imdb;
|
||||||
|
}
|
||||||
|
catch (e){ }
|
||||||
|
|
||||||
|
return self.get('imdb');
|
||||||
|
},
|
||||||
|
|
||||||
get: function(attr){
|
get: function(attr){
|
||||||
return this.data[attr] || this.data.info[attr]
|
return this.data[attr] || this.data.info[attr]
|
||||||
},
|
},
|
||||||
|
|||||||
+1
@@ -2,6 +2,7 @@ Page.Wanted = new Class({
|
|||||||
|
|
||||||
Extends: PageBase,
|
Extends: PageBase,
|
||||||
|
|
||||||
|
order: 10,
|
||||||
name: 'wanted',
|
name: 'wanted',
|
||||||
title: 'Gimmy gimmy gimmy!',
|
title: 'Gimmy gimmy gimmy!',
|
||||||
folder_browser: null,
|
folder_browser: null,
|
||||||
@@ -22,11 +22,18 @@ config = [{
|
|||||||
'description': 'Maximum number of items displayed from each chart.',
|
'description': 'Maximum number of items displayed from each chart.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'update_interval',
|
'name': 'hide_wanted',
|
||||||
'default': 12,
|
'default': False,
|
||||||
'type': 'int',
|
'type': 'bool',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': '(hours)',
|
'description': 'Hide the chart movies that are already in your wanted list.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'hide_library',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool',
|
||||||
|
'advanced': True,
|
||||||
|
'description': 'Hide the chart movies that are already in your library.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from couchpotato import tryInt
|
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent,fireEvent
|
from couchpotato.core.event import addEvent,fireEvent
|
||||||
@@ -13,13 +12,14 @@ log = CPLog(__name__)
|
|||||||
class Charts(Plugin):
|
class Charts(Plugin):
|
||||||
|
|
||||||
update_in_progress = False
|
update_in_progress = False
|
||||||
|
update_interval = 72 # hours
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addApiView('charts.view', self.automationView)
|
addApiView('charts.view', self.automationView)
|
||||||
addEvent('app.load', self.setCrons)
|
addEvent('app.load', self.setCrons)
|
||||||
|
|
||||||
def setCrons(self):
|
def setCrons(self):
|
||||||
fireEvent('schedule.interval', 'charts.update_cache', self.updateViewCache, hours = self.conf('update_interval', default = 12))
|
fireEvent('schedule.interval', 'charts.update_cache', self.updateViewCache, hours = self.update_interval)
|
||||||
|
|
||||||
def automationView(self, force_update = False, **kwargs):
|
def automationView(self, force_update = False, **kwargs):
|
||||||
|
|
||||||
@@ -36,7 +36,6 @@ class Charts(Plugin):
|
|||||||
'charts': charts
|
'charts': charts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def updateViewCache(self):
|
def updateViewCache(self):
|
||||||
|
|
||||||
if self.update_in_progress:
|
if self.update_in_progress:
|
||||||
@@ -46,10 +45,14 @@ class Charts(Plugin):
|
|||||||
if catched_charts:
|
if catched_charts:
|
||||||
return catched_charts
|
return catched_charts
|
||||||
|
|
||||||
|
charts = []
|
||||||
try:
|
try:
|
||||||
self.update_in_progress = True
|
self.update_in_progress = True
|
||||||
charts = fireEvent('automation.get_chart_list', merge = True)
|
charts = fireEvent('automation.get_chart_list', merge = True)
|
||||||
self.setCache('charts_cached', charts, timeout = 7200 * tryInt(self.conf('update_interval', default = 12)))
|
for chart in charts:
|
||||||
|
chart['hide_wanted'] = self.conf('hide_wanted')
|
||||||
|
chart['hide_library'] = self.conf('hide_library')
|
||||||
|
self.setCache('charts_cached', charts, timeout = self.update_interval * 3600)
|
||||||
except:
|
except:
|
||||||
log.error('Failed refreshing charts')
|
log.error('Failed refreshing charts')
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,21 @@
|
|||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.charts > h2 {
|
.charts > h2 {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.charts .chart {
|
.charts .chart {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
max-height: 510px;
|
||||||
|
scrollbar-base-color: #4e5969;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts .chart .media_result.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.charts .refresh {
|
.charts .refresh {
|
||||||
clear:both;
|
clear:both;
|
||||||
@@ -25,30 +31,30 @@
|
|||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.charts .refresh a {
|
.charts .refresh a {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: none;
|
display: none;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: -40px;
|
top: -40px;
|
||||||
opacity: .7;
|
opacity: .7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.charts .refresh a:hover {
|
.charts .refresh a:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.charts p.no_charts_enabled {
|
.charts p.no_charts_enabled {
|
||||||
padding: 0.7em 1em;
|
padding: 0.7em 1em;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.charts .chart h3 a {
|
.charts .chart h3 a {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.charts .chart .media_result {
|
.charts .chart .media_result {
|
||||||
@@ -137,7 +143,6 @@
|
|||||||
padding: 0 3px 10px 0;
|
padding: 0 3px 10px 0;
|
||||||
}
|
}
|
||||||
.charts .chart .media_result .data:before {
|
.charts .chart .media_result .data:before {
|
||||||
bottom: 0;
|
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ var Charts = new Class({
|
|||||||
|
|
||||||
Implements: [Options, Events],
|
Implements: [Options, Events],
|
||||||
|
|
||||||
|
shown_once: false,
|
||||||
|
|
||||||
initialize: function(options){
|
initialize: function(options){
|
||||||
var self = this;
|
var self = this;
|
||||||
self.setOptions(options);
|
self.setOptions(options);
|
||||||
@@ -22,9 +24,11 @@ var Charts = new Class({
|
|||||||
'events': {
|
'events': {
|
||||||
'click': function(e) {
|
'click': function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
self.el.getChildren('div.chart').destroy();
|
|
||||||
|
self.el.getElements('.chart').destroy();
|
||||||
self.el_refreshing_text.show();
|
self.el_refreshing_text.show();
|
||||||
self.el_refresh_link.hide();
|
self.el_refresh_link.hide();
|
||||||
|
|
||||||
self.api_request = Api.request('charts.view', {
|
self.api_request = Api.request('charts.view', {
|
||||||
'data': { 'force_update': 1 },
|
'data': { 'force_update': 1 },
|
||||||
'onComplete': self.fill.bind(self)
|
'onComplete': self.fill.bind(self)
|
||||||
@@ -38,17 +42,13 @@ var Charts = new Class({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if( Cookie.read('suggestions_charts_menu_selected') === 'charts')
|
if( Cookie.read('suggestions_charts_menu_selected') === 'charts'){
|
||||||
self.el.show();
|
self.show();
|
||||||
|
self.fireEvent.delay(0, self, 'created');
|
||||||
|
}
|
||||||
else
|
else
|
||||||
self.el.hide();
|
self.el.hide();
|
||||||
|
|
||||||
self.api_request = Api.request('charts.view', {
|
|
||||||
'onComplete': self.fill.bind(self)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.fireEvent.delay(0, self, 'created');
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fill: function(json){
|
fill: function(json){
|
||||||
@@ -72,7 +72,7 @@ var Charts = new Class({
|
|||||||
|
|
||||||
Object.each(json.charts, function(chart){
|
Object.each(json.charts, function(chart){
|
||||||
|
|
||||||
var c = new Element('div.chart').grab(
|
var c = new Element('div.chart.tiny_scroll').grab(
|
||||||
new Element('h3').grab( new Element('a', {
|
new Element('h3').grab( new Element('a', {
|
||||||
'text': chart.name,
|
'text': chart.name,
|
||||||
'href': chart.url
|
'href': chart.url
|
||||||
@@ -89,7 +89,7 @@ var Charts = new Class({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var in_database_class = movie.in_wanted ? 'chart_in_wanted' : (movie.in_library ? 'chart_in_library' : ''),
|
var in_database_class = (chart.hide_wanted && movie.in_wanted) ? 'hidden' : (movie.in_wanted ? 'chart_in_wanted' : ((chart.hide_library && movie.in_library) ? 'hidden': (movie.in_library ? 'chart_in_library' : ''))),
|
||||||
in_database_title = movie.in_wanted ? 'Movie in wanted list' : (movie.in_library ? 'Movie in library' : '');
|
in_database_title = movie.in_wanted ? 'Movie in wanted list' : (movie.in_library ? 'Movie in library' : '');
|
||||||
|
|
||||||
m.el
|
m.el
|
||||||
@@ -155,6 +155,24 @@ var Charts = new Class({
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
show: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.el.show();
|
||||||
|
|
||||||
|
if(!self.shown_once){
|
||||||
|
self.api_request = Api.request('charts.view', {
|
||||||
|
'onComplete': self.fill.bind(self)
|
||||||
|
});
|
||||||
|
|
||||||
|
self.shown_once = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function(){
|
||||||
|
this.el.hide();
|
||||||
|
},
|
||||||
|
|
||||||
afterAdded: function(m){
|
afterAdded: function(m){
|
||||||
|
|
||||||
$(m).getElement('div.chart_number')
|
$(m).getElement('div.chart_number')
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
|
from couchpotato.core.helpers.variable import getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media._base.library.base import LibraryBase
|
from couchpotato.core.media._base.library.base import LibraryBase
|
||||||
|
|
||||||
@@ -17,7 +18,9 @@ class MovieLibraryPlugin(LibraryBase):
|
|||||||
if media.get('type') != 'movie':
|
if media.get('type') != 'movie':
|
||||||
return
|
return
|
||||||
|
|
||||||
|
default_title = getTitle(media)
|
||||||
titles = media['info'].get('titles', [])
|
titles = media['info'].get('titles', [])
|
||||||
|
titles.insert(0, default_title)
|
||||||
|
|
||||||
# Add year identifier to titles
|
# Add year identifier to titles
|
||||||
if include_year:
|
if include_year:
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
|
import traceback
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from couchpotato import fireEvent
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
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
|
||||||
from couchpotato.core.media.movie.providers.automation.base import Automation
|
from couchpotato.core.media.movie.providers.automation.base import Automation
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
|
|
||||||
autoload = 'Bluray'
|
autoload = 'Bluray'
|
||||||
@@ -33,27 +37,49 @@ class Bluray(Automation, RSS):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Stop if the release year is before the minimal year
|
# Stop if the release year is before the minimal year
|
||||||
page_year = soup.body.find_all('center')[3].table.tr.find_all('td', recursive = False)[3].h3.get_text().split(', ')[1]
|
brk = False
|
||||||
if tryInt(page_year) < self.getMinimal('year'):
|
h3s = soup.body.find_all('h3')
|
||||||
|
for h3 in h3s:
|
||||||
|
if h3.parent.name != 'a':
|
||||||
|
|
||||||
|
try:
|
||||||
|
page_year = tryInt(h3.get_text()[-4:])
|
||||||
|
if page_year > 0 and page_year < self.getMinimal('year'):
|
||||||
|
brk = True
|
||||||
|
except:
|
||||||
|
log.error('Failed determining page year: %s', traceback.format_exc())
|
||||||
|
brk = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if brk:
|
||||||
break
|
break
|
||||||
|
|
||||||
for table in soup.body.find_all('center')[3].table.tr.find_all('td', recursive = False)[3].find_all('table')[1:20]:
|
for h3 in h3s:
|
||||||
name = table.h3.get_text().lower().split('blu-ray')[0].strip()
|
try:
|
||||||
year = table.small.get_text().split('|')[1].strip()
|
if h3.parent.name == 'a':
|
||||||
|
name = h3.get_text().lower().split('blu-ray')[0].strip()
|
||||||
|
|
||||||
if not name.find('/') == -1: # make sure it is not a double movie release
|
if not name.find('/') == -1: # make sure it is not a double movie release
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if tryInt(year) < self.getMinimal('year'):
|
if not h3.parent.parent.small: # ignore non-movie tables
|
||||||
continue
|
continue
|
||||||
|
|
||||||
imdb = self.search(name, year)
|
year = h3.parent.parent.small.get_text().split('|')[1].strip()
|
||||||
|
|
||||||
if imdb:
|
if tryInt(year) < self.getMinimal('year'):
|
||||||
if self.isMinimalMovie(imdb):
|
continue
|
||||||
movies.append(imdb['imdb'])
|
|
||||||
|
imdb = self.search(name, year)
|
||||||
|
|
||||||
|
if imdb:
|
||||||
|
if self.isMinimalMovie(imdb):
|
||||||
|
movies.append(imdb['imdb'])
|
||||||
|
except:
|
||||||
|
log.debug('Error parsing movie html: %s', traceback.format_exc())
|
||||||
|
break
|
||||||
except:
|
except:
|
||||||
log.debug('Error loading page: %s', page)
|
log.debug('Error loading page %s: %s', (page, traceback.format_exc()))
|
||||||
break
|
break
|
||||||
|
|
||||||
self.conf('backlog', value = False)
|
self.conf('backlog', value = False)
|
||||||
@@ -82,6 +108,7 @@ class Bluray(Automation, RSS):
|
|||||||
def getChartList(self):
|
def getChartList(self):
|
||||||
# Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id)
|
# Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id)
|
||||||
movie_list = {'name': 'Blu-ray.com - New Releases', 'url': self.display_url, 'order': self.chart_order, 'list': []}
|
movie_list = {'name': 'Blu-ray.com - New Releases', 'url': self.display_url, 'order': self.chart_order, 'list': []}
|
||||||
|
movie_ids = []
|
||||||
max_items = int(self.conf('max_items', section='charts', default=5))
|
max_items = int(self.conf('max_items', section='charts', default=5))
|
||||||
rss_movies = self.getRSSData(self.rss_url)
|
rss_movies = self.getRSSData(self.rss_url)
|
||||||
|
|
||||||
@@ -95,6 +122,15 @@ class Bluray(Automation, RSS):
|
|||||||
movie = self.search(name, year)
|
movie = self.search(name, year)
|
||||||
|
|
||||||
if movie:
|
if movie:
|
||||||
|
|
||||||
|
if movie.get('imdb') in movie_ids:
|
||||||
|
continue
|
||||||
|
|
||||||
|
is_movie = fireEvent('movie.is_movie', identifier = movie.get('imdb'), single = True)
|
||||||
|
if not is_movie:
|
||||||
|
continue
|
||||||
|
|
||||||
|
movie_ids.append(movie.get('imdb'))
|
||||||
movie_list['list'].append( movie )
|
movie_list['list'].append( movie )
|
||||||
if len(movie_list['list']) >= max_items:
|
if len(movie_list['list']) >= max_items:
|
||||||
break
|
break
|
||||||
@@ -123,7 +159,7 @@ config = [{
|
|||||||
{
|
{
|
||||||
'name': 'backlog',
|
'name': 'backlog',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': 'Parses the history until the minimum movie year is reached. (Will be disabled once it has completed)',
|
'description': ('Parses the history until the minimum movie year is reached. (Takes a while)', 'Will be disabled once it has completed'),
|
||||||
'default': False,
|
'default': False,
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import re
|
|||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from couchpotato import fireEvent
|
from couchpotato import fireEvent
|
||||||
|
from couchpotato.core.helpers.encoding import ss
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import getImdb, splitString, tryInt
|
from couchpotato.core.helpers.variable import getImdb, splitString, tryInt
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
@@ -28,6 +29,39 @@ class IMDBBase(Automation, RSS):
|
|||||||
def getInfo(self, imdb_id):
|
def getInfo(self, imdb_id):
|
||||||
return fireEvent('movie.info', identifier = imdb_id, extended = False, merge = True)
|
return fireEvent('movie.info', identifier = imdb_id, extended = False, merge = True)
|
||||||
|
|
||||||
|
def getFromURL(self, url):
|
||||||
|
log.debug('Getting IMDBs from: %s', url)
|
||||||
|
html = self.getHTMLData(url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
split = splitString(html, split_on = "<div class=\"list compact\">")[1]
|
||||||
|
html = splitString(split, split_on = "<div class=\"pages\">")[0]
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
split = splitString(html, split_on = "<div id=\"main\">")
|
||||||
|
|
||||||
|
if len(split) < 2:
|
||||||
|
log.error('Failed parsing IMDB page "%s", unexpected html.', url)
|
||||||
|
return []
|
||||||
|
|
||||||
|
html = BeautifulSoup(split[1])
|
||||||
|
for x in ['list compact', 'lister', 'list detail sub-list']:
|
||||||
|
html2 = html.find('div', attrs = {
|
||||||
|
'class': x
|
||||||
|
})
|
||||||
|
|
||||||
|
if html2:
|
||||||
|
html = html2.contents
|
||||||
|
html = ''.join([str(x) for x in html])
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
log.error('Failed parsing IMDB page "%s": %s', (url, traceback.format_exc()))
|
||||||
|
|
||||||
|
html = ss(html)
|
||||||
|
imdbs = getImdb(html, multiple = True) if html else []
|
||||||
|
|
||||||
|
return imdbs
|
||||||
|
|
||||||
|
|
||||||
class IMDBWatchlist(IMDBBase):
|
class IMDBWatchlist(IMDBBase):
|
||||||
|
|
||||||
@@ -65,16 +99,7 @@ class IMDBWatchlist(IMDBBase):
|
|||||||
try:
|
try:
|
||||||
|
|
||||||
w_url = '%s&start=%s' % (watchlist_url, start)
|
w_url = '%s&start=%s' % (watchlist_url, start)
|
||||||
log.debug('Started IMDB watchlists: %s', w_url)
|
imdbs = self.getFromURL(w_url)
|
||||||
html = self.getHTMLData(w_url)
|
|
||||||
|
|
||||||
try:
|
|
||||||
split = splitString(html, split_on="<div class=\"list compact\">")[1]
|
|
||||||
html = splitString(split, split_on="<div class=\"pages\">")[0]
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
imdbs = getImdb(html, multiple = True) if html else []
|
|
||||||
|
|
||||||
for imdb in imdbs:
|
for imdb in imdbs:
|
||||||
if imdb not in movies:
|
if imdb not in movies:
|
||||||
@@ -85,13 +110,14 @@ class IMDBWatchlist(IMDBBase):
|
|||||||
|
|
||||||
log.debug('Found %s movies on %s', (len(imdbs), w_url))
|
log.debug('Found %s movies on %s', (len(imdbs), w_url))
|
||||||
|
|
||||||
if len(imdbs) < 250:
|
if len(imdbs) < 225:
|
||||||
break
|
break
|
||||||
|
|
||||||
start += 250
|
start = len(movies)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
log.error('Failed loading IMDB watchlist: %s %s', (watchlist_url, traceback.format_exc()))
|
log.error('Failed loading IMDB watchlist: %s %s', (watchlist_url, traceback.format_exc()))
|
||||||
|
break
|
||||||
|
|
||||||
return movies
|
return movies
|
||||||
|
|
||||||
@@ -100,95 +126,88 @@ class IMDBAutomation(IMDBBase):
|
|||||||
|
|
||||||
enabled_option = 'automation_providers_enabled'
|
enabled_option = 'automation_providers_enabled'
|
||||||
|
|
||||||
chart_urls = {
|
charts = {
|
||||||
'theater': 'http://www.imdb.com/movies-in-theaters/',
|
'theater': {
|
||||||
'top250': 'http://www.imdb.com/chart/top',
|
'order': 1,
|
||||||
'boxoffice': 'http://www.imdb.com/chart/',
|
'name': 'IMDB - Movies in Theaters',
|
||||||
|
'url': 'http://www.imdb.com/movies-in-theaters/',
|
||||||
|
},
|
||||||
|
'boxoffice': {
|
||||||
|
'order': 2,
|
||||||
|
'name': 'IMDB - Box Office',
|
||||||
|
'url': 'http://www.imdb.com/boxoffice/',
|
||||||
|
},
|
||||||
|
'rentals': {
|
||||||
|
'order': 3,
|
||||||
|
'name': 'IMDB - Top DVD rentals',
|
||||||
|
'url': 'http://www.imdb.com/boxoffice/rentals',
|
||||||
|
'type': 'json',
|
||||||
|
},
|
||||||
|
'top250': {
|
||||||
|
'order': 4,
|
||||||
|
'name': 'IMDB - Top 250 Movies',
|
||||||
|
'url': 'http://www.imdb.com/chart/top',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
chart_names = {
|
|
||||||
'theater': 'IMDB - Movies in Theaters',
|
|
||||||
'top250': 'IMDB - Top 250 Movies',
|
|
||||||
'boxoffice': 'IMDB - Box Office',
|
|
||||||
}
|
|
||||||
chart_order = {
|
|
||||||
'theater': 2,
|
|
||||||
'top250': 4,
|
|
||||||
'boxoffice': 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
first_table = ['boxoffice']
|
|
||||||
|
|
||||||
def getIMDBids(self):
|
def getIMDBids(self):
|
||||||
|
|
||||||
movies = []
|
movies = []
|
||||||
|
|
||||||
for url in self.chart_urls:
|
for name in self.charts:
|
||||||
if self.conf('automation_charts_%s' % url):
|
chart = self.charts[name]
|
||||||
data = self.getHTMLData(self.chart_urls[url])
|
url = chart.get('url')
|
||||||
if data:
|
|
||||||
html = BeautifulSoup(data)
|
|
||||||
|
|
||||||
try:
|
if self.conf('automation_charts_%s' % name):
|
||||||
result_div = html.find('div', attrs = {'id': 'main'})
|
imdb_ids = self.getFromURL(url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if url in self.first_table:
|
for imdb_id in imdb_ids:
|
||||||
table = result_div.find('table')
|
info = self.getInfo(imdb_id)
|
||||||
result_div = table if table else result_div
|
if info and self.isMinimalMovie(info):
|
||||||
except:
|
movies.append(imdb_id)
|
||||||
pass
|
|
||||||
|
|
||||||
imdb_ids = getImdb(str(result_div), multiple = True)
|
if self.shuttingDown():
|
||||||
|
break
|
||||||
|
|
||||||
for imdb_id in imdb_ids:
|
except:
|
||||||
info = self.getInfo(imdb_id)
|
log.error('Failed loading IMDB chart results from %s: %s', (url, traceback.format_exc()))
|
||||||
if info and self.isMinimalMovie(info):
|
|
||||||
movies.append(imdb_id)
|
|
||||||
|
|
||||||
if self.shuttingDown():
|
|
||||||
break
|
|
||||||
|
|
||||||
except:
|
|
||||||
log.error('Failed loading IMDB chart results from %s: %s', (url, traceback.format_exc()))
|
|
||||||
|
|
||||||
return movies
|
return movies
|
||||||
|
|
||||||
|
|
||||||
def getChartList(self):
|
def getChartList(self):
|
||||||
|
|
||||||
# Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id)
|
# Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id)
|
||||||
movie_lists = []
|
movie_lists = []
|
||||||
max_items = int(self.conf('max_items', section='charts', default=5))
|
max_items = int(self.conf('max_items', section = 'charts', default=5))
|
||||||
|
|
||||||
for url in self.chart_urls:
|
for name in self.charts:
|
||||||
if self.conf('chart_display_%s' % url):
|
chart = self.charts[name].copy()
|
||||||
movie_list = {'name': self.chart_names[url], 'url': self.chart_urls[url], 'order': self.chart_order[url], 'list': []}
|
url = chart.get('url')
|
||||||
data = self.getHTMLData(self.chart_urls[url])
|
|
||||||
if data:
|
|
||||||
html = BeautifulSoup(data)
|
|
||||||
|
|
||||||
try:
|
if self.conf('chart_display_%s' % name):
|
||||||
result_div = html.find('div', attrs = {'id': 'main'})
|
|
||||||
|
|
||||||
try:
|
chart['list'] = []
|
||||||
if url in self.first_table:
|
|
||||||
table = result_div.find('table')
|
|
||||||
result_div = table if table else result_div
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
imdb_ids = getImdb(str(result_div), multiple = True)
|
imdb_ids = self.getFromURL(url)
|
||||||
|
|
||||||
for imdb_id in imdb_ids[0:max_items]:
|
try:
|
||||||
info = self.getInfo(imdb_id)
|
for imdb_id in imdb_ids[0:max_items]:
|
||||||
movie_list['list'].append(info)
|
|
||||||
|
|
||||||
if self.shuttingDown():
|
is_movie = fireEvent('movie.is_movie', identifier = imdb_id, single = True)
|
||||||
break
|
if not is_movie:
|
||||||
except:
|
continue
|
||||||
log.error('Failed loading IMDB chart results from %s: %s', (url, traceback.format_exc()))
|
|
||||||
|
|
||||||
if movie_list['list']:
|
info = self.getInfo(imdb_id)
|
||||||
movie_lists.append(movie_list)
|
chart['list'].append(info)
|
||||||
|
|
||||||
|
if self.shuttingDown():
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
log.error('Failed loading IMDB chart results from %s: %s', (url, traceback.format_exc()))
|
||||||
|
|
||||||
|
if chart['list']:
|
||||||
|
movie_lists.append(chart)
|
||||||
|
|
||||||
|
|
||||||
return movie_lists
|
return movie_lists
|
||||||
@@ -240,12 +259,19 @@ config = [{
|
|||||||
'description': 'New Movies <a href="http://www.imdb.com/movies-in-theaters/">In-Theaters</a> chart',
|
'description': 'New Movies <a href="http://www.imdb.com/movies-in-theaters/">In-Theaters</a> chart',
|
||||||
'default': True,
|
'default': True,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'automation_charts_rentals',
|
||||||
|
'type': 'bool',
|
||||||
|
'label': 'DVD Rentals',
|
||||||
|
'description': 'Top DVD <a href="http://www.imdb.com/boxoffice/rentals">rentals</a> chart',
|
||||||
|
'default': True,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'automation_charts_top250',
|
'name': 'automation_charts_top250',
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
'label': 'TOP 250',
|
'label': 'TOP 250',
|
||||||
'description': 'IMDB <a href="http://www.imdb.com/chart/top/">TOP 250</a> chart',
|
'description': 'IMDB <a href="http://www.imdb.com/chart/top/">TOP 250</a> chart',
|
||||||
'default': True,
|
'default': False,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'automation_charts_boxoffice',
|
'name': 'automation_charts_boxoffice',
|
||||||
@@ -282,6 +308,13 @@ config = [{
|
|||||||
'description': 'IMDB <a href="http://www.imdb.com/chart/top/">TOP 250</a> chart',
|
'description': 'IMDB <a href="http://www.imdb.com/chart/top/">TOP 250</a> chart',
|
||||||
'default': False,
|
'default': False,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'chart_display_rentals',
|
||||||
|
'type': 'bool',
|
||||||
|
'label': 'DVD Rentals',
|
||||||
|
'description': 'Top DVD <a href="http://www.imdb.com/boxoffice/rentals">rentals</a> chart',
|
||||||
|
'default': True,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'chart_display_boxoffice',
|
'name': 'chart_display_boxoffice',
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
|
|||||||
@@ -21,11 +21,15 @@ class Moviemeter(Automation, RSS):
|
|||||||
|
|
||||||
for movie in rss_movies:
|
for movie in rss_movies:
|
||||||
|
|
||||||
name_year = fireEvent('scanner.name_year', self.getTextElement(movie, 'title'), single = True)
|
title = self.getTextElement(movie, 'title')
|
||||||
imdb = self.search(name_year.get('name'), name_year.get('year'))
|
name_year = fireEvent('scanner.name_year', title, single = True)
|
||||||
|
if name_year.get('name') and name_year.get('year'):
|
||||||
|
imdb = self.search(name_year.get('name'), name_year.get('year'))
|
||||||
|
|
||||||
if imdb and self.isMinimalMovie(imdb):
|
if imdb and self.isMinimalMovie(imdb):
|
||||||
movies.append(imdb['imdb'])
|
movies.append(imdb['imdb'])
|
||||||
|
else:
|
||||||
|
log.error('Failed getting name and year from: %s', title)
|
||||||
|
|
||||||
return movies
|
return movies
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
from couchpotato import fireEvent
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.media.movie.providers.automation.base import Automation
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
autoload = 'PopularMovies'
|
||||||
|
|
||||||
|
|
||||||
|
class PopularMovies(Automation):
|
||||||
|
|
||||||
|
interval = 1800
|
||||||
|
url = 'https://s3.amazonaws.com/popular-movies/movies.json'
|
||||||
|
|
||||||
|
def getIMDBids(self):
|
||||||
|
|
||||||
|
movies = []
|
||||||
|
retrieved_movies = self.getJsonData(self.url)
|
||||||
|
|
||||||
|
for movie in retrieved_movies.get('movies'):
|
||||||
|
imdb_id = movie.get('imdb_id')
|
||||||
|
info = fireEvent('movie.info', identifier = imdb_id, extended = False, merge = True)
|
||||||
|
if self.isMinimalMovie(info):
|
||||||
|
movies.append(imdb_id)
|
||||||
|
|
||||||
|
return movies
|
||||||
|
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'popularmovies',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'automation',
|
||||||
|
'list': 'automation_providers',
|
||||||
|
'name': 'popularmovies_automation',
|
||||||
|
'label': 'Popular Movies',
|
||||||
|
'description': 'Imports the <a href="http://movies.stevenlu.com/">top titles of movies that have been in theaters</a>. Script provided by <a href="https://github.com/sjlu/popular-movies">Steven Lu</a>',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'automation_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -26,7 +26,14 @@ class MovieResultModifier(Plugin):
|
|||||||
'backdrop': [],
|
'backdrop': [],
|
||||||
'poster_original': [],
|
'poster_original': [],
|
||||||
'backdrop_original': [],
|
'backdrop_original': [],
|
||||||
'actors': {}
|
'actors': {},
|
||||||
|
'landscape': [],
|
||||||
|
'logo': [],
|
||||||
|
'clear_art': [],
|
||||||
|
'disc_art': [],
|
||||||
|
'banner': [],
|
||||||
|
'extra_thumbs': [],
|
||||||
|
'extra_fanart': []
|
||||||
},
|
},
|
||||||
'runtime': 0,
|
'runtime': 0,
|
||||||
'plot': '',
|
'plot': '',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import base64
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode, ss
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media.movie.providers.base import MovieProvider
|
from couchpotato.core.media.movie.providers.base import MovieProvider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
@@ -29,7 +29,7 @@ class CouchPotatoApi(MovieProvider):
|
|||||||
api_version = 1
|
api_version = 1
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('movie.info', self.getInfo, priority = 1)
|
addEvent('movie.info', self.getInfo, priority = 2)
|
||||||
addEvent('movie.info.release_date', self.getReleaseDate)
|
addEvent('movie.info.release_date', self.getReleaseDate)
|
||||||
|
|
||||||
addEvent('info.search', self.search, priority = 1)
|
addEvent('info.search', self.search, priority = 1)
|
||||||
@@ -66,7 +66,7 @@ class CouchPotatoApi(MovieProvider):
|
|||||||
if not name:
|
if not name:
|
||||||
return
|
return
|
||||||
|
|
||||||
name_enc = base64.b64encode(name)
|
name_enc = base64.b64encode(ss(name))
|
||||||
return self.getJsonData(self.urls['validate'] % name_enc, headers = self.getRequestHeaders())
|
return self.getJsonData(self.urls['validate'] % name_enc, headers = self.getRequestHeaders())
|
||||||
|
|
||||||
def isMovie(self, identifier = None):
|
def isMovie(self, identifier = None):
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import traceback
|
||||||
|
|
||||||
|
from couchpotato import tryInt
|
||||||
|
from couchpotato.core.event import addEvent
|
||||||
|
from couchpotato.core.logger import CPLog
|
||||||
|
from couchpotato.core.media.movie.providers.base import MovieProvider
|
||||||
|
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
|
autoload = 'FanartTV'
|
||||||
|
|
||||||
|
|
||||||
|
class FanartTV(MovieProvider):
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
'api': 'http://webservice.fanart.tv/v3/movies/%s?api_key=b28b14e9be662e027cfbc7c3dd600405'
|
||||||
|
}
|
||||||
|
|
||||||
|
MAX_EXTRAFANART = 20
|
||||||
|
http_time_between_calls = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
addEvent('movie.info', self.getArt, priority = 1)
|
||||||
|
|
||||||
|
def getArt(self, identifier = None, extended = True, **kwargs):
|
||||||
|
|
||||||
|
if not identifier or not extended:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
images = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = self.urls['api'] % identifier
|
||||||
|
fanart_data = self.getJsonData(url)
|
||||||
|
|
||||||
|
if fanart_data:
|
||||||
|
log.debug('Found images for %s', fanart_data.get('name'))
|
||||||
|
images = self._parseMovie(fanart_data)
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.error('Failed getting extra art for %s: %s',
|
||||||
|
(identifier, traceback.format_exc()))
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'images': images
|
||||||
|
}
|
||||||
|
|
||||||
|
def _parseMovie(self, movie):
|
||||||
|
images = {
|
||||||
|
'landscape': self._getMultImages(movie.get('moviethumb', []), 1),
|
||||||
|
'logo': [],
|
||||||
|
'disc_art': self._getMultImages(self._trimDiscs(movie.get('moviedisc', [])), 1),
|
||||||
|
'clear_art': self._getMultImages(movie.get('hdmovieart', []), 1),
|
||||||
|
'banner': self._getMultImages(movie.get('moviebanner', []), 1),
|
||||||
|
'extra_fanart': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(images['clear_art']) == 0:
|
||||||
|
images['clear_art'] = self._getMultImages(movie.get('movieart', []), 1)
|
||||||
|
|
||||||
|
images['logo'] = self._getMultImages(movie.get('hdmovielogo', []), 1)
|
||||||
|
if len(images['logo']) == 0:
|
||||||
|
images['logo'] = self._getMultImages(movie.get('movielogo', []), 1)
|
||||||
|
|
||||||
|
fanarts = self._getMultImages(movie.get('moviebackground', []), self.MAX_EXTRAFANART + 1)
|
||||||
|
|
||||||
|
if fanarts:
|
||||||
|
images['backdrop_original'] = [fanarts[0]]
|
||||||
|
images['extra_fanart'] = fanarts[1:]
|
||||||
|
|
||||||
|
return images
|
||||||
|
|
||||||
|
def _trimDiscs(self, disc_images):
|
||||||
|
"""
|
||||||
|
Return a subset of discImages. Only bluray disc images will be returned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
trimmed = []
|
||||||
|
for disc in disc_images:
|
||||||
|
if disc.get('disc_type') == 'bluray':
|
||||||
|
trimmed.append(disc)
|
||||||
|
|
||||||
|
if len(trimmed) == 0:
|
||||||
|
return disc_images
|
||||||
|
|
||||||
|
return trimmed
|
||||||
|
|
||||||
|
def _getImage(self, images):
|
||||||
|
image_url = None
|
||||||
|
highscore = -1
|
||||||
|
for image in images:
|
||||||
|
if tryInt(image.get('likes')) > highscore:
|
||||||
|
highscore = tryInt(image.get('likes'))
|
||||||
|
image_url = image.get('url') or image.get('href')
|
||||||
|
|
||||||
|
return image_url
|
||||||
|
|
||||||
|
def _getMultImages(self, images, n):
|
||||||
|
"""
|
||||||
|
Chooses the best n images and returns them as a list.
|
||||||
|
If n<0, all images will be returned.
|
||||||
|
"""
|
||||||
|
image_urls = []
|
||||||
|
pool = []
|
||||||
|
for image in images:
|
||||||
|
if image.get('lang') == 'en':
|
||||||
|
pool.append(image)
|
||||||
|
orig_pool_size = len(pool)
|
||||||
|
|
||||||
|
while len(pool) > 0 and (n < 0 or orig_pool_size - len(pool) < n):
|
||||||
|
best = None
|
||||||
|
highscore = -1
|
||||||
|
for image in pool:
|
||||||
|
if tryInt(image.get('likes')) > highscore:
|
||||||
|
highscore = tryInt(image.get('likes'))
|
||||||
|
best = image
|
||||||
|
url = best.get('url') or best.get('href')
|
||||||
|
if url:
|
||||||
|
image_urls.append(url)
|
||||||
|
pool.remove(best)
|
||||||
|
|
||||||
|
return image_urls
|
||||||
|
|
||||||
|
def isDisabled(self):
|
||||||
|
if self.conf('api_key') == '':
|
||||||
|
log.error('No API key provided.')
|
||||||
|
return True
|
||||||
|
return False
|
||||||
@@ -13,9 +13,10 @@ autoload = 'TheMovieDb'
|
|||||||
|
|
||||||
|
|
||||||
class TheMovieDb(MovieProvider):
|
class TheMovieDb(MovieProvider):
|
||||||
|
MAX_EXTRATHUMBS = 4
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('movie.info', self.getInfo, priority = 2)
|
addEvent('movie.info', self.getInfo, priority = 3)
|
||||||
addEvent('movie.info_by_tmdb', self.getInfo)
|
addEvent('movie.info_by_tmdb', self.getInfo)
|
||||||
|
|
||||||
# Configure TMDB settings
|
# Configure TMDB settings
|
||||||
@@ -97,16 +98,18 @@ class TheMovieDb(MovieProvider):
|
|||||||
if not movie_data:
|
if not movie_data:
|
||||||
|
|
||||||
# Images
|
# Images
|
||||||
poster = self.getImage(movie, type = 'poster', size = 'poster')
|
poster = self.getImage(movie, type = 'poster', size = 'w154')
|
||||||
poster_original = self.getImage(movie, type = 'poster', size = 'original')
|
poster_original = self.getImage(movie, type = 'poster', size = 'original')
|
||||||
backdrop_original = self.getImage(movie, type = 'backdrop', size = 'original')
|
backdrop_original = self.getImage(movie, type = 'backdrop', size = 'original')
|
||||||
|
extra_thumbs = self.getMultImages(movie, type = 'backdrops', size = 'original', n = self.MAX_EXTRATHUMBS, skipfirst = True)
|
||||||
|
|
||||||
images = {
|
images = {
|
||||||
'poster': [poster] if poster else [],
|
'poster': [poster] if poster else [],
|
||||||
#'backdrop': [backdrop] if backdrop else [],
|
#'backdrop': [backdrop] if backdrop else [],
|
||||||
'poster_original': [poster_original] if poster_original else [],
|
'poster_original': [poster_original] if poster_original else [],
|
||||||
'backdrop_original': [backdrop_original] if backdrop_original else [],
|
'backdrop_original': [backdrop_original] if backdrop_original else [],
|
||||||
'actors': {}
|
'actors': {},
|
||||||
|
'extra_thumbs': extra_thumbs
|
||||||
}
|
}
|
||||||
|
|
||||||
# Genres
|
# Genres
|
||||||
@@ -150,8 +153,10 @@ class TheMovieDb(MovieProvider):
|
|||||||
movie_data = dict((k, v) for k, v in movie_data.items() if v)
|
movie_data = dict((k, v) for k, v in movie_data.items() if v)
|
||||||
|
|
||||||
# Add alternative names
|
# Add alternative names
|
||||||
|
if movie_data['original_title'] and movie_data['original_title'] not in movie_data['titles']:
|
||||||
|
movie_data['titles'].append(movie_data['original_title'])
|
||||||
|
|
||||||
if extended:
|
if extended:
|
||||||
movie_data['titles'].append(movie.originaltitle)
|
|
||||||
for alt in movie.alternate_titles:
|
for alt in movie.alternate_titles:
|
||||||
alt_name = alt.title
|
alt_name = alt.title
|
||||||
if alt_name and alt_name not in movie_data['titles'] and alt_name.lower() != 'none' and alt_name is not None:
|
if alt_name and alt_name not in movie_data['titles'] and alt_name.lower() != 'none' and alt_name is not None:
|
||||||
@@ -172,6 +177,30 @@ class TheMovieDb(MovieProvider):
|
|||||||
|
|
||||||
return image_url
|
return image_url
|
||||||
|
|
||||||
|
def getMultImages(self, movie, type = 'backdrops', size = 'original', n = -1, skipfirst = False):
|
||||||
|
"""
|
||||||
|
If n < 0, return all images. Otherwise return n images.
|
||||||
|
If n > len(getattr(movie, type)), then return all images.
|
||||||
|
If skipfirst is True, then it will skip getattr(movie, type)[0]. This
|
||||||
|
is because backdrops[0] is typically backdrop.
|
||||||
|
"""
|
||||||
|
|
||||||
|
image_urls = []
|
||||||
|
try:
|
||||||
|
images = getattr(movie, type)
|
||||||
|
if n < 0 or n > len(images):
|
||||||
|
num_images = len(images)
|
||||||
|
else:
|
||||||
|
num_images = n
|
||||||
|
|
||||||
|
for i in range(int(skipfirst), num_images + int(skipfirst)):
|
||||||
|
image_urls.append(images[i].geturl(size = size))
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.debug('Failed getting %i %s.%s for "%s"', (n, type, size, ss(str(movie))))
|
||||||
|
|
||||||
|
return image_urls
|
||||||
|
|
||||||
def isDisabled(self):
|
def isDisabled(self):
|
||||||
if self.conf('api_key') == '':
|
if self.conf('api_key') == '':
|
||||||
log.error('No API key provided.')
|
log.error('No API key provided.')
|
||||||
|
|||||||
Regular → Executable
+114
-39
@@ -4,7 +4,7 @@ import traceback
|
|||||||
|
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.encoding import sp
|
from couchpotato.core.helpers.encoding import sp
|
||||||
from couchpotato.core.helpers.variable import getIdentifier
|
from couchpotato.core.helpers.variable import getIdentifier, underscoreToCamel
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media._base.providers.metadata.base import MetaDataBase
|
from couchpotato.core.media._base.providers.metadata.base import MetaDataBase
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
@@ -28,7 +28,7 @@ class MovieMetaData(MetaDataBase):
|
|||||||
|
|
||||||
# Update library to get latest info
|
# Update library to get latest info
|
||||||
try:
|
try:
|
||||||
group['media'] = fireEvent('movie.update_info', group['media'].get('_id'), identifier = getIdentifier(group['media']), extended = True, single = True)
|
group['media'] = fireEvent('movie.update', group['media'].get('_id'), identifier = getIdentifier(group['media']), extended = True, single = True)
|
||||||
except:
|
except:
|
||||||
log.error('Failed to update movie, before creating metadata: %s', traceback.format_exc())
|
log.error('Failed to update movie, before creating metadata: %s', traceback.format_exc())
|
||||||
|
|
||||||
@@ -38,75 +38,150 @@ class MovieMetaData(MetaDataBase):
|
|||||||
|
|
||||||
movie_info = group['media'].get('info')
|
movie_info = group['media'].get('info')
|
||||||
|
|
||||||
for file_type in ['nfo', 'thumbnail', 'fanart']:
|
for file_type in ['nfo']:
|
||||||
try:
|
try:
|
||||||
# Get file path
|
self._createType(meta_name, root, movie_info, group, file_type, 0)
|
||||||
name = getattr(self, 'get' + file_type.capitalize() + 'Name')(meta_name, root)
|
except:
|
||||||
|
log.error('Unable to create %s file: %s', ('nfo', traceback.format_exc()))
|
||||||
|
|
||||||
if name and (self.conf('meta_' + file_type) or self.conf('meta_' + file_type) is None):
|
for file_type in ['thumbnail', 'fanart', 'banner', 'disc_art', 'logo', 'clear_art', 'landscape', 'extra_thumbs', 'extra_fanart']:
|
||||||
|
try:
|
||||||
# Get file content
|
if file_type == 'thumbnail':
|
||||||
content = getattr(self, 'get' + file_type.capitalize())(movie_info = movie_info, data = group)
|
num_images = len(movie_info['images']['poster_original'])
|
||||||
if content:
|
elif file_type == 'fanart':
|
||||||
log.debug('Creating %s file: %s', (file_type, name))
|
num_images = len(movie_info['images']['backdrop_original'])
|
||||||
if os.path.isfile(content):
|
else:
|
||||||
content = sp(content)
|
num_images = len(movie_info['images'][file_type])
|
||||||
name = sp(name)
|
|
||||||
|
|
||||||
shutil.copy2(content, name)
|
|
||||||
shutil.copyfile(content, name)
|
|
||||||
|
|
||||||
# Try and copy stats seperately
|
|
||||||
try: shutil.copystat(content, name)
|
|
||||||
except: pass
|
|
||||||
else:
|
|
||||||
self.createFile(name, content)
|
|
||||||
group['renamed_files'].append(name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
os.chmod(sp(name), Env.getPermission('file'))
|
|
||||||
except:
|
|
||||||
log.debug('Failed setting permissions for %s: %s', (name, traceback.format_exc()))
|
|
||||||
|
|
||||||
|
for i in range(num_images):
|
||||||
|
self._createType(meta_name, root, movie_info, group, file_type, i)
|
||||||
except:
|
except:
|
||||||
log.error('Unable to create %s file: %s', (file_type, traceback.format_exc()))
|
log.error('Unable to create %s file: %s', (file_type, traceback.format_exc()))
|
||||||
|
|
||||||
|
def _createType(self, meta_name, root, movie_info, group, file_type, i): # Get file path
|
||||||
|
camelcase_method = underscoreToCamel(file_type.capitalize())
|
||||||
|
name = getattr(self, 'get' + camelcase_method + 'Name')(meta_name, root, i)
|
||||||
|
|
||||||
|
if name and (self.conf('meta_' + file_type) or self.conf('meta_' + file_type) is None):
|
||||||
|
|
||||||
|
# Get file content
|
||||||
|
content = getattr(self, 'get' + camelcase_method)(movie_info = movie_info, data = group, i = i)
|
||||||
|
if content:
|
||||||
|
log.debug('Creating %s file: %s', (file_type, name))
|
||||||
|
if os.path.isfile(content):
|
||||||
|
content = sp(content)
|
||||||
|
name = sp(name)
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.dirname(name)):
|
||||||
|
os.makedirs(os.path.dirname(name))
|
||||||
|
|
||||||
|
shutil.copy2(content, name)
|
||||||
|
shutil.copyfile(content, name)
|
||||||
|
|
||||||
|
# Try and copy stats seperately
|
||||||
|
try: shutil.copystat(content, name)
|
||||||
|
except: pass
|
||||||
|
else:
|
||||||
|
self.createFile(name, content)
|
||||||
|
group['renamed_files'].append(name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.chmod(sp(name), Env.getPermission('file'))
|
||||||
|
except:
|
||||||
|
log.debug('Failed setting permissions for %s: %s', (name, traceback.format_exc()))
|
||||||
|
|
||||||
def getRootName(self, data = None):
|
def getRootName(self, data = None):
|
||||||
if not data: data = {}
|
if not data: data = {}
|
||||||
return os.path.join(data['destination_dir'], data['filename'])
|
return os.path.join(data['destination_dir'], data['filename'])
|
||||||
|
|
||||||
def getFanartName(self, name, root):
|
def getFanartName(self, name, root, i):
|
||||||
return
|
return
|
||||||
|
|
||||||
def getThumbnailName(self, name, root):
|
def getThumbnailName(self, name, root, i):
|
||||||
return
|
return
|
||||||
|
|
||||||
def getNfoName(self, name, root):
|
def getBannerName(self, name, root, i):
|
||||||
return
|
return
|
||||||
|
|
||||||
def getNfo(self, movie_info = None, data = None):
|
def getClearArtName(self, name, root, i):
|
||||||
|
return
|
||||||
|
|
||||||
|
def getLogoName(self, name, root, i):
|
||||||
|
return
|
||||||
|
|
||||||
|
def getDiscArtName(self, name, root, i):
|
||||||
|
return
|
||||||
|
|
||||||
|
def getLandscapeName(self, name, root, i):
|
||||||
|
return
|
||||||
|
|
||||||
|
def getExtraThumbsName(self, name, root, i):
|
||||||
|
return
|
||||||
|
|
||||||
|
def getExtraFanartName(self, name, root, i):
|
||||||
|
return
|
||||||
|
|
||||||
|
def getNfoName(self, name, root, i):
|
||||||
|
return
|
||||||
|
|
||||||
|
def getNfo(self, movie_info = None, data = None, i = 0):
|
||||||
if not data: data = {}
|
if not data: data = {}
|
||||||
if not movie_info: movie_info = {}
|
if not movie_info: movie_info = {}
|
||||||
|
|
||||||
def getThumbnail(self, movie_info = None, data = None, wanted_file_type = 'poster_original'):
|
def getThumbnail(self, movie_info = None, data = None, wanted_file_type = 'poster_original', i = 0):
|
||||||
if not data: data = {}
|
if not data: data = {}
|
||||||
if not movie_info: movie_info = {}
|
if not movie_info: movie_info = {}
|
||||||
|
|
||||||
# See if it is in current files
|
# See if it is in current files
|
||||||
files = data['media'].get('files')
|
files = data['media'].get('files')
|
||||||
if files.get('image_' + wanted_file_type):
|
if files.get('image_' + wanted_file_type):
|
||||||
if os.path.isfile(files['image_' + wanted_file_type][0]):
|
if os.path.isfile(files['image_' + wanted_file_type][i]):
|
||||||
return files['image_' + wanted_file_type][0]
|
return files['image_' + wanted_file_type][i]
|
||||||
|
|
||||||
# Download using existing info
|
# Download using existing info
|
||||||
try:
|
try:
|
||||||
images = movie_info['images'][wanted_file_type]
|
images = movie_info['images'][wanted_file_type]
|
||||||
file_path = fireEvent('file.download', url = images[0], single = True)
|
file_path = fireEvent('file.download', url = images[i], single = True)
|
||||||
return file_path
|
return file_path
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def getFanart(self, movie_info = None, data = None):
|
def getFanart(self, movie_info = None, data = None, i = 0):
|
||||||
if not data: data = {}
|
if not data: data = {}
|
||||||
if not movie_info: movie_info = {}
|
if not movie_info: movie_info = {}
|
||||||
return self.getThumbnail(movie_info = movie_info, data = data, wanted_file_type = 'backdrop_original')
|
return self.getThumbnail(movie_info = movie_info, data = data, wanted_file_type = 'backdrop_original', i = i)
|
||||||
|
|
||||||
|
def getBanner(self, movie_info = None, data = None, i = 0):
|
||||||
|
if not data: data = {}
|
||||||
|
if not movie_info: movie_info = {}
|
||||||
|
return self.getThumbnail(movie_info = movie_info, data = data, wanted_file_type = 'banner', i = i)
|
||||||
|
|
||||||
|
def getClearArt(self, movie_info = None, data = None, i = 0):
|
||||||
|
if not data: data = {}
|
||||||
|
if not movie_info: movie_info = {}
|
||||||
|
return self.getThumbnail(movie_info = movie_info, data = data, wanted_file_type = 'clear_art', i = i)
|
||||||
|
|
||||||
|
def getLogo(self, movie_info = None, data = None, i = 0):
|
||||||
|
if not data: data = {}
|
||||||
|
if not movie_info: movie_info = {}
|
||||||
|
return self.getThumbnail(movie_info = movie_info, data = data, wanted_file_type = 'logo', i = i)
|
||||||
|
|
||||||
|
def getDiscArt(self, movie_info = None, data = None, i = 0):
|
||||||
|
if not data: data = {}
|
||||||
|
if not movie_info: movie_info = {}
|
||||||
|
return self.getThumbnail(movie_info = movie_info, data = data, wanted_file_type = 'disc_art', i = i)
|
||||||
|
|
||||||
|
def getLandscape(self, movie_info = None, data = None, i = 0):
|
||||||
|
if not data: data = {}
|
||||||
|
if not movie_info: movie_info = {}
|
||||||
|
return self.getThumbnail(movie_info = movie_info, data= data, wanted_file_type = 'landscape', i = i)
|
||||||
|
|
||||||
|
def getExtraThumbs(self, movie_info = None, data = None, i = 0):
|
||||||
|
if not data: data = {}
|
||||||
|
if not movie_info: movie_info = {}
|
||||||
|
return self.getThumbnail(movie_info = movie_info, data = data, wanted_file_type = 'extra_thumbs', i = i)
|
||||||
|
|
||||||
|
def getExtraFanart(self, movie_info = None, data = None, i = 0):
|
||||||
|
if not data: data = {}
|
||||||
|
if not movie_info: movie_info = {}
|
||||||
|
return self.getThumbnail(movie_info = movie_info, data = data, wanted_file_type = 'extra_fanart', i = i)
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from couchpotato.core.media.movie.providers.metadata.base import MovieMetaData
|
||||||
|
|
||||||
|
|
||||||
|
autoload = 'MediaBrowser'
|
||||||
|
|
||||||
|
|
||||||
|
class MediaBrowser(MovieMetaData):
|
||||||
|
|
||||||
|
def getThumbnailName(self, name, root, i):
|
||||||
|
return os.path.join(root, 'folder.jpg')
|
||||||
|
|
||||||
|
def getFanartName(self, name, root, i):
|
||||||
|
return os.path.join(root, 'backdrop.jpg')
|
||||||
|
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'mediabrowser',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'renamer',
|
||||||
|
'subtab': 'metadata',
|
||||||
|
'name': 'mediabrowser_metadata',
|
||||||
|
'label': 'MediaBrowser',
|
||||||
|
'description': 'Generate folder.jpg and backdrop.jpg',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'meta_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from couchpotato.core.media.movie.providers.metadata.base import MovieMetaData
|
||||||
|
|
||||||
|
|
||||||
|
autoload = 'SonyPS3'
|
||||||
|
|
||||||
|
|
||||||
|
class SonyPS3(MovieMetaData):
|
||||||
|
|
||||||
|
def getThumbnailName(self, name, root, i):
|
||||||
|
return os.path.join(root, 'cover.jpg')
|
||||||
|
|
||||||
|
|
||||||
|
config = [{
|
||||||
|
'name': 'sonyps3',
|
||||||
|
'groups': [
|
||||||
|
{
|
||||||
|
'tab': 'renamer',
|
||||||
|
'subtab': 'metadata',
|
||||||
|
'name': 'sonyps3_metadata',
|
||||||
|
'label': 'Sony PS3',
|
||||||
|
'description': 'Generate cover.jpg',
|
||||||
|
'options': [
|
||||||
|
{
|
||||||
|
'name': 'meta_enabled',
|
||||||
|
'default': False,
|
||||||
|
'type': 'enabler',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
@@ -8,7 +8,7 @@ autoload = 'WindowsMediaCenter'
|
|||||||
|
|
||||||
class WindowsMediaCenter(MovieMetaData):
|
class WindowsMediaCenter(MovieMetaData):
|
||||||
|
|
||||||
def getThumbnailName(self, name, root):
|
def getThumbnailName(self, name, root, i):
|
||||||
return os.path.join(root, 'folder.jpg')
|
return os.path.join(root, 'folder.jpg')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,19 +17,43 @@ autoload = 'XBMC'
|
|||||||
|
|
||||||
class XBMC(MovieMetaData):
|
class XBMC(MovieMetaData):
|
||||||
|
|
||||||
def getFanartName(self, name, root):
|
def getFanartName(self, name, root, i):
|
||||||
return self.createMetaName(self.conf('meta_fanart_name'), name, root)
|
return self.createMetaName(self.conf('meta_fanart_name'), name, root)
|
||||||
|
|
||||||
def getThumbnailName(self, name, root):
|
def getThumbnailName(self, name, root, i):
|
||||||
return self.createMetaName(self.conf('meta_thumbnail_name'), name, root)
|
return self.createMetaName(self.conf('meta_thumbnail_name'), name, root)
|
||||||
|
|
||||||
def getNfoName(self, name, root):
|
def getNfoName(self, name, root, i):
|
||||||
return self.createMetaName(self.conf('meta_nfo_name'), name, root)
|
return self.createMetaName(self.conf('meta_nfo_name'), name, root)
|
||||||
|
|
||||||
|
def getBannerName(self, name, root, i):
|
||||||
|
return self.createMetaName(self.conf('meta_banner_name'), name, root)
|
||||||
|
|
||||||
|
def getClearArtName(self, name, root, i):
|
||||||
|
return self.createMetaName(self.conf('meta_clear_art_name'), name, root)
|
||||||
|
|
||||||
|
def getLogoName(self, name, root, i):
|
||||||
|
return self.createMetaName(self.conf('meta_logo_name'), name, root)
|
||||||
|
|
||||||
|
def getDiscArtName(self, name, root, i):
|
||||||
|
return self.createMetaName(self.conf('meta_disc_art_name'), name, root)
|
||||||
|
|
||||||
|
def getLandscapeName(self, name, root, i):
|
||||||
|
return self.createMetaName(self.conf('meta_landscape_name'), name, root)
|
||||||
|
|
||||||
|
def getExtraThumbsName(self, name, root, i):
|
||||||
|
return self.createMetaNameMult(self.conf('meta_extra_thumbs_name'), name, root, i)
|
||||||
|
|
||||||
|
def getExtraFanartName(self, name, root, i):
|
||||||
|
return self.createMetaNameMult(self.conf('meta_extra_fanart_name'), name, root, i)
|
||||||
|
|
||||||
def createMetaName(self, basename, name, root):
|
def createMetaName(self, basename, name, root):
|
||||||
return os.path.join(root, basename.replace('%s', name))
|
return os.path.join(root, basename.replace('%s', name))
|
||||||
|
|
||||||
def getNfo(self, movie_info = None, data = None):
|
def createMetaNameMult(self, basename, name, root, i):
|
||||||
|
return os.path.join(root, basename.replace('%s', name).replace('<i>', str(i + 1)))
|
||||||
|
|
||||||
|
def getNfo(self, movie_info=None, data=None, i=0):
|
||||||
if not data: data = {}
|
if not data: data = {}
|
||||||
if not movie_info: movie_info = {}
|
if not movie_info: movie_info = {}
|
||||||
|
|
||||||
@@ -129,10 +153,25 @@ class XBMC(MovieMetaData):
|
|||||||
for image_url in movie_info['images']['poster_original']:
|
for image_url in movie_info['images']['poster_original']:
|
||||||
image = SubElement(nfoxml, 'thumb')
|
image = SubElement(nfoxml, 'thumb')
|
||||||
image.text = toUnicode(image_url)
|
image.text = toUnicode(image_url)
|
||||||
fanart = SubElement(nfoxml, 'fanart')
|
|
||||||
for image_url in movie_info['images']['backdrop_original']:
|
image_types = [
|
||||||
image = SubElement(fanart, 'thumb')
|
('fanart', 'backdrop_original'),
|
||||||
image.text = toUnicode(image_url)
|
('banner', 'banner'),
|
||||||
|
('discart', 'disc_art'),
|
||||||
|
('logo', 'logo'),
|
||||||
|
('clearart', 'clear_art'),
|
||||||
|
('landscape', 'landscape'),
|
||||||
|
('extrathumb', 'extra_thumbs'),
|
||||||
|
('extrafanart', 'extra_fanart'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for image_type in image_types:
|
||||||
|
sub, type = image_type
|
||||||
|
|
||||||
|
sub_element = SubElement(nfoxml, sub)
|
||||||
|
for image_url in movie_info['images'][type]:
|
||||||
|
image = SubElement(sub_element, 'thumb')
|
||||||
|
image.text = toUnicode(image_url)
|
||||||
|
|
||||||
# Add trailer if found
|
# Add trailer if found
|
||||||
trailer_found = False
|
trailer_found = False
|
||||||
@@ -239,6 +278,92 @@ config = [{
|
|||||||
'default': '%s.tbn',
|
'default': '%s.tbn',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_banner',
|
||||||
|
'label': 'Banner',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_banner_name',
|
||||||
|
'label': 'Banner filename',
|
||||||
|
'default': 'banner.jpg',
|
||||||
|
'advanced': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_clear_art',
|
||||||
|
'label': 'ClearArt',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_clear_art_name',
|
||||||
|
'label': 'ClearArt filename',
|
||||||
|
'default': 'clearart.png',
|
||||||
|
'advanced': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_disc_art',
|
||||||
|
'label': 'DiscArt',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_disc_art_name',
|
||||||
|
'label': 'DiscArt filename',
|
||||||
|
'default': 'disc.png',
|
||||||
|
'advanced': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_landscape',
|
||||||
|
'label': 'Landscape',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_landscape_name',
|
||||||
|
'label': 'Landscape filename',
|
||||||
|
'default': 'landscape.jpg',
|
||||||
|
'advanced': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_logo',
|
||||||
|
'label': 'ClearLogo',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_logo_name',
|
||||||
|
'label': 'ClearLogo filename',
|
||||||
|
'default': 'logo.png',
|
||||||
|
'advanced': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_extra_thumbs',
|
||||||
|
'label': 'Extrathumbs',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_extra_thumbs_name',
|
||||||
|
'label': 'Extrathumbs filename',
|
||||||
|
'description': '<i> is the image number, and must be included to have multiple images',
|
||||||
|
'default': 'extrathumbs/thumb<i>.jpg',
|
||||||
|
'advanced': True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_extra_fanart',
|
||||||
|
'label': 'Extrafanart',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'meta_extra_fanart_name',
|
||||||
|
'label': 'Extrafanart filename',
|
||||||
|
'default': 'extrafanart/extrafanart<i>.jpg',
|
||||||
|
'description': '<i> is the image number, and must be included to have multiple images',
|
||||||
|
'advanced': True
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -11,11 +11,16 @@ autoload = 'Newznab'
|
|||||||
|
|
||||||
class Newznab(MovieProvider, Base):
|
class Newznab(MovieProvider, Base):
|
||||||
|
|
||||||
def buildUrl(self, media, api_key):
|
def buildUrl(self, media, host):
|
||||||
|
|
||||||
query = tryUrlencode({
|
query = tryUrlencode({
|
||||||
't': 'movie',
|
't': 'movie',
|
||||||
'imdbid': getIdentifier(media).replace('tt', ''),
|
'imdbid': getIdentifier(media).replace('tt', ''),
|
||||||
'apikey': api_key,
|
'apikey': host['api_key'],
|
||||||
'extended': 1
|
'extended': 1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if len(host.get('custom_tag', '')) > 0:
|
||||||
|
query = '%s&%s' % (query, host.get('custom_tag'))
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
|
||||||
from couchpotato.core.logger import CPLog
|
|
||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.media._base.providers.nzb.nzbindex import Base
|
|
||||||
from couchpotato.core.media.movie.providers.base import MovieProvider
|
|
||||||
from couchpotato.environment import Env
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
|
||||||
|
|
||||||
autoload = 'NzbIndex'
|
|
||||||
|
|
||||||
|
|
||||||
class NzbIndex(MovieProvider, Base):
|
|
||||||
|
|
||||||
def buildUrl(self, media, quality):
|
|
||||||
title = fireEvent('library.query', media, include_year = False, single = True)
|
|
||||||
year = media['info']['year']
|
|
||||||
|
|
||||||
query = tryUrlencode({
|
|
||||||
'q': '"%s %s" | "%s (%s)"' % (title, year, title, year),
|
|
||||||
'age': Env.setting('retention', 'nzb'),
|
|
||||||
'sort': 'agedesc',
|
|
||||||
'minsize': quality.get('size_min'),
|
|
||||||
'maxsize': quality.get('size_max'),
|
|
||||||
'rating': 1,
|
|
||||||
'max': 250,
|
|
||||||
'more': 1,
|
|
||||||
'complete': 1,
|
|
||||||
})
|
|
||||||
return query
|
|
||||||
@@ -10,10 +10,14 @@ autoload = 'BiTHDTV'
|
|||||||
|
|
||||||
|
|
||||||
class BiTHDTV(MovieProvider, Base):
|
class BiTHDTV(MovieProvider, Base):
|
||||||
|
cat_ids = [
|
||||||
|
([2], ['bd50']),
|
||||||
|
]
|
||||||
|
cat_backup_id = 7 # Movies
|
||||||
|
|
||||||
def buildUrl(self, media):
|
def buildUrl(self, media, quality):
|
||||||
query = tryUrlencode({
|
query = tryUrlencode({
|
||||||
'search': fireEvent('library.query', media, single = True),
|
'search': fireEvent('library.query', media, single = True),
|
||||||
'cat': 7 # Movie cat
|
'cat': self.getCatId(quality)[0]
|
||||||
})
|
})
|
||||||
return query
|
return query
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.media._base.providers.torrent.bitsoup import Base
|
from couchpotato.core.media._base.providers.torrent.bitsoup import Base
|
||||||
from couchpotato.core.media.movie.providers.base import MovieProvider
|
from couchpotato.core.media.movie.providers.base import MovieProvider
|
||||||
|
|
||||||
@@ -18,12 +17,9 @@ class Bitsoup(MovieProvider, Base):
|
|||||||
]
|
]
|
||||||
cat_backup_id = 0
|
cat_backup_id = 0
|
||||||
|
|
||||||
def buildUrl(self, media, quality):
|
def buildUrl(self, title, media, quality):
|
||||||
query = tryUrlencode({
|
query = tryUrlencode({
|
||||||
'search': '"%s" %s' % (
|
'search': '"%s" %s' % (title, media['info']['year']),
|
||||||
fireEvent('library.query', media, include_year = False, single = True),
|
|
||||||
media['info']['year']
|
|
||||||
),
|
|
||||||
'cat': self.getCatId(quality)[0],
|
'cat': self.getCatId(quality)[0],
|
||||||
})
|
})
|
||||||
return query
|
return query
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ class IPTorrents(MovieProvider, Base):
|
|||||||
([87], ['3d']),
|
([87], ['3d']),
|
||||||
([48], ['720p', '1080p', 'bd50']),
|
([48], ['720p', '1080p', 'bd50']),
|
||||||
([72], ['cam', 'ts', 'tc', 'r5', 'scr']),
|
([72], ['cam', 'ts', 'tc', 'r5', 'scr']),
|
||||||
([7], ['dvdrip', 'brrip']),
|
([7,48], ['dvdrip', 'brrip']),
|
||||||
([6], ['dvdr']),
|
([6], ['dvdr']),
|
||||||
]
|
]
|
||||||
|
|
||||||
def buildUrl(self, title, media, quality):
|
def buildUrl(self, title, media, quality):
|
||||||
query = '%s %s' % (title.replace(':', ''), media['info']['year'])
|
query = '"%s" %s' % (title.replace(':', ''), media['info']['year'])
|
||||||
|
|
||||||
return self._buildUrl(query, quality)
|
return self._buildUrl(query, quality)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class PassThePopcorn(MovieProvider, Base):
|
|||||||
'bd50': {'media': 'Blu-ray', 'format': 'BD50'},
|
'bd50': {'media': 'Blu-ray', 'format': 'BD50'},
|
||||||
'1080p': {'resolution': '1080p'},
|
'1080p': {'resolution': '1080p'},
|
||||||
'720p': {'resolution': '720p'},
|
'720p': {'resolution': '720p'},
|
||||||
'brrip': {'media': 'Blu-ray'},
|
'brrip': {'resolution': 'anyhd'},
|
||||||
'dvdr': {'resolution': 'anysd'},
|
'dvdr': {'resolution': 'anysd'},
|
||||||
'dvdrip': {'media': 'DVD'},
|
'dvdrip': {'media': 'DVD'},
|
||||||
'scr': {'media': 'DVD-Screener'},
|
'scr': {'media': 'DVD-Screener'},
|
||||||
@@ -27,7 +27,7 @@ class PassThePopcorn(MovieProvider, Base):
|
|||||||
'bd50': {'Codec': ['BD50']},
|
'bd50': {'Codec': ['BD50']},
|
||||||
'1080p': {'Resolution': ['1080p']},
|
'1080p': {'Resolution': ['1080p']},
|
||||||
'720p': {'Resolution': ['720p']},
|
'720p': {'Resolution': ['720p']},
|
||||||
'brrip': {'Source': ['Blu-ray'], 'Quality': ['High Definition'], 'Container': ['!ISO']},
|
'brrip': {'Quality': ['High Definition'], 'Container': ['!ISO']},
|
||||||
'dvdr': {'Codec': ['DVD5', 'DVD9']},
|
'dvdr': {'Codec': ['DVD5', 'DVD9']},
|
||||||
'dvdrip': {'Source': ['DVD'], 'Codec': ['!DVD5', '!DVD9']},
|
'dvdrip': {'Source': ['DVD'], 'Codec': ['!DVD5', '!DVD9']},
|
||||||
'scr': {'Source': ['DVD-Screener']},
|
'scr': {'Source': ['DVD-Screener']},
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
from couchpotato.core.logger import CPLog
|
|
||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.media._base.providers.torrent.publichd import Base
|
|
||||||
from couchpotato.core.media.movie.providers.base import MovieProvider
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
|
||||||
|
|
||||||
autoload = 'PublicHD'
|
|
||||||
|
|
||||||
|
|
||||||
class PublicHD(MovieProvider, Base):
|
|
||||||
|
|
||||||
def buildUrl(self, media):
|
|
||||||
return fireEvent('library.query', media, single = True)
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media._base.providers.torrent.sceneaccess import Base
|
from couchpotato.core.media._base.providers.torrent.sceneaccess import Base
|
||||||
from couchpotato.core.media.movie.providers.base import MovieProvider
|
from couchpotato.core.media.movie.providers.base import MovieProvider
|
||||||
@@ -17,13 +16,13 @@ class SceneAccess(MovieProvider, Base):
|
|||||||
([8], ['dvdr']),
|
([8], ['dvdr']),
|
||||||
]
|
]
|
||||||
|
|
||||||
def buildUrl(self, media, quality):
|
def buildUrl(self, title, media, quality):
|
||||||
cat_id = self.getCatId(quality)[0]
|
cat_id = self.getCatId(quality)[0]
|
||||||
url = self.urls['search'] % (cat_id, cat_id)
|
url = self.urls['search'] % (cat_id, cat_id)
|
||||||
|
|
||||||
arguments = tryUrlencode({
|
arguments = tryUrlencode({
|
||||||
'search': fireEvent('library.query', media, single = True),
|
'search': '%s %s' % (title, media['info']['year']),
|
||||||
'method': 3,
|
'method': 2,
|
||||||
})
|
})
|
||||||
query = "%s&%s" % (url, arguments)
|
query = "%s&%s" % (url, arguments)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class ThePirateBay(MovieProvider, Base):
|
|||||||
|
|
||||||
cat_ids = [
|
cat_ids = [
|
||||||
([209], ['3d']),
|
([209], ['3d']),
|
||||||
([207], ['720p', '1080p']),
|
([207], ['720p', '1080p', 'bd50']),
|
||||||
([201], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
([201], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
|
||||||
([201, 207], ['brrip']),
|
([201, 207], ['brrip']),
|
||||||
([202], ['dvdr'])
|
([202], ['dvdr'])
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.event import fireEvent
|
|
||||||
from couchpotato.core.media._base.providers.torrent.torrentday import Base
|
from couchpotato.core.media._base.providers.torrent.torrentday import Base
|
||||||
from couchpotato.core.media.movie.providers.base import MovieProvider
|
from couchpotato.core.media.movie.providers.base import MovieProvider
|
||||||
|
|
||||||
@@ -16,6 +15,3 @@ class TorrentDay(MovieProvider, Base):
|
|||||||
([3], ['dvdr']),
|
([3], ['dvdr']),
|
||||||
([5], ['bd50']),
|
([5], ['bd50']),
|
||||||
]
|
]
|
||||||
|
|
||||||
def buildUrl(self, media):
|
|
||||||
return fireEvent('library.query', media, single = True)
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from couchpotato import fireEvent
|
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media._base.providers.torrent.torrentleech import Base
|
from couchpotato.core.media._base.providers.torrent.torrentleech import Base
|
||||||
@@ -12,7 +11,7 @@ autoload = 'TorrentLeech'
|
|||||||
class TorrentLeech(MovieProvider, Base):
|
class TorrentLeech(MovieProvider, Base):
|
||||||
|
|
||||||
cat_ids = [
|
cat_ids = [
|
||||||
([13], ['720p', '1080p']),
|
([13], ['720p', '1080p', 'bd50']),
|
||||||
([8], ['cam']),
|
([8], ['cam']),
|
||||||
([9], ['ts', 'tc']),
|
([9], ['ts', 'tc']),
|
||||||
([10], ['r5', 'scr']),
|
([10], ['r5', 'scr']),
|
||||||
@@ -21,8 +20,8 @@ class TorrentLeech(MovieProvider, Base):
|
|||||||
([12], ['dvdr']),
|
([12], ['dvdr']),
|
||||||
]
|
]
|
||||||
|
|
||||||
def buildUrl(self, media, quality):
|
def buildUrl(self, title, media, quality):
|
||||||
return (
|
return (
|
||||||
tryUrlencode(fireEvent('library.query', media, single = True)),
|
tryUrlencode(title.replace(':', '')),
|
||||||
self.getCatId(quality)[0]
|
self.getCatId(quality)[0]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,6 +31,6 @@ class TorrentShack(MovieProvider, Base):
|
|||||||
|
|
||||||
def buildUrl(self, media, quality):
|
def buildUrl(self, media, quality):
|
||||||
query = (tryUrlencode(fireEvent('library.query', media, single = True)),
|
query = (tryUrlencode(fireEvent('library.query', media, single = True)),
|
||||||
self.getCatId(quality)[0],
|
self.getSceneOnly(),
|
||||||
self.getSceneOnly())
|
self.getCatId(quality)[0])
|
||||||
return query
|
return query
|
||||||
|
|||||||
@@ -11,3 +11,6 @@ class TrailerProvider(Provider):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('trailer.search', self.search)
|
addEvent('trailer.search', self.search)
|
||||||
|
|
||||||
|
def search(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from string import digits, ascii_letters
|
from string import digits, ascii_letters
|
||||||
from urllib2 import HTTPError
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from bs4 import SoupStrainer, BeautifulSoup
|
from bs4 import SoupStrainer, BeautifulSoup
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.variable import mergeDicts, getTitle
|
from couchpotato.core.helpers.variable import mergeDicts, getTitle, getIdentifier
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.media.movie.providers.trailer.base import TrailerProvider
|
from couchpotato.core.media.movie.providers.trailer.base import TrailerProvider
|
||||||
|
from requests import HTTPError
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -21,6 +21,7 @@ class HDTrailers(TrailerProvider):
|
|||||||
'backup': 'http://www.hd-trailers.net/blog/',
|
'backup': 'http://www.hd-trailers.net/blog/',
|
||||||
}
|
}
|
||||||
providers = ['apple.ico', 'yahoo.ico', 'moviefone.ico', 'myspace.ico', 'favicon.ico']
|
providers = ['apple.ico', 'yahoo.ico', 'moviefone.ico', 'myspace.ico', 'favicon.ico']
|
||||||
|
only_tables_tags = SoupStrainer('table')
|
||||||
|
|
||||||
def search(self, group):
|
def search(self, group):
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ class HDTrailers(TrailerProvider):
|
|||||||
|
|
||||||
url = self.urls['api'] % self.movieUrlName(movie_name)
|
url = self.urls['api'] % self.movieUrlName(movie_name)
|
||||||
try:
|
try:
|
||||||
data = self.getCache('hdtrailers.%s' % group['identifier'], url, show_error = False)
|
data = self.getCache('hdtrailers.%s' % getIdentifier(group), url, show_error = False)
|
||||||
except HTTPError:
|
except HTTPError:
|
||||||
log.debug('No page found for: %s', movie_name)
|
log.debug('No page found for: %s', movie_name)
|
||||||
data = None
|
data = None
|
||||||
@@ -58,7 +59,7 @@ class HDTrailers(TrailerProvider):
|
|||||||
|
|
||||||
url = "%s?%s" % (self.urls['backup'], tryUrlencode({'s':movie_name}))
|
url = "%s?%s" % (self.urls['backup'], tryUrlencode({'s':movie_name}))
|
||||||
try:
|
try:
|
||||||
data = self.getCache('hdtrailers.alt.%s' % group['identifier'], url, show_error = False)
|
data = self.getCache('hdtrailers.alt.%s' % getIdentifier(group), url, show_error = False)
|
||||||
except HTTPError:
|
except HTTPError:
|
||||||
log.debug('No alternative page found for: %s', movie_name)
|
log.debug('No alternative page found for: %s', movie_name)
|
||||||
data = None
|
data = None
|
||||||
@@ -67,8 +68,7 @@ class HDTrailers(TrailerProvider):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tables = SoupStrainer('div')
|
html = BeautifulSoup(data, parse_only = self.only_tables_tags)
|
||||||
html = BeautifulSoup(data, parse_only = tables)
|
|
||||||
result_table = html.find_all('h2', text = re.compile(movie_name))
|
result_table = html.find_all('h2', text = re.compile(movie_name))
|
||||||
|
|
||||||
for h2 in result_table:
|
for h2 in result_table:
|
||||||
@@ -90,8 +90,7 @@ class HDTrailers(TrailerProvider):
|
|||||||
|
|
||||||
results = {'480p':[], '720p':[], '1080p':[]}
|
results = {'480p':[], '720p':[], '1080p':[]}
|
||||||
try:
|
try:
|
||||||
tables = SoupStrainer('table')
|
html = BeautifulSoup(data, parse_only = self.only_tables_tags)
|
||||||
html = BeautifulSoup(data, parse_only = tables)
|
|
||||||
result_table = html.find('table', attrs = {'class':'bottomTable'})
|
result_table = html.find('table', attrs = {'class':'bottomTable'})
|
||||||
|
|
||||||
for tr in result_table.find_all('tr'):
|
for tr in result_table.find_all('tr'):
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from couchpotato.core.media._base.providers.userscript.base import UserscriptBase
|
||||||
|
|
||||||
|
autoload = 'Filmstarts'
|
||||||
|
|
||||||
|
|
||||||
|
class Filmstarts(UserscriptBase):
|
||||||
|
|
||||||
|
includes = ['*://www.filmstarts.de/kritiken/*']
|
||||||
|
|
||||||
|
def getMovie(self, url):
|
||||||
|
try:
|
||||||
|
data = self.getUrl(url)
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
html = BeautifulSoup(data)
|
||||||
|
table = html.find("table", attrs={"class": "table table-standard thead-standard table-striped_2 fs11"})
|
||||||
|
|
||||||
|
if table.find(text='Originaltitel'):
|
||||||
|
# Get original film title from the table specified above
|
||||||
|
name = table.find("div", text="Originaltitel").parent.parent.parent.td.text
|
||||||
|
else:
|
||||||
|
# If none is available get the title from the meta data
|
||||||
|
name = html.find("meta", {"property":"og:title"})['content']
|
||||||
|
|
||||||
|
# Year of production is not available in the meta data, so get it from the table
|
||||||
|
year = table.find(text="Produktionsjahr").parent.parent.next_sibling.text
|
||||||
|
|
||||||
|
return self.search(name, year)
|
||||||
Regular → Executable
+114
-65
@@ -58,13 +58,13 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
|
|
||||||
def searchAllView(self, **kwargs):
|
def searchAllView(self, **kwargs):
|
||||||
|
|
||||||
fireEventAsync('movie.searcher.all')
|
fireEventAsync('movie.searcher.all', manual = True)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': not self.in_progress
|
'success': not self.in_progress
|
||||||
}
|
}
|
||||||
|
|
||||||
def searchAll(self):
|
def searchAll(self, manual = False):
|
||||||
|
|
||||||
if self.in_progress:
|
if self.in_progress:
|
||||||
log.info('Search already in progress')
|
log.info('Search already in progress')
|
||||||
@@ -74,7 +74,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
self.in_progress = True
|
self.in_progress = True
|
||||||
fireEvent('notify.frontend', type = 'movie.searcher.started', data = True, message = 'Full search started')
|
fireEvent('notify.frontend', type = 'movie.searcher.started', data = True, message = 'Full search started')
|
||||||
|
|
||||||
medias = [x['_id'] for x in fireEvent('media.with_status', 'active', with_doc = False, single = True)]
|
medias = [x['_id'] for x in fireEvent('media.with_status', 'active', types = 'movie', with_doc = False, single = True)]
|
||||||
random.shuffle(medias)
|
random.shuffle(medias)
|
||||||
|
|
||||||
total = len(medias)
|
total = len(medias)
|
||||||
@@ -89,12 +89,13 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
for media_id in medias:
|
for media_id in medias:
|
||||||
|
|
||||||
media = fireEvent('media.get', media_id, single = True)
|
media = fireEvent('media.get', media_id, single = True)
|
||||||
|
if not media: continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.single(media, search_protocols)
|
self.single(media, search_protocols, manual = manual)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
log.error('Forcing library update for %s, if you see this often, please report: %s', (getIdentifier(media), traceback.format_exc()))
|
log.error('Forcing library update for %s, if you see this often, please report: %s', (getIdentifier(media), traceback.format_exc()))
|
||||||
fireEvent('movie.update_info', media_id)
|
fireEvent('movie.update', media_id)
|
||||||
except:
|
except:
|
||||||
log.error('Search failed for %s: %s', (getIdentifier(media), traceback.format_exc()))
|
log.error('Search failed for %s: %s', (getIdentifier(media), traceback.format_exc()))
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
|
|
||||||
self.in_progress = False
|
self.in_progress = False
|
||||||
|
|
||||||
def single(self, movie, search_protocols = None, manual = False):
|
def single(self, movie, search_protocols = None, manual = False, force_download = False):
|
||||||
|
|
||||||
# Find out search type
|
# Find out search type
|
||||||
try:
|
try:
|
||||||
@@ -120,92 +121,138 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
|
|
||||||
if not movie['profile_id'] or (movie['status'] == 'done' and not manual):
|
if not movie['profile_id'] or (movie['status'] == 'done' and not manual):
|
||||||
log.debug('Movie doesn\'t have a profile or already done, assuming in manage tab.')
|
log.debug('Movie doesn\'t have a profile or already done, assuming in manage tab.')
|
||||||
|
fireEvent('media.restatus', movie['_id'], single = True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
default_title = getTitle(movie)
|
||||||
|
if not default_title:
|
||||||
|
log.error('No proper info found for movie, removing it from library to stop it from causing more issues.')
|
||||||
|
fireEvent('media.delete', movie['_id'], single = True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update media status and check if it is still not done (due to the stop searching after feature
|
||||||
|
if fireEvent('media.restatus', movie['_id'], single = True) == 'done':
|
||||||
|
log.debug('No better quality found, marking movie %s as done.', default_title)
|
||||||
|
|
||||||
pre_releases = fireEvent('quality.pre_releases', single = True)
|
pre_releases = fireEvent('quality.pre_releases', single = True)
|
||||||
release_dates = fireEvent('movie.update_release_dates', movie['_id'], merge = True)
|
release_dates = fireEvent('movie.update_release_dates', movie['_id'], merge = True)
|
||||||
|
|
||||||
found_releases = []
|
found_releases = []
|
||||||
|
previous_releases = movie.get('releases', [])
|
||||||
too_early_to_search = []
|
too_early_to_search = []
|
||||||
|
outside_eta_results = 0
|
||||||
default_title = getTitle(movie)
|
always_search = self.conf('always_search')
|
||||||
if not default_title:
|
ignore_eta = manual
|
||||||
log.error('No proper info found for movie, removing it from library to cause it from having more issues.')
|
total_result_count = 0
|
||||||
fireEvent('media.delete', movie['_id'], single = True)
|
|
||||||
return
|
|
||||||
|
|
||||||
fireEvent('notify.frontend', type = 'movie.searcher.started', data = {'_id': movie['_id']}, message = 'Searching for "%s"' % default_title)
|
fireEvent('notify.frontend', type = 'movie.searcher.started', data = {'_id': movie['_id']}, message = 'Searching for "%s"' % default_title)
|
||||||
|
|
||||||
|
# Ignore eta once every 7 days
|
||||||
|
if not always_search:
|
||||||
|
prop_name = 'last_ignored_eta.%s' % movie['_id']
|
||||||
|
last_ignored_eta = float(Env.prop(prop_name, default = 0))
|
||||||
|
if last_ignored_eta < time.time() - 604800:
|
||||||
|
ignore_eta = True
|
||||||
|
Env.prop(prop_name, value = time.time())
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
||||||
profile = db.get('id', movie['profile_id'])
|
profile = db.get('id', movie['profile_id'])
|
||||||
quality_order = fireEvent('quality.order', single = True)
|
|
||||||
|
|
||||||
ret = False
|
ret = False
|
||||||
|
|
||||||
index = 0
|
for index, q_identifier in enumerate(profile.get('qualities', [])):
|
||||||
for q_identifier in profile.get('qualities'):
|
|
||||||
quality_custom = {
|
quality_custom = {
|
||||||
|
'index': index,
|
||||||
'quality': q_identifier,
|
'quality': q_identifier,
|
||||||
'finish': profile['finish'][index],
|
'finish': profile['finish'][index],
|
||||||
'wait_for': profile['wait_for'][index],
|
'wait_for': tryInt(profile['wait_for'][index]),
|
||||||
'3d': profile['3d'][index] if profile.get('3d') else False
|
'3d': profile['3d'][index] if profile.get('3d') else False
|
||||||
}
|
}
|
||||||
|
|
||||||
index += 1
|
could_not_be_released = not self.couldBeReleased(q_identifier in pre_releases, release_dates, movie['info']['year'])
|
||||||
|
if not always_search and could_not_be_released:
|
||||||
if not self.conf('always_search') and not self.couldBeReleased(q_identifier in pre_releases, release_dates, movie['info']['year']):
|
|
||||||
too_early_to_search.append(q_identifier)
|
too_early_to_search.append(q_identifier)
|
||||||
continue
|
|
||||||
|
# Skip release, if ETA isn't ignored
|
||||||
|
if not ignore_eta:
|
||||||
|
continue
|
||||||
|
|
||||||
has_better_quality = 0
|
has_better_quality = 0
|
||||||
|
|
||||||
# See if better quality is available
|
# See if better quality is available
|
||||||
for release in movie.get('releases', []):
|
for release in movie.get('releases', []):
|
||||||
if quality_order.index(release['quality']) <= quality_order.index(q_identifier) and release['status'] not in ['available', 'ignored', 'failed']:
|
if release['status'] not in ['available', 'ignored', 'failed']:
|
||||||
has_better_quality += 1
|
is_higher = fireEvent('quality.ishigher', \
|
||||||
|
{'identifier': q_identifier, 'is_3d': quality_custom.get('3d', 0)}, \
|
||||||
|
{'identifier': release['quality'], 'is_3d': release.get('is_3d', 0)}, \
|
||||||
|
profile, single = True)
|
||||||
|
if is_higher != 'higher':
|
||||||
|
has_better_quality += 1
|
||||||
|
|
||||||
# Don't search for quality lower then already available.
|
# Don't search for quality lower then already available.
|
||||||
if has_better_quality is 0:
|
if has_better_quality > 0:
|
||||||
|
|
||||||
quality = fireEvent('quality.single', identifier = q_identifier, single = True)
|
|
||||||
log.info('Search for %s in %s', (default_title, quality['label']))
|
|
||||||
|
|
||||||
# Extend quality with profile customs
|
|
||||||
quality['custom'] = quality_custom
|
|
||||||
|
|
||||||
results = fireEvent('searcher.search', search_protocols, movie, quality, single = True) or []
|
|
||||||
if len(results) == 0:
|
|
||||||
log.debug('Nothing found for %s in %s', (default_title, quality['label']))
|
|
||||||
|
|
||||||
# Check if movie isn't deleted while searching
|
|
||||||
if not fireEvent('media.get', movie.get('_id'), single = True):
|
|
||||||
break
|
|
||||||
|
|
||||||
# Add them to this movie releases list
|
|
||||||
found_releases += fireEvent('release.create_from_search', results, movie, quality, single = True)
|
|
||||||
|
|
||||||
# Try find a valid result and download it
|
|
||||||
if fireEvent('release.try_download_result', results, movie, quality_custom, manual, single = True):
|
|
||||||
ret = True
|
|
||||||
|
|
||||||
# Remove releases that aren't found anymore
|
|
||||||
for release in movie.get('releases', []):
|
|
||||||
if release.get('status') == 'available' and release.get('identifier') not in found_releases:
|
|
||||||
fireEvent('release.delete', release.get('_id'), single = True)
|
|
||||||
|
|
||||||
else:
|
|
||||||
log.info('Better quality (%s) already available or snatched for %s', (q_identifier, default_title))
|
log.info('Better quality (%s) already available or snatched for %s', (q_identifier, default_title))
|
||||||
fireEvent('media.restatus', movie['_id'])
|
fireEvent('media.restatus', movie['_id'], single = True)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
quality = fireEvent('quality.single', identifier = q_identifier, single = True)
|
||||||
|
log.info('Search for %s in %s%s', (default_title, quality['label'], ' ignoring ETA' if always_search or ignore_eta else ''))
|
||||||
|
|
||||||
|
# Extend quality with profile customs
|
||||||
|
quality['custom'] = quality_custom
|
||||||
|
|
||||||
|
results = fireEvent('searcher.search', search_protocols, movie, quality, single = True) or []
|
||||||
|
results_count = len(results)
|
||||||
|
total_result_count += results_count
|
||||||
|
if results_count == 0:
|
||||||
|
log.debug('Nothing found for %s in %s', (default_title, quality['label']))
|
||||||
|
|
||||||
|
# Keep track of releases found outside ETA window
|
||||||
|
outside_eta_results += results_count if could_not_be_released else 0
|
||||||
|
|
||||||
|
# Check if movie isn't deleted while searching
|
||||||
|
if not fireEvent('media.get', movie.get('_id'), single = True):
|
||||||
|
break
|
||||||
|
|
||||||
|
# Add them to this movie releases list
|
||||||
|
found_releases += fireEvent('release.create_from_search', results, movie, quality, single = True)
|
||||||
|
|
||||||
|
# Don't trigger download, but notify user of available releases
|
||||||
|
if could_not_be_released:
|
||||||
|
if results_count > 0:
|
||||||
|
log.debug('Found %s releases for "%s", but ETA isn\'t correct yet.', (results_count, default_title))
|
||||||
|
|
||||||
|
# Try find a valid result and download it
|
||||||
|
if (force_download or not could_not_be_released or always_search) and fireEvent('release.try_download_result', results, movie, quality_custom, single = True):
|
||||||
|
ret = True
|
||||||
|
|
||||||
|
# Remove releases that aren't found anymore
|
||||||
|
temp_previous_releases = []
|
||||||
|
for release in previous_releases:
|
||||||
|
if release.get('status') == 'available' and release.get('identifier') not in found_releases:
|
||||||
|
fireEvent('release.delete', release.get('_id'), single = True)
|
||||||
|
else:
|
||||||
|
temp_previous_releases.append(release)
|
||||||
|
previous_releases = temp_previous_releases
|
||||||
|
del temp_previous_releases
|
||||||
|
|
||||||
# Break if CP wants to shut down
|
# Break if CP wants to shut down
|
||||||
if self.shuttingDown() or ret:
|
if self.shuttingDown() or ret:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if total_result_count > 0:
|
||||||
|
fireEvent('media.tag', movie['_id'], 'recent', update_edited = True, single = True)
|
||||||
|
|
||||||
if len(too_early_to_search) > 0:
|
if len(too_early_to_search) > 0:
|
||||||
log.info2('Too early to search for %s, %s', (too_early_to_search, default_title))
|
log.info2('Too early to search for %s, %s', (too_early_to_search, default_title))
|
||||||
|
|
||||||
|
if outside_eta_results > 0:
|
||||||
|
message = 'Found %s releases for "%s" before ETA. Select and download via the dashboard.' % (outside_eta_results, default_title)
|
||||||
|
log.info(message)
|
||||||
|
|
||||||
|
if not manual:
|
||||||
|
fireEvent('media.available', message = message, data = {})
|
||||||
|
|
||||||
fireEvent('notify.frontend', type = 'movie.searcher.ended', data = {'_id': movie['_id']})
|
fireEvent('notify.frontend', type = 'movie.searcher.ended', data = {'_id': movie['_id']})
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
@@ -230,8 +277,9 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
preferred_quality = quality if quality else fireEvent('quality.single', identifier = quality['identifier'], single = True)
|
preferred_quality = quality if quality else fireEvent('quality.single', identifier = quality['identifier'], single = True)
|
||||||
|
|
||||||
# Contains lower quality string
|
# Contains lower quality string
|
||||||
if fireEvent('searcher.contains_other_quality', nzb, movie_year = media['info']['year'], preferred_quality = preferred_quality, single = True):
|
contains_other = fireEvent('searcher.contains_other_quality', nzb, movie_year = media['info']['year'], preferred_quality = preferred_quality, single = True)
|
||||||
log.info2('Wrong: %s, looking for %s', (nzb['name'], quality['label']))
|
if contains_other and isinstance(contains_other, dict):
|
||||||
|
log.info2('Wrong: %s, looking for %s, found %s', (nzb['name'], quality['label'], [x for x in contains_other] if contains_other else 'no quality'))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Contains lower quality string
|
# Contains lower quality string
|
||||||
@@ -288,7 +336,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
now_year = date.today().year
|
now_year = date.today().year
|
||||||
now_month = date.today().month
|
now_month = date.today().month
|
||||||
|
|
||||||
if (year is None or year < now_year - 1) and (not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0)):
|
if (year is None or year < now_year - 1 or (year <= now_year - 1 and now_month > 4)) and (not dates or (dates.get('theater', 0) == 0 and dates.get('dvd', 0) == 0)):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
|
||||||
@@ -325,25 +373,26 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
|
|
||||||
def tryNextReleaseView(self, media_id = None, **kwargs):
|
def tryNextReleaseView(self, media_id = None, **kwargs):
|
||||||
|
|
||||||
trynext = self.tryNextRelease(media_id, manual = True)
|
trynext = self.tryNextRelease(media_id, manual = True, force_download = True)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': trynext
|
'success': trynext
|
||||||
}
|
}
|
||||||
|
|
||||||
def tryNextRelease(self, media_id, manual = False):
|
def tryNextRelease(self, media_id, manual = False, force_download = False):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db = get_db()
|
|
||||||
rels = fireEvent('media.with_status', ['snatched', 'done'], single = True)
|
rels = fireEvent('release.for_media', media_id, single = True)
|
||||||
|
|
||||||
for rel in rels:
|
for rel in rels:
|
||||||
rel['status'] = 'ignored'
|
if rel.get('status') in ['snatched', 'done']:
|
||||||
db.update(rel)
|
fireEvent('release.update_status', rel.get('_id'), status = 'ignored')
|
||||||
|
|
||||||
movie_dict = fireEvent('media.get', media_id, single = True)
|
media = fireEvent('media.get', media_id, single = True)
|
||||||
log.info('Trying next release for: %s', getTitle(movie_dict))
|
if media:
|
||||||
self.single(movie_dict, manual = manual)
|
log.info('Trying next release for: %s', getTitle(media))
|
||||||
|
self.single(media, manual = manual, force_download = force_download)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
Regular → Executable
+1
-3
@@ -1,4 +1,3 @@
|
|||||||
from couchpotato import get_db
|
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.variable import splitString, removeDuplicate, getIdentifier
|
from couchpotato.core.helpers.variable import splitString, removeDuplicate, getIdentifier
|
||||||
@@ -28,7 +27,7 @@ class Suggestion(Plugin):
|
|||||||
else:
|
else:
|
||||||
|
|
||||||
if not movies or len(movies) == 0:
|
if not movies or len(movies) == 0:
|
||||||
active_movies = fireEvent('media.with_status', ['active', 'done'], single = True)
|
active_movies = fireEvent('media.with_status', ['active', 'done'], types = 'movie', single = True)
|
||||||
movies = [getIdentifier(x) for x in active_movies]
|
movies = [getIdentifier(x) for x in active_movies]
|
||||||
|
|
||||||
if not ignored or len(ignored) == 0:
|
if not ignored or len(ignored) == 0:
|
||||||
@@ -84,7 +83,6 @@ class Suggestion(Plugin):
|
|||||||
|
|
||||||
# Get new results and add them
|
# Get new results and add them
|
||||||
if len(new_suggestions) - 1 < limit:
|
if len(new_suggestions) - 1 < limit:
|
||||||
db = get_db()
|
|
||||||
active_movies = fireEvent('media.with_status', ['active', 'done'], single = True)
|
active_movies = fireEvent('media.with_status', ['active', 'done'], single = True)
|
||||||
movies = [getIdentifier(x) for x in active_movies]
|
movies = [getIdentifier(x) for x in active_movies]
|
||||||
movies.extend(seen)
|
movies.extend(seen)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ var SuggestList = new Class({
|
|||||||
|
|
||||||
Implements: [Options, Events],
|
Implements: [Options, Events],
|
||||||
|
|
||||||
|
shown_once: false,
|
||||||
|
|
||||||
initialize: function(options){
|
initialize: function(options){
|
||||||
var self = this;
|
var self = this;
|
||||||
self.setOptions(options);
|
self.setOptions(options);
|
||||||
@@ -44,12 +46,13 @@ var SuggestList = new Class({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var cookie_menu_select = Cookie.read('suggestions_charts_menu_selected');
|
var cookie_menu_select = Cookie.read('suggestions_charts_menu_selected') || 'suggestions';
|
||||||
if( cookie_menu_select === 'suggestions' || cookie_menu_select === null ) self.el.show(); else self.el.hide();
|
if( cookie_menu_select === 'suggestions')
|
||||||
|
self.show();
|
||||||
|
else
|
||||||
|
self.hide();
|
||||||
|
|
||||||
self.api_request = Api.request('suggestion.view', {
|
self.fireEvent('created');
|
||||||
'onComplete': self.fill.bind(self)
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -145,6 +148,24 @@ var SuggestList = new Class({
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
show: function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.el.show();
|
||||||
|
|
||||||
|
if(!self.shown_once){
|
||||||
|
self.api_request = Api.request('suggestion.view', {
|
||||||
|
'onComplete': self.fill.bind(self)
|
||||||
|
});
|
||||||
|
|
||||||
|
self.shown_once = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function(){
|
||||||
|
this.el.hide();
|
||||||
|
},
|
||||||
|
|
||||||
toElement: function(){
|
toElement: function(){
|
||||||
return this.el;
|
return this.el;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class Notification(Provider):
|
|||||||
test_message = 'ZOMG Lazors Pewpewpew!'
|
test_message = 'ZOMG Lazors Pewpewpew!'
|
||||||
|
|
||||||
listen_to = [
|
listen_to = [
|
||||||
|
'media.available',
|
||||||
'renamer.after', 'movie.snatched',
|
'renamer.after', 'movie.snatched',
|
||||||
'updater.available', 'updater.updated',
|
'updater.available', 'updater.updated',
|
||||||
'core.message.important',
|
'core.message.important',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user