Compare commits
458
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68dcba8853 | ||
|
|
ae8f66df1a | ||
|
|
5237ead5cb | ||
|
|
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 | ||
|
|
c4aaa10308 | ||
|
|
d10536a829 | ||
|
|
1e7fa82e11 | ||
|
|
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 | ||
|
|
fbccba77a7 | ||
|
|
d3efda74b2 | ||
|
|
66b849cb29 | ||
|
|
b19f98ef5b | ||
|
|
c389790cf2 | ||
|
|
d7445dfa80 | ||
|
|
36782768a4 | ||
|
|
2c9d487614 | ||
|
|
b9a724c8bb | ||
|
|
68d826ca1c | ||
|
|
d6921882e1 | ||
|
|
2cfff73486 | ||
|
|
0c7dda8d44 | ||
|
|
dbaa377770 | ||
|
|
47d2b81d1c | ||
|
|
f79fcda27f | ||
|
|
cdbcad2238 | ||
|
|
5d913e87c3 | ||
|
|
16f02bda27 | ||
|
|
8d108b92bf | ||
|
|
46783028b1 | ||
|
|
d08c7c57a8 | ||
|
|
eeeb845ef3 | ||
|
|
651a063f94 | ||
|
|
f20aaa2d9d | ||
|
|
ba925ec191 | ||
|
|
3b7376fd18 | ||
|
|
c31b10c798 | ||
|
|
acda664686 | ||
|
|
e2852407ea | ||
|
|
88e738c6cd | ||
|
|
eaae8bdb0b | ||
|
|
821f68909d | ||
|
|
2b8dfed475 | ||
|
|
607b5ea766 | ||
|
|
88579cd71a | ||
|
|
6c57316ce6 | ||
|
|
6702683da3 | ||
|
|
1ed58586a1 | ||
|
|
f08ccd4fd8 | ||
|
|
312562a9f5 | ||
|
|
9e260a89af | ||
|
|
d233e4d22e | ||
|
|
23893dbcb9 | ||
|
|
506871b506 | ||
|
|
6115917660 | ||
|
|
21df8819d3 | ||
|
|
fb3f3e11f6 | ||
|
|
178c8942c3 | ||
|
|
51e747049d | ||
|
|
0582f7d694 | ||
|
|
fa7cac7538 | ||
|
|
9a314cfbc4 | ||
|
|
5941d0bf77 | ||
|
|
d326c1c25c | ||
|
|
96472a9a8f | ||
|
|
27252561e2 | ||
|
|
c9e732651f | ||
|
|
7849e7170d | ||
|
|
087894eb4e | ||
|
|
25f1b8c7a7 | ||
|
|
e71da1f14d | ||
|
|
938b14ba18 | ||
|
|
d6522d8f38 | ||
|
|
78eab890e7 | ||
|
|
1a56191f83 | ||
|
|
41c0f34d95 | ||
|
|
37bf205d7a | ||
|
|
aa1fa3eb9a | ||
|
|
0e2f8a612c | ||
|
|
465e7b2abc | ||
|
|
578fb45785 | ||
|
|
96995bbbe5 | ||
|
|
4cfdafebbc | ||
|
|
b97acb8ef5 | ||
|
|
d68d2dfdb6 | ||
|
|
39b269a454 | ||
|
|
ac081d3e10 | ||
|
|
5d4efb60cf | ||
|
|
cc408b980c | ||
|
|
59590b3ac9 | ||
|
|
ff759dacf3 | ||
|
|
a328e44130 | ||
|
|
7924cac5f9 | ||
|
|
1cef3b0c93 | ||
|
|
3cd59edc8b | ||
|
|
0d624af01d | ||
|
|
a09132570c | ||
|
|
ee3fc38432 | ||
|
|
dbf0192c8e | ||
|
|
6962cfc3f5 | ||
|
|
e096ec3b5b | ||
|
|
b30a74ae0c | ||
|
|
978eeb16c9 | ||
|
|
e5c9d91657 | ||
|
|
fa81c3a07a | ||
|
|
9cdd520d41 | ||
|
|
55d7898771 | ||
|
|
b8256bef97 | ||
|
|
5be9dc0b4a | ||
|
|
7d0be0cefb | ||
|
|
f7ce1edb13 | ||
|
|
5ad9280b60 | ||
|
|
2b353f1b20 | ||
|
|
75ab90b87b | ||
|
|
0219296120 | ||
|
|
20032b3a31 | ||
|
|
ea9e9a8c90 | ||
|
|
f7b0ee145b | ||
|
|
cc866738ee | ||
|
|
eadccf6e33 | ||
|
|
b70b66e567 | ||
|
|
5b6792dc20 | ||
|
|
f498e7343a | ||
|
|
6962f441e6 | ||
|
|
1def62b1b1 | ||
|
|
a4a4a6a185 | ||
|
|
d4c9469c1a | ||
|
|
3e2d4c5d7b | ||
|
|
d03f711d69 | ||
|
|
44dd8d9b96 | ||
|
|
549a3be0d8 | ||
|
|
1bb2edf8ec | ||
|
|
84c6f36315 |
+11
-6
@@ -19,7 +19,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 +34,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,7 +55,7 @@ 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
|
||||||
@@ -67,10 +72,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 +104,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)
|
||||||
|
|||||||
+232
@@ -0,0 +1,232 @@
|
|||||||
|
from esky.util import appdir_from_executable #@UnresolvedImport
|
||||||
|
from threading import Thread
|
||||||
|
from version import VERSION
|
||||||
|
from wx.lib.softwareupdate import SoftwareUpdate
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import webbrowser
|
||||||
|
import wx
|
||||||
|
|
||||||
|
# Include proper dirs
|
||||||
|
if hasattr(sys, 'frozen'):
|
||||||
|
import libs
|
||||||
|
base_path = os.path.dirname(os.path.dirname(os.path.abspath(libs.__file__)))
|
||||||
|
else:
|
||||||
|
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
lib_dir = os.path.join(base_path, 'libs')
|
||||||
|
|
||||||
|
sys.path.insert(0, base_path)
|
||||||
|
sys.path.insert(0, lib_dir)
|
||||||
|
|
||||||
|
from couchpotato.environment import Env
|
||||||
|
|
||||||
|
class TaskBarIcon(wx.TaskBarIcon):
|
||||||
|
|
||||||
|
TBMENU_OPEN = wx.NewId()
|
||||||
|
TBMENU_SETTINGS = wx.NewId()
|
||||||
|
TBMENU_EXIT = wx.ID_EXIT
|
||||||
|
|
||||||
|
closed = False
|
||||||
|
menu = False
|
||||||
|
enabled = False
|
||||||
|
|
||||||
|
def __init__(self, frame):
|
||||||
|
wx.TaskBarIcon.__init__(self)
|
||||||
|
self.frame = frame
|
||||||
|
|
||||||
|
icon = wx.Icon('icon.png', wx.BITMAP_TYPE_PNG)
|
||||||
|
self.SetIcon(icon)
|
||||||
|
|
||||||
|
self.Bind(wx.EVT_TASKBAR_LEFT_UP, self.OnTaskBarClick)
|
||||||
|
self.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.OnTaskBarClick)
|
||||||
|
|
||||||
|
self.Bind(wx.EVT_MENU, self.onOpen, id = self.TBMENU_OPEN)
|
||||||
|
self.Bind(wx.EVT_MENU, self.onSettings, id = self.TBMENU_SETTINGS)
|
||||||
|
self.Bind(wx.EVT_MENU, self.onTaskBarClose, id = self.TBMENU_EXIT)
|
||||||
|
|
||||||
|
def OnTaskBarClick(self, evt):
|
||||||
|
menu = self.CreatePopupMenu()
|
||||||
|
self.PopupMenu(menu)
|
||||||
|
menu.Destroy()
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
self.enabled = True
|
||||||
|
|
||||||
|
if self.menu:
|
||||||
|
self.open_menu.Enable(True)
|
||||||
|
self.setting_menu.Enable(True)
|
||||||
|
|
||||||
|
self.open_menu.SetText('Open')
|
||||||
|
|
||||||
|
def CreatePopupMenu(self):
|
||||||
|
|
||||||
|
if not self.menu:
|
||||||
|
self.menu = wx.Menu()
|
||||||
|
self.open_menu = self.menu.Append(self.TBMENU_OPEN, 'Open')
|
||||||
|
self.setting_menu = self.menu.Append(self.TBMENU_SETTINGS, 'About')
|
||||||
|
self.exit_menu = self.menu.Append(self.TBMENU_EXIT, 'Quit')
|
||||||
|
|
||||||
|
if not self.enabled:
|
||||||
|
self.open_menu.Enable(False)
|
||||||
|
self.setting_menu.Enable(False)
|
||||||
|
|
||||||
|
self.open_menu.SetText('Loading...')
|
||||||
|
|
||||||
|
return self.menu
|
||||||
|
|
||||||
|
def onOpen(self, event):
|
||||||
|
url = self.frame.parent.getSetting('base_url')
|
||||||
|
webbrowser.open(url)
|
||||||
|
|
||||||
|
def onSettings(self, event):
|
||||||
|
url = self.frame.parent.getSetting('base_url') + 'settings/about/'
|
||||||
|
webbrowser.open(url)
|
||||||
|
|
||||||
|
def onTaskBarClose(self, evt):
|
||||||
|
if self.closed:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.closed = True
|
||||||
|
|
||||||
|
self.RemoveIcon()
|
||||||
|
wx.CallAfter(self.frame.Close)
|
||||||
|
|
||||||
|
|
||||||
|
def makeIcon(self, img):
|
||||||
|
if "wxMSW" in wx.PlatformInfo:
|
||||||
|
img = img.Scale(16, 16)
|
||||||
|
elif "wxGTK" in wx.PlatformInfo:
|
||||||
|
img = img.Scale(22, 22)
|
||||||
|
|
||||||
|
icon = wx.IconFromBitmap(img.CopyFromBitmap())
|
||||||
|
return icon
|
||||||
|
|
||||||
|
|
||||||
|
class MainFrame(wx.Frame):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
wx.Frame.__init__(self, None, style = wx.FRAME_NO_TASKBAR)
|
||||||
|
|
||||||
|
self.parent = parent
|
||||||
|
self.tbicon = TaskBarIcon(self)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkerThread(Thread):
|
||||||
|
|
||||||
|
def __init__(self, desktop):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.daemon = True
|
||||||
|
self._desktop = desktop
|
||||||
|
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
|
||||||
|
# Get options via arg
|
||||||
|
from couchpotato.runner import getOptions
|
||||||
|
args = ['--quiet']
|
||||||
|
self.options = getOptions(args)
|
||||||
|
|
||||||
|
# Load settings
|
||||||
|
settings = Env.get('settings')
|
||||||
|
settings.setFile(self.options.config_file)
|
||||||
|
|
||||||
|
# Create data dir if needed
|
||||||
|
self.data_dir = os.path.expanduser(Env.setting('data_dir'))
|
||||||
|
if self.data_dir == '':
|
||||||
|
from couchpotato.core.helpers.variable import getDataDir
|
||||||
|
self.data_dir = getDataDir()
|
||||||
|
|
||||||
|
if not os.path.isdir(self.data_dir):
|
||||||
|
os.makedirs(self.data_dir)
|
||||||
|
|
||||||
|
# Create logging dir
|
||||||
|
self.log_dir = os.path.join(self.data_dir, 'logs');
|
||||||
|
if not os.path.isdir(self.log_dir):
|
||||||
|
os.mkdir(self.log_dir)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from couchpotato.runner import runCouchPotato
|
||||||
|
runCouchPotato(self.options, base_path, args, data_dir = self.data_dir, log_dir = self.log_dir, Env = Env, desktop = self._desktop)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._desktop.frame.Close()
|
||||||
|
self._desktop.ExitMainLoop()
|
||||||
|
|
||||||
|
|
||||||
|
class CouchPotatoApp(wx.App, SoftwareUpdate):
|
||||||
|
|
||||||
|
settings = {}
|
||||||
|
events = {}
|
||||||
|
restart = False
|
||||||
|
closing = False
|
||||||
|
|
||||||
|
def OnInit(self):
|
||||||
|
|
||||||
|
# Updater
|
||||||
|
base_url = 'https://api.couchpota.to/updates/%s'
|
||||||
|
self.InitUpdates(base_url % VERSION + '/', 'https://couchpota.to/updates/%s' % 'changelog.html',
|
||||||
|
icon = wx.Icon('icon.png'))
|
||||||
|
|
||||||
|
self.frame = MainFrame(self)
|
||||||
|
self.frame.Bind(wx.EVT_CLOSE, self.onClose)
|
||||||
|
|
||||||
|
# CouchPotato thread
|
||||||
|
self.worker = WorkerThread(self)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def onAppLoad(self):
|
||||||
|
self.frame.tbicon.enable()
|
||||||
|
|
||||||
|
def setSettings(self, settings = {}):
|
||||||
|
self.settings = settings
|
||||||
|
|
||||||
|
def getSetting(self, name):
|
||||||
|
return self.settings.get(name)
|
||||||
|
|
||||||
|
def addEvents(self, events = {}):
|
||||||
|
for name in events.iterkeys():
|
||||||
|
self.events[name] = events[name]
|
||||||
|
|
||||||
|
def onClose(self, event):
|
||||||
|
|
||||||
|
if not self.closing:
|
||||||
|
self.closing = True
|
||||||
|
self.frame.tbicon.onTaskBarClose(event)
|
||||||
|
|
||||||
|
onClose = self.events.get('onClose')
|
||||||
|
onClose(event)
|
||||||
|
|
||||||
|
def afterShutdown(self, restart = False):
|
||||||
|
self.frame.Destroy()
|
||||||
|
self.restart = restart
|
||||||
|
self.ExitMainLoop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
app = CouchPotatoApp(redirect = False)
|
||||||
|
app.MainLoop()
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
if app.restart:
|
||||||
|
|
||||||
|
def appexe_from_executable(exepath):
|
||||||
|
appdir = appdir_from_executable(exepath)
|
||||||
|
exename = os.path.basename(exepath)
|
||||||
|
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
if os.path.isdir(os.path.join(appdir, "Contents", "MacOS")):
|
||||||
|
return os.path.join(appdir, "Contents", "MacOS", exename)
|
||||||
|
|
||||||
|
return os.path.join(appdir, exename)
|
||||||
|
|
||||||
|
exe = appexe_from_executable(sys.executable)
|
||||||
|
os.chdir(os.path.dirname(exe))
|
||||||
|
|
||||||
|
os.execv(exe, [exe] + sys.argv[1:])
|
||||||
@@ -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,19 +27,20 @@ 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 (Ubuntu / Debian):
|
||||||
|
|
||||||
* Install [GIT](http://git-scm.com/) with `apt-get install git-core`
|
* Install [GIT](http://git-scm.com/) with `apt-get install git-core`
|
||||||
* '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`
|
* 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`
|
* Copy the default paths file `sudo cp CouchPotatoServer/init/ubuntu.default /etc/default/couchpotato`
|
||||||
* Make it executable. `sudo chmod +x /etc/init.d/couchpotato`
|
* Change the paths inside the default file `sudo nano /etc/default/couchpotato`
|
||||||
* Add it to defaults. `sudo update-rc.d couchpotato defaults`
|
* Make it executable `sudo chmod +x /etc/init.d/couchpotato`
|
||||||
* Open your browser and go to: `http://localhost:5050/`
|
* Add it to defaults `sudo update-rc.d couchpotato defaults`
|
||||||
|
* Open your browser and go to `http://localhost:5050/`
|
||||||
|
|
||||||
|
|
||||||
FreeBSD :
|
FreeBSD :
|
||||||
|
|||||||
+3
-3
@@ -22,11 +22,11 @@ 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!
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
## 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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+45
-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,43 @@ 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:
|
||||||
|
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:
|
||||||
|
|||||||
@@ -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'):
|
||||||
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, ''))
|
||||||
|
|||||||
+120
-26
@@ -3,10 +3,11 @@ import os
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
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
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
@@ -15,19 +16,24 @@ 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('app.migrate', self.migrate)
|
addEvent('app.migrate', self.migrate)
|
||||||
|
addEvent('app.after_shutdown', self.close)
|
||||||
|
|
||||||
def getDB(self):
|
def getDB(self):
|
||||||
|
|
||||||
@@ -37,28 +43,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):
|
||||||
|
|
||||||
@@ -132,20 +160,80 @@ 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()
|
||||||
|
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
|
||||||
@@ -285,13 +373,16 @@ class Database(object):
|
|||||||
for profile_type in types:
|
for profile_type in types:
|
||||||
p_type = types[profile_type]
|
p_type = types[profile_type]
|
||||||
if types[profile_type]['profile_id'] == p['id']:
|
if types[profile_type]['profile_id'] == p['id']:
|
||||||
new_profile['finish'].append(p_type['finish'])
|
if p_type['quality_id']:
|
||||||
new_profile['wait_for'].append(p_type['wait_for'])
|
new_profile['finish'].append(p_type['finish'])
|
||||||
new_profile['qualities'].append(migrate_data['quality'][p_type['quality_id']]['identifier'])
|
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))
|
if len(new_profile['qualities']) > 0:
|
||||||
|
new_profile.update(db.insert(new_profile))
|
||||||
profile_link[x] = new_profile.get('_id')
|
profile_link[x] = new_profile.get('_id')
|
||||||
|
else:
|
||||||
|
log.error('Corrupt profile list for "%s", using default.', p.get('label'))
|
||||||
|
|
||||||
# Qualities
|
# Qualities
|
||||||
log.info('Importing quality sizes')
|
log.info('Importing quality sizes')
|
||||||
@@ -365,10 +456,10 @@ class Database(object):
|
|||||||
m = medias[x]
|
m = medias[x]
|
||||||
|
|
||||||
status = statuses.get(m['status_id']).get('identifier')
|
status = statuses.get(m['status_id']).get('identifier')
|
||||||
l = libraries[m['library_id']]
|
l = libraries.get(m['library_id'])
|
||||||
|
|
||||||
# Only migrate wanted movies, Skip if no identifier present
|
# Only migrate wanted movies, Skip if no identifier present
|
||||||
if not getImdb(l.get('identifier')): continue
|
if not l or not getImdb(l.get('identifier')): continue
|
||||||
|
|
||||||
profile_id = profile_link.get(m['profile_id'])
|
profile_id = profile_link.get(m['profile_id'])
|
||||||
category_id = category_link.get(m['category_id'])
|
category_id = category_link.get(m['category_id'])
|
||||||
@@ -412,7 +503,10 @@ class Database(object):
|
|||||||
empty_info = True
|
empty_info = True
|
||||||
rel['info'] = {}
|
rel['info'] = {}
|
||||||
|
|
||||||
quality = quality_link[rel.get('quality_id')]
|
quality = quality_link.get(rel.get('quality_id'))
|
||||||
|
if not quality:
|
||||||
|
continue
|
||||||
|
|
||||||
release_status = statuses.get(rel.get('status_id')).get('identifier')
|
release_status = statuses.get(rel.get('status_id')).get('identifier')
|
||||||
|
|
||||||
if rel['info'].get('download_id'):
|
if rel['info'].get('download_id'):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -174,8 +174,8 @@ class TransmissionRPC(object):
|
|||||||
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:
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -78,7 +78,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):
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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,69 @@ 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
|
||||||
|
|||||||
@@ -25,6 +25,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))
|
||||||
|
|
||||||
|
|||||||
@@ -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')
|
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())
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
from operator import itemgetter
|
||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from string import ascii_lowercase
|
from string import ascii_lowercase
|
||||||
|
|
||||||
|
from CodernityDB.database import RecordNotFound
|
||||||
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 +12,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 +24,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,
|
||||||
@@ -80,6 +85,8 @@ 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)
|
||||||
|
|
||||||
def refresh(self, id = '', **kwargs):
|
def refresh(self, id = '', **kwargs):
|
||||||
handlers = []
|
handlers = []
|
||||||
@@ -107,8 +114,7 @@ class MediaPlugin(MediaBase):
|
|||||||
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 +126,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:
|
||||||
|
log.error('Media with id "%s" not found', media_id)
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
|
||||||
def getView(self, id = None, **kwargs):
|
def getView(self, id = None, **kwargs):
|
||||||
|
|
||||||
@@ -156,8 +167,15 @@ class MediaPlugin(MediaBase):
|
|||||||
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'])
|
||||||
|
yield doc
|
||||||
|
except RecordNotFound:
|
||||||
|
log.debug('Record not found, skipping: %s', ms['_id'])
|
||||||
|
else:
|
||||||
|
yield ms
|
||||||
|
|
||||||
def withIdentifiers(self, identifiers, with_doc = False):
|
def withIdentifiers(self, identifiers, with_doc = False):
|
||||||
|
|
||||||
@@ -172,7 +190,7 @@ class MediaPlugin(MediaBase):
|
|||||||
|
|
||||||
log.debug('No media found with identifiers: %s', identifiers)
|
log.debug('No media found with identifiers: %s', identifiers)
|
||||||
|
|
||||||
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 +201,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 +229,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)]
|
||||||
@@ -266,6 +292,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')
|
||||||
)
|
)
|
||||||
@@ -361,13 +388,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 +411,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 and media['status'] != 'active') 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)
|
||||||
|
|
||||||
@@ -428,24 +462,72 @@ 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)
|
# Only look at latest added release
|
||||||
|
release = sorted(done_releases, key = itemgetter('last_edit'), reverse = True)[0]
|
||||||
|
|
||||||
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]):
|
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):
|
||||||
move_to_wanted = False
|
m['status'] = 'done'
|
||||||
|
elif previous_status == 'done':
|
||||||
|
m['status'] = 'done'
|
||||||
|
|
||||||
m['status'] = 'active' if move_to_wanted else '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
|
||||||
|
self.tag(media_id, 'recent')
|
||||||
|
|
||||||
|
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):
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = get_db()
|
||||||
|
m = db.get('id', media_id)
|
||||||
|
|
||||||
|
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,16 +183,7 @@ 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:
|
||||||
@@ -191,6 +199,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',
|
||||||
@@ -205,6 +222,7 @@ config = [{
|
|||||||
<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://smackdownonyou.com" target="_blank">SmackDown</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',
|
||||||
@@ -217,7 +235,7 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'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://index.nzbgeek.info, https://smackdownonyou.com, https://www.nzbfinder.ws',
|
||||||
'description': 'The hostname of your newznab provider',
|
'description': 'The hostname of your newznab provider',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ config = [{
|
|||||||
'name': 'nzbindex',
|
'name': 'nzbindex',
|
||||||
'description': 'Free provider, less accurate. See <a href="https://www.nzbindex.com/">NZBIndex</a>',
|
'description': 'Free provider, less accurate. See <a href="https://www.nzbindex.com/">NZBIndex</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAo0lEQVR42t2SQQ2AMBAEcUCwUAv94QMLfHliAQtYqIVawEItYAG6yZFMLkUANNlk79Kbbtp2P1j9uKxVV9VWFeStl+Wh3fWK9hNwEoADZkJtMD49AqS5AUjWGx6A+m+ARICGrM5W+wSTB0gETKzdHZwCEZAJ8PGZQN4AiQAmkR9s06EBAugJiBoAAPFfAQcBgZcIHzwA6TYP4JsXeSg3P9L31w3eksbH3zMb/wAAAABJRU5ErkJggg==',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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,20 @@ 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')
|
||||||
|
|
||||||
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'})
|
||||||
@@ -94,8 +88,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,12 @@ class Base(TorrentMagnetProvider):
|
|||||||
proxy_list = [
|
proxy_list = [
|
||||||
'https://kickass.to',
|
'https://kickass.to',
|
||||||
'http://kickass.pw',
|
'http://kickass.pw',
|
||||||
|
'http://kickassto.come.in',
|
||||||
|
'http://katproxy.ws',
|
||||||
'http://www.kickassunblock.info',
|
'http://www.kickassunblock.info',
|
||||||
'http://www.kickassproxy.info',
|
'http://www.kickassproxy.info',
|
||||||
|
'http://katph.eu',
|
||||||
|
'http://kickassto.come.in',
|
||||||
]
|
]
|
||||||
|
|
||||||
def _search(self, media, quality, results):
|
def _search(self, media, quality, results):
|
||||||
@@ -65,12 +69,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 +87,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 +132,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 +161,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,
|
||||||
|
|||||||
@@ -187,8 +187,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',
|
||||||
|
|||||||
@@ -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://nobay.net',
|
||||||
|
'https://thebay.al',
|
||||||
'https://thepiratebay.se',
|
'https://thepiratebay.se',
|
||||||
'http://pirateproxy.ca',
|
'http://thepiratebay.cd',
|
||||||
'http://tpb.al',
|
'http://thebootlegbay.com',
|
||||||
'http://www.tpb.gr',
|
'http://www.tpb.gr',
|
||||||
'http://bayproxy.me',
|
'http://tpbproxy.co.uk',
|
||||||
'http://proxybay.eu',
|
'http://pirateproxy.in',
|
||||||
'http://www.getpirate.com',
|
'http://www.getpirate.com',
|
||||||
'http://piratebay.io',
|
'http://piratebay.io',
|
||||||
|
'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',
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ class Base(TorrentProvider):
|
|||||||
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',
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ class Base(TorrentProvider):
|
|||||||
'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(result.find_all('td')[7].string),
|
||||||
'leechers': tryInt(result.find_all('td')[7].string),
|
'leechers': tryInt(result.find_all('td')[8].string),
|
||||||
})
|
})
|
||||||
|
|
||||||
except:
|
except:
|
||||||
@@ -80,7 +80,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="https://www.torrentshack.net/">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);
|
||||||
|
|||||||
@@ -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 = {}
|
||||||
|
|||||||
@@ -2,6 +2,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
|
||||||
@@ -90,7 +91,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 +106,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 +118,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 +149,17 @@ 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'
|
release['status'] = 'ignored'
|
||||||
db.update(release)
|
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)
|
||||||
@@ -225,7 +236,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)
|
||||||
|
|
||||||
|
|||||||
@@ -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(),
|
||||||
@@ -252,7 +252,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 +302,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 +684,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 +703,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;
|
||||||
|
|||||||
@@ -136,6 +136,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': {
|
||||||
@@ -158,9 +173,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 +269,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 +319,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,
|
||||||
@@ -28,6 +28,20 @@ config = [{
|
|||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': '(hours)',
|
'description': '(hours)',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'hide_wanted',
|
||||||
|
'default': False,
|
||||||
|
'type': 'bool',
|
||||||
|
'advanced': True,
|
||||||
|
'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.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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,9 +45,13 @@ 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)
|
||||||
|
for chart in charts:
|
||||||
|
chart['hide_wanted'] = self.conf('hide_wanted')
|
||||||
|
chart['hide_library'] = self.conf('hide_library')
|
||||||
self.setCache('charts_cached', charts, timeout = 7200 * tryInt(self.conf('update_interval', default = 12)))
|
self.setCache('charts_cached', charts, timeout = 7200 * tryInt(self.conf('update_interval', default = 12)))
|
||||||
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;
|
||||||
|
|||||||
@@ -22,9 +22,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)
|
||||||
@@ -72,7 +74,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 +91,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
|
||||||
|
|||||||
@@ -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,4 +1,5 @@
|
|||||||
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
|
||||||
@@ -82,6 +83,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 +97,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
|
||||||
|
|||||||
@@ -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': '',
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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://api.fanart.tv/webservice/movie/b28b14e9be662e027cfbc7c3dd600405/%s/JSON/all/1/2'
|
||||||
|
}
|
||||||
|
|
||||||
|
MAX_EXTRAFANART = 20
|
||||||
|
http_time_between_calls = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
addEvent('movie.info', self.getArt, priority = 1)
|
||||||
|
|
||||||
|
def getArt(self, identifier = None, **kwargs):
|
||||||
|
|
||||||
|
log.debug("Getting Extra Artwork from Fanart.tv...")
|
||||||
|
if not identifier:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
images = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = self.urls['api'] % identifier
|
||||||
|
fanart_data = self.getJsonData(url)
|
||||||
|
|
||||||
|
if fanart_data:
|
||||||
|
name, resource = fanart_data.items()[0]
|
||||||
|
log.debug('Found images for %s', name)
|
||||||
|
images = self._parseMovie(resource)
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
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
|
||||||
|
image_urls.append(best.get('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.')
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ class IPTorrents(MovieProvider, Base):
|
|||||||
]
|
]
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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,5 +1,4 @@
|
|||||||
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
|
||||||
@@ -7,6 +6,7 @@ from couchpotato.core.helpers.encoding import tryUrlencode
|
|||||||
from couchpotato.core.helpers.variable import mergeDicts, getTitle
|
from couchpotato.core.helpers.variable import mergeDicts, getTitle
|
||||||
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):
|
||||||
|
|
||||||
@@ -67,8 +68,7 @@ class HDTrailers(TrailerProvider):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tables = SoupStrainer('div')
|
html = BeautifulSoup(data, 'html.parser', 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, 'html.parser', 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("tr", text="Produktionsjahr").parent.parent.parent.td.text
|
||||||
|
|
||||||
|
return self.search(name, year)
|
||||||
@@ -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')
|
||||||
@@ -91,7 +91,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
media = fireEvent('media.get', media_id, single = True)
|
media = fireEvent('media.get', media_id, single = True)
|
||||||
|
|
||||||
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_info', media_id)
|
||||||
@@ -109,7 +109,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 +120,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)
|
alway_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 alway_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 alway_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 alway_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 alway_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', 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 +276,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 != False:
|
||||||
|
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 +335,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,13 +372,13 @@ 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()
|
db = get_db()
|
||||||
@@ -343,7 +390,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
|
|
||||||
movie_dict = fireEvent('media.get', media_id, single = True)
|
movie_dict = fireEvent('media.get', media_id, single = True)
|
||||||
log.info('Trying next release for: %s', getTitle(movie_dict))
|
log.info('Trying next release for: %s', getTitle(movie_dict))
|
||||||
self.single(movie_dict, manual = manual)
|
self.single(movie_dict, manual = manual, force_download = force_download)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
import time
|
|
||||||
|
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
|
||||||
from couchpotato.core.logger import CPLog
|
|
||||||
from couchpotato.core.notifications.base import Notification
|
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
|
||||||
|
|
||||||
autoload = 'Boxcar'
|
|
||||||
|
|
||||||
|
|
||||||
class Boxcar(Notification):
|
|
||||||
|
|
||||||
url = 'https://boxcar.io/devices/providers/7MNNXY3UIzVBwvzkKwkC/notifications'
|
|
||||||
|
|
||||||
def notify(self, message = '', data = None, listener = None):
|
|
||||||
if not data: data = {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
message = message.strip()
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'email': self.conf('email'),
|
|
||||||
'notification[from_screen_name]': self.default_title,
|
|
||||||
'notification[message]': toUnicode(message),
|
|
||||||
'notification[from_remote_service_id]': int(time.time()),
|
|
||||||
}
|
|
||||||
|
|
||||||
self.urlopen(self.url, data = data)
|
|
||||||
except:
|
|
||||||
log.error('Check your email and added services on boxcar.io')
|
|
||||||
return False
|
|
||||||
|
|
||||||
log.info('Boxcar notification successful.')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def isEnabled(self):
|
|
||||||
return super(Boxcar, self).isEnabled() and self.conf('email')
|
|
||||||
|
|
||||||
|
|
||||||
config = [{
|
|
||||||
'name': 'boxcar',
|
|
||||||
'groups': [
|
|
||||||
{
|
|
||||||
'tab': 'notifications',
|
|
||||||
'list': 'notification_providers',
|
|
||||||
'name': 'boxcar',
|
|
||||||
'options': [
|
|
||||||
{
|
|
||||||
'name': 'enabled',
|
|
||||||
'default': 0,
|
|
||||||
'type': 'enabler',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'email',
|
|
||||||
'description': 'Your Boxcar registration emailaddress.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'on_snatch',
|
|
||||||
'default': 0,
|
|
||||||
'type': 'bool',
|
|
||||||
'advanced': True,
|
|
||||||
'description': 'Also send message when movie is snatched.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}]
|
|
||||||
@@ -28,6 +28,7 @@ class CoreNotifier(Notification):
|
|||||||
m_lock = None
|
m_lock = None
|
||||||
|
|
||||||
listen_to = [
|
listen_to = [
|
||||||
|
'media.available',
|
||||||
'renamer.after', 'movie.snatched',
|
'renamer.after', 'movie.snatched',
|
||||||
'updater.available', 'updater.updated',
|
'updater.available', 'updater.updated',
|
||||||
'core.message', 'core.message.important',
|
'core.message', 'core.message.important',
|
||||||
@@ -258,14 +259,14 @@ class CoreNotifier(Notification):
|
|||||||
|
|
||||||
messages = []
|
messages = []
|
||||||
|
|
||||||
# Get unread
|
# Get last message
|
||||||
if init:
|
if init:
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
||||||
notifications = db.all('notification_unread', with_doc = True)
|
notifications = db.all('notification', with_doc = True)
|
||||||
|
|
||||||
for n in notifications:
|
for n in notifications:
|
||||||
if n['doc'].get('time') > (time.time() - 259200):
|
if n['doc'].get('time') > (time.time() - 604800):
|
||||||
messages.append(n['doc'])
|
messages.append(n['doc'])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -122,9 +122,12 @@ var NotificationBase = new Class({
|
|||||||
startPoll: function(){
|
startPoll: function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if(self.stopped || (self.request && self.request.isRunning()))
|
if(self.stopped)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if(self.request && self.request.isRunning())
|
||||||
|
self.request.cancel();
|
||||||
|
|
||||||
self.request = Api.request('nonblock/notification.listener', {
|
self.request = Api.request('nonblock/notification.listener', {
|
||||||
'onSuccess': function(json){
|
'onSuccess': function(json){
|
||||||
self.processData(json, false)
|
self.processData(json, false)
|
||||||
@@ -149,7 +152,7 @@ var NotificationBase = new Class({
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Process data
|
// Process data
|
||||||
if(json){
|
if(json && json.result){
|
||||||
Array.each(json.result, function(result){
|
Array.each(json.result, function(result){
|
||||||
App.trigger(result._t || result.type, [result]);
|
App.trigger(result._t || result.type, [result]);
|
||||||
if(result.message && result.read === undefined && !init)
|
if(result.message && result.read === undefined && !init)
|
||||||
|
|||||||
@@ -22,10 +22,11 @@ class NMJ(Notification):
|
|||||||
|
|
||||||
# noinspection PyMissingConstructor
|
# noinspection PyMissingConstructor
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
addEvent('renamer.after', self.addToLibrary)
|
|
||||||
addApiView(self.testNotifyName(), self.test)
|
addApiView(self.testNotifyName(), self.test)
|
||||||
addApiView('notify.nmj.auto_config', self.autoConfig)
|
addApiView('notify.nmj.auto_config', self.autoConfig)
|
||||||
|
|
||||||
|
addEvent('renamer.after', self.addToLibrary)
|
||||||
|
|
||||||
def autoConfig(self, host = 'localhost', **kwargs):
|
def autoConfig(self, host = 'localhost', **kwargs):
|
||||||
|
|
||||||
mount = ''
|
mount = ''
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ autoload = 'Pushbullet'
|
|||||||
|
|
||||||
class Pushbullet(Notification):
|
class Pushbullet(Notification):
|
||||||
|
|
||||||
url = 'https://api.pushbullet.com/api/%s'
|
url = 'https://api.pushbullet.com/v2/%s'
|
||||||
|
|
||||||
def notify(self, message = '', data = None, listener = None):
|
def notify(self, message = '', data = None, listener = None):
|
||||||
if not data: data = {}
|
if not data: data = {}
|
||||||
@@ -25,11 +25,7 @@ class Pushbullet(Notification):
|
|||||||
|
|
||||||
# Get all the device IDs linked to this user
|
# Get all the device IDs linked to this user
|
||||||
if not len(devices):
|
if not len(devices):
|
||||||
response = self.request('devices')
|
devices = [None]
|
||||||
if not response:
|
|
||||||
return False
|
|
||||||
|
|
||||||
devices += [device.get('id') for device in response['devices']]
|
|
||||||
|
|
||||||
successful = 0
|
successful = 0
|
||||||
for device in devices:
|
for device in devices:
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ autoload = 'Pushover'
|
|||||||
|
|
||||||
class Pushover(Notification):
|
class Pushover(Notification):
|
||||||
|
|
||||||
app_token = 'YkxHMYDZp285L265L3IwH3LmzkTaCy'
|
|
||||||
|
|
||||||
def notify(self, message = '', data = None, listener = None):
|
def notify(self, message = '', data = None, listener = None):
|
||||||
if not data: data = {}
|
if not data: data = {}
|
||||||
@@ -22,7 +21,7 @@ class Pushover(Notification):
|
|||||||
|
|
||||||
api_data = {
|
api_data = {
|
||||||
'user': self.conf('user_key'),
|
'user': self.conf('user_key'),
|
||||||
'token': self.app_token,
|
'token': self.conf('api_token'),
|
||||||
'message': toUnicode(message),
|
'message': toUnicode(message),
|
||||||
'priority': self.conf('priority'),
|
'priority': self.conf('priority'),
|
||||||
'sound': self.conf('sound'),
|
'sound': self.conf('sound'),
|
||||||
@@ -49,7 +48,7 @@ class Pushover(Notification):
|
|||||||
log.error('Pushover auth failed: %s', response.reason)
|
log.error('Pushover auth failed: %s', response.reason)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
log.error('Pushover notification failed.')
|
log.error('Pushover notification failed: %s', request_status)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -70,6 +69,12 @@ config = [{
|
|||||||
'name': 'user_key',
|
'name': 'user_key',
|
||||||
'description': 'Register on pushover.net to get one.'
|
'description': 'Register on pushover.net to get one.'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'api_token',
|
||||||
|
'description': '<a href="https://pushover.net/apps/clone/couchpotato" target="_blank">Register on pushover.net</a> to get one.',
|
||||||
|
'advanced': True,
|
||||||
|
'default': 'YkxHMYDZp285L265L3IwH3LmzkTaCy',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'priority',
|
'name': 'priority',
|
||||||
'default': 0,
|
'default': 0,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
@@ -16,7 +17,8 @@ class Synoindex(Notification):
|
|||||||
index_path = '/usr/syno/bin/synoindex'
|
index_path = '/usr/syno/bin/synoindex'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Synoindex, self).__init__()
|
addApiView(self.testNotifyName(), self.test)
|
||||||
|
|
||||||
addEvent('renamer.after', self.addToLibrary)
|
addEvent('renamer.after', self.addToLibrary)
|
||||||
|
|
||||||
def addToLibrary(self, message = None, group = None):
|
def addToLibrary(self, message = None, group = None):
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from couchpotato.core.helpers.variable import splitString, getTitle
|
|||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
import requests
|
import requests
|
||||||
from requests.packages.urllib3.exceptions import MaxRetryError
|
from requests.packages.urllib3.exceptions import MaxRetryError, ConnectionError
|
||||||
|
|
||||||
|
|
||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
@@ -36,7 +36,7 @@ class XBMC(Notification):
|
|||||||
|
|
||||||
if self.use_json_notifications.get(host):
|
if self.use_json_notifications.get(host):
|
||||||
calls = [
|
calls = [
|
||||||
('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.getNotificationImage('small')}),
|
('GUI.ShowNotification', None, {'title': self.default_title, 'message': message, 'image': self.getNotificationImage('small')}),
|
||||||
]
|
]
|
||||||
|
|
||||||
if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
|
if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
|
||||||
@@ -44,7 +44,7 @@ class XBMC(Notification):
|
|||||||
if not self.conf('force_full_scan') and (self.conf('remote_dir_scan') or socket.getfqdn('localhost') == socket.getfqdn(host.split(':')[0])):
|
if not self.conf('force_full_scan') and (self.conf('remote_dir_scan') or socket.getfqdn('localhost') == socket.getfqdn(host.split(':')[0])):
|
||||||
param = {'directory': data['destination_dir']}
|
param = {'directory': data['destination_dir']}
|
||||||
|
|
||||||
calls.append(('VideoLibrary.Scan', param))
|
calls.append(('VideoLibrary.Scan', None, param))
|
||||||
|
|
||||||
max_successful += len(calls)
|
max_successful += len(calls)
|
||||||
response = self.request(host, calls)
|
response = self.request(host, calls)
|
||||||
@@ -52,7 +52,7 @@ class XBMC(Notification):
|
|||||||
response = self.notifyXBMCnoJSON(host, {'title': self.default_title, 'message': message})
|
response = self.notifyXBMCnoJSON(host, {'title': self.default_title, 'message': message})
|
||||||
|
|
||||||
if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
|
if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
|
||||||
response += self.request(host, [('VideoLibrary.Scan', {})])
|
response += self.request(host, [('VideoLibrary.Scan', None, {})])
|
||||||
max_successful += 1
|
max_successful += 1
|
||||||
|
|
||||||
max_successful += 1
|
max_successful += 1
|
||||||
@@ -75,7 +75,7 @@ class XBMC(Notification):
|
|||||||
|
|
||||||
# XBMC JSON-RPC version request
|
# XBMC JSON-RPC version request
|
||||||
response = self.request(host, [
|
response = self.request(host, [
|
||||||
('JSONRPC.Version', {})
|
('JSONRPC.Version', None, {})
|
||||||
])
|
])
|
||||||
for result in response:
|
for result in response:
|
||||||
if result.get('result') and type(result['result']['version']).__name__ == 'int':
|
if result.get('result') and type(result['result']['version']).__name__ == 'int':
|
||||||
@@ -112,7 +112,7 @@ class XBMC(Notification):
|
|||||||
self.use_json_notifications[host] = True
|
self.use_json_notifications[host] = True
|
||||||
|
|
||||||
# send the text message
|
# send the text message
|
||||||
resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message, 'image': self.getNotificationImage('small')})])
|
resp = self.request(host, [('GUI.ShowNotification', None, {'title':self.default_title, 'message':message, 'image': self.getNotificationImage('small')})])
|
||||||
for r in resp:
|
for r in resp:
|
||||||
if r.get('result') and r['result'] == 'OK':
|
if r.get('result') and r['result'] == 'OK':
|
||||||
log.debug('Message delivered successfully!')
|
log.debug('Message delivered successfully!')
|
||||||
@@ -172,7 +172,7 @@ class XBMC(Notification):
|
|||||||
# manually fake expected response array
|
# manually fake expected response array
|
||||||
return [{'result': 'Error'}]
|
return [{'result': 'Error'}]
|
||||||
|
|
||||||
except (MaxRetryError, requests.exceptions.Timeout):
|
except (MaxRetryError, requests.exceptions.Timeout, ConnectionError):
|
||||||
log.info2('Couldn\'t send request to XBMC, assuming it\'s turned off')
|
log.info2('Couldn\'t send request to XBMC, assuming it\'s turned off')
|
||||||
return [{'result': 'Error'}]
|
return [{'result': 'Error'}]
|
||||||
except:
|
except:
|
||||||
@@ -184,12 +184,13 @@ class XBMC(Notification):
|
|||||||
|
|
||||||
data = []
|
data = []
|
||||||
for req in do_requests:
|
for req in do_requests:
|
||||||
method, kwargs = req
|
method, id, kwargs = req
|
||||||
|
|
||||||
data.append({
|
data.append({
|
||||||
'method': method,
|
'method': method,
|
||||||
'params': kwargs,
|
'params': kwargs,
|
||||||
'jsonrpc': '2.0',
|
'jsonrpc': '2.0',
|
||||||
'id': method,
|
'id': id if id else method,
|
||||||
})
|
})
|
||||||
data = json.dumps(data)
|
data = json.dumps(data)
|
||||||
|
|
||||||
@@ -223,7 +224,7 @@ config = [{
|
|||||||
'list': 'notification_providers',
|
'list': 'notification_providers',
|
||||||
'name': 'xbmc',
|
'name': 'xbmc',
|
||||||
'label': 'XBMC',
|
'label': 'XBMC',
|
||||||
'description': 'v11 (Eden) and v12 (Frodo)',
|
'description': 'v11 (Eden), v12 (Frodo), v13 (Gotham)',
|
||||||
'options': [
|
'options': [
|
||||||
{
|
{
|
||||||
'name': 'enabled',
|
'name': 'enabled',
|
||||||
@@ -256,7 +257,7 @@ config = [{
|
|||||||
'default': 0,
|
'default': 0,
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': 'Only scan new movie folder at remote XBMC servers. Works if movie location is the same.',
|
'description': ('Only scan new movie folder at remote XBMC servers.', 'Useful if the XBMC path is different from the path CPS uses.'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'force_full_scan',
|
'name': 'force_full_scan',
|
||||||
@@ -264,11 +265,11 @@ config = [{
|
|||||||
'default': 0,
|
'default': 0,
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': 'Do a full scan instead of only the new movie. Useful if the XBMC path is different from the path CPS uses.',
|
'description': ('Do a full scan instead of only the new movie.', 'Useful if the XBMC path is different from the path CPS uses.'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'on_snatch',
|
'name': 'on_snatch',
|
||||||
'default': 0,
|
'default': False,
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'description': 'Also send message when movie is snatched.',
|
'description': 'Also send message when movie is snatched.',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user