diff --git a/.gitignore b/.gitignore index 09fac32b499d391d25586f6decd43122a6bd79cd..881c850920071df24204b18dd7f56ec5aba4aaa8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ config.ini Logs/* sickbeard.db* autoProcessTV/autoProcessTV.cfg +server.crt +server.key # Compiled source # ###################### diff --git a/SickBeard.py b/SickBeard.py index 03c158134e1b1ab7013f979dcc6a6e8e944c3b36..ec3af30f924ea97be50222572ffe4a31efed3bb4 100755 --- a/SickBeard.py +++ b/SickBeard.py @@ -118,6 +118,7 @@ def main(): sickbeard.DATA_DIR = sickbeard.PROG_DIR sickbeard.MY_ARGS = sys.argv[1:] sickbeard.CREATEPID = False + sickbeard.DAEMON = False sickbeard.SYS_ENCODING = None @@ -265,8 +266,6 @@ def main(): else: webhost = '0.0.0.0' - logger.log(u"Starting Sick Beard on http://" + str(webhost) + ":" + str(startPort) + "/") - try: initWebServer({ 'port': startPort, @@ -276,10 +275,13 @@ def main(): 'log_dir': log_dir, 'username': sickbeard.WEB_USERNAME, 'password': sickbeard.WEB_PASSWORD, + 'enable_https': sickbeard.ENABLE_HTTPS, + 'https_cert': sickbeard.HTTPS_CERT, + 'https_key': sickbeard.HTTPS_KEY, }) except IOError: logger.log(u"Unable to start web server, is something else running on port %d?" % startPort, logger.ERROR) - if sickbeard.LAUNCH_BROWSER: + if sickbeard.LAUNCH_BROWSER and not sickbeard.DAEMON: logger.log(u"Launching browser and exiting", logger.ERROR) sickbeard.launchBrowser(startPort) sys.exit() @@ -292,7 +294,7 @@ def main(): sickbeard.start() # launch browser if we're supposed to - if sickbeard.LAUNCH_BROWSER and not noLaunch: + if sickbeard.LAUNCH_BROWSER and not noLaunch and not sickbeard.DAEMON: sickbeard.launchBrowser(startPort) # start an update if we're supposed to @@ -303,7 +305,6 @@ def main(): while (True): if sickbeard.invoked_command: - logger.log(u"Executing invoked command: " + repr(sickbeard.invoked_command)) sickbeard.invoked_command() sickbeard.invoked_command = None diff --git a/autoProcessTV/autoProcessTV.cfg.sample b/autoProcessTV/autoProcessTV.cfg.sample index e194862c911a54332824f69787d1c161d3550f40..15dc900c2d24944e736388a6b36d64fb3691bfbc 100644 --- a/autoProcessTV/autoProcessTV.cfg.sample +++ b/autoProcessTV/autoProcessTV.cfg.sample @@ -3,4 +3,5 @@ host=localhost port=8081 username= password= -web_root= \ No newline at end of file +web_root= +ssl=0 \ No newline at end of file diff --git a/autoProcessTV/autoProcessTV.py b/autoProcessTV/autoProcessTV.py index 974830e7419182254e69cdacd24aacffc214bfcd..413c93ff1b4bf4b29686d4275299eb727be2990d 100644 --- a/autoProcessTV/autoProcessTV.py +++ b/autoProcessTV/autoProcessTV.py @@ -57,6 +57,10 @@ def processEpisode(dirName, nzbName=None): port = config.get("SickBeard", "port") username = config.get("SickBeard", "username") password = config.get("SickBeard", "password") + try: + ssl = int(config.get("SickBeard", "ssl")) + except (ConfigParser.NoOptionError, ValueError): + ssl = 0 try: web_root = config.get("SickBeard", "web_root") @@ -73,7 +77,12 @@ def processEpisode(dirName, nzbName=None): myOpener = AuthURLOpener(username, password) - url = "http://" + host + ":" + port + web_root + "/home/postprocess/processEpisode?" + urllib.urlencode(params) + if ssl: + protocol = "https://" + else: + protocol = "http://" + + url = protocol + host + ":" + port + web_root + "/home/postprocess/processEpisode?" + urllib.urlencode(params) print "Opening URL:", url diff --git a/data/css/config.css b/data/css/config.css index 4412896544e18e0878095a54db0bf8419edc6266..0898cc29627299b1c89c926c443249ae7c280f75 100644 --- a/data/css/config.css +++ b/data/css/config.css @@ -14,7 +14,7 @@ #config-components{float:left;width:auto;} #config-components-border{float:left;width:auto;border-top:1px solid #999;padding:5px 0;} #config .title-group{border-bottom:1px dotted #666;position:relative;padding:25px 15px 25px;} -#config .component-group{border-bottom:1px dotted #666;position:relative;padding:15px 15px 25px;} +#config .component-group{border-top:1px dotted #666;position:relative;padding:15px 15px 25px;} #config .component-group-desc{float:left;width:235px;} #config .component-group-desc h3{font-size:1.5em;} #config .component-group-desc p{width:85%;font-size:1.2em;color:#666;margin:.8em 0;} diff --git a/data/css/default.css b/data/css/default.css index a90a3a2a7605c5a4fbb2f6a21fbf8e7d0cb27904..1aa00054414543fd98ea314e819bbd2075059a76 100644 --- a/data/css/default.css +++ b/data/css/default.css @@ -60,11 +60,12 @@ text-align:left; font-size:21px; line-height:23px; font-weight:400; +} +h1.title { padding-bottom:4px; margin-bottom:12px; border-bottom:1px solid #4e4e4e; } - h1 a { text-decoration:none; } diff --git a/data/images/notifiers/nma.jpg b/data/images/notifiers/nma.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b22ba96fec2323b557eede65cec0d8d7e16a0a99 Binary files /dev/null and b/data/images/notifiers/nma.jpg differ diff --git a/data/images/notifiers/pytivo.gif b/data/images/notifiers/pytivo.gif new file mode 100644 index 0000000000000000000000000000000000000000..121e4fde799bc185ed950a4bfd5780401a31ba47 Binary files /dev/null and b/data/images/notifiers/pytivo.gif differ diff --git a/data/images/providers/btn.gif b/data/images/providers/btn.gif new file mode 100644 index 0000000000000000000000000000000000000000..7164eb61adbbcdca32818d35fb76712c55637c79 Binary files /dev/null and b/data/images/providers/btn.gif differ diff --git a/data/interfaces/default/config_general.tmpl b/data/interfaces/default/config_general.tmpl index a958356a407ee5038cf2adf4196884b0f46aa393..8eb0e30187d0f4da3569a00b8447a6f3fe7c8484 100644 --- a/data/interfaces/default/config_general.tmpl +++ b/data/interfaces/default/config_general.tmpl @@ -68,10 +68,11 @@ <div class="component-group-desc"> <h3>Web Interface</h3> <p>It is recommended that you enable a username and password to secure Sick Beard from being tampered with remotely.</p> - <p><b>Some options may require a manual restart to take effect.</b></p> + <p><b>These options require a manual restart to take effect.</b></p> </div> <fieldset class="component-group-list"> + <div class="field-pair"> <input type="checkbox" name="web_ipv6" id="web_ipv6" #if $sickbeard.WEB_IPV6 then "checked=\"checked\"" else ""#/> <label class="clearfix" for="web_ipv6"> @@ -121,11 +122,43 @@ </label> </div> + <div class="field-pair"> + <label class="clearfix"> + <input type="checkbox" name="enable_https" class="enabler" id="enable_https" #if $sickbeard.ENABLE_HTTPS then "checked=\"checked\"" else ""#/> + <span class="component-title">Enable HTTPS</span> + <span class="component-desc">Enable accessing the interface from a HTTPS address.</span> + </label> + </div> + + <div id="content_enable_https"> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">HTTPS Certificate</span> + <input type="text" name="https_cert" value="$sickbeard.HTTPS_CERT" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">File name or path to HTTPS Certificate.</span> + </label> + </div> + + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">HTTPS Key</span> + <input type="text" name="https_key" value="$sickbeard.HTTPS_KEY" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">File name or path to HTTPS Key.</span> + </label> + </div> + </div> + <input type="submit" class="config_submitter" value="Save Changes" /> </fieldset> </div><!-- /component-group2 //--> - <div id="core-component-group3" class="component-group clearfix"> + <div id="core-component-group4" class="component-group clearfix"> <div class="component-group-desc"> <h3>API</h3> @@ -157,7 +190,7 @@ <input type="submit" class="config_submitter" value="Save Changes" /> </fieldset> - </div><!-- /component-group3 //--> + </div><!-- /component-group4 //--> <br/><input type="submit" class="config_submitter" value="Save Changes" /><br/> </div><!-- /config-components --> diff --git a/data/interfaces/default/config_notifications.tmpl b/data/interfaces/default/config_notifications.tmpl index 980a999b88fed29c42f7bee9dbae3decc58d966d..aa71ebd1f302a5d39ef04f7e1ca988a9148a69b9 100755 --- a/data/interfaces/default/config_notifications.tmpl +++ b/data/interfaces/default/config_notifications.tmpl @@ -11,19 +11,21 @@ <script type="text/javascript" src="$sbRoot/js/config.js"></script> <div id="config"> -<div id="config-content"> + <div id="config-content"> + <form id="configForm" action="saveNotifications" method="post"> + <div id="config-components"> -<form id="configForm" action="saveNotifications" method="post"> - <div id="config-components"> + <br /> + <h1>Home Theater</h1> + <br /> - <div id="core-component-group1" class="component-group clearfix"> + <div class="component-group clearfix"> <div class="component-group-desc"> - <h3><a href="http://xbmc.org/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/xbmc.gif" alt="XBMC" title="XBMC" width="16" height="16" /> XBMC</a></h3> + <h3><a href="http://xbmc.org/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/xbmc.gif" alt="" title="XBMC" width="16" height="16" /> XBMC </a></h3> <p>A free and open source cross-platform media center and home entertainment system software with a 10-foot user interface designed for the living-room TV.</p> </div> - <fieldset class="component-group-list"> <div class="field-pair"> <input type="checkbox" class="enabler" name="use_xbmc" id="use_xbmc" #if $sickbeard.USE_XBMC then "checked=\"checked\"" else ""# /> @@ -34,91 +36,82 @@ </div> <div id="content_use_xbmc"> - <div class="field-pair"> - <input type="checkbox" name="xbmc_notify_onsnatch" id="xbmc_notify_onsnatch" #if $sickbeard.XBMC_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="xbmc_notify_onsnatch"> - <span class="component-title">Notify on Snatch</span> - <span class="component-desc">Send notification when we start a download?</span> - </label> - </div> - - <div class="field-pair"> - <input type="checkbox" name="xbmc_notify_ondownload" id="xbmc_notify_ondownload" #if $sickbeard.XBMC_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="xbmc_notify_ondownload"> - <span class="component-title">Notify on Download</span> - <span class="component-desc">Send notification when we finish a download?</span> - </label> - </div> - - <div class="field-pair"> - <input type="checkbox" name="xbmc_update_library" id="xbmc_update_library" #if $sickbeard.XBMC_UPDATE_LIBRARY then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="xbmc_update_library"> - <span class="component-title">Update Library</span> - <span class="component-desc">Update XBMC library when we finish a download?</span> - </label> - </div> - - <div class="field-pair"> - <input type="checkbox" name="xbmc_update_full" id="xbmc_update_full" #if $sickbeard.XBMC_UPDATE_FULL then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="xbmc_update_full"> - <span class="component-title">Full Library Update</span> - <span class="component-desc">Do a full library update if per-show fails?</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">XBMC IP:Port</span> - <input type="text" name="xbmc_host" id="xbmc_host" value="$sickbeard.XBMC_HOST" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Host running XBMC (eg. 192.168.1.100:8080)</span> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">(multiple host strings can be separated by commas)</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">XBMC Username</span> - <input type="text" name="xbmc_username" id="xbmc_username" value="$sickbeard.XBMC_USERNAME" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Username of your XBMC server (blank for none)</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">XBMC Password</span> - <input type="password" name="xbmc_password" id="xbmc_password" value="$sickbeard.XBMC_PASSWORD" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Password of your XBMC server (blank for none)</span> - </label> - </div> - - <div class="testNotification" id="testXBMC-result">Click below to test.</div> - <input type="button" value="Test XBMC" id="testXBMC" /> - <input type="submit" class="config_submitter" value="Save Changes" /> - - </div><!-- /enabler_xbmc //--> + <div class="field-pair"> + <input type="checkbox" name="xbmc_notify_onsnatch" id="xbmc_notify_onsnatch" #if $sickbeard.XBMC_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="xbmc_notify_onsnatch"> + <span class="component-title">Notify on Snatch</span> + <span class="component-desc">Send notification when we start a download?</span> + </label> + </div> + <div class="field-pair"> + <input type="checkbox" name="xbmc_notify_ondownload" id="xbmc_notify_ondownload" #if $sickbeard.XBMC_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="xbmc_notify_ondownload"> + <span class="component-title">Notify on Download</span> + <span class="component-desc">Send notification when we finish a download?</span> + </label> + </div> + <div class="field-pair"> + <input type="checkbox" name="xbmc_update_library" id="xbmc_update_library" #if $sickbeard.XBMC_UPDATE_LIBRARY then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="xbmc_update_library"> + <span class="component-title">Update Library</span> + <span class="component-desc">Update XBMC library when we finish a download?</span> + </label> + </div> + <div class="field-pair"> + <input type="checkbox" name="xbmc_update_full" id="xbmc_update_full" #if $sickbeard.XBMC_UPDATE_FULL then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="xbmc_update_full"> + <span class="component-title">Full Library Update</span> + <span class="component-desc">Do a full library update if per-show fails?</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">XBMC IP:Port</span> + <input type="text" name="xbmc_host" id="xbmc_host" value="$sickbeard.XBMC_HOST" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Host running XBMC (eg. 192.168.1.100:8080)</span> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">(multiple host strings can be separated by commas)</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">XBMC Username</span> + <input type="text" name="xbmc_username" id="xbmc_username" value="$sickbeard.XBMC_USERNAME" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Username of your XBMC server (blank for none)</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">XBMC Password</span> + <input type="password" name="xbmc_password" id="xbmc_password" value="$sickbeard.XBMC_PASSWORD" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Password of your XBMC server (blank for none)</span> + </label> + </div> + <div class="testNotification" id="testXBMC-result">Click below to test.</div> + <input type="button" value="Test XBMC" id="testXBMC" /> + <input type="submit" class="config_submitter" value="Save Changes" /> + </div><!-- /content_use_xbmc //--> </fieldset> - </div><!-- /component-group //--> + </div><!-- /xbmc component-group //--> - <div id="core-component-group2" class="component-group clearfix"> + <div class="component-group clearfix"> <div class="component-group-desc"> - <h3><a href="http://www.plexapp.com/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/plex.gif" alt="Plex Media Server" title="Plex Media Server" width="16" height="16" /> Plex Media Server</a></h3> + <h3><a href="http://www.plexapp.com/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/plex.gif" alt="" title="Plex Media Server" width="16" height="16" /> Plex Media Server </a></h3> <p>Experience your media on a visually stunning, easy to use interface on your Mac connected to your TV. Your media library has never looked this good!</p> </div> - <fieldset class="component-group-list"> <div class="field-pair"> <input type="checkbox" class="enabler" name="use_plex" id="use_plex" #if $sickbeard.USE_PLEX then "checked=\"checked\"" else ""# /> @@ -129,233 +122,296 @@ </div> <div id="content_use_plex"> - <div class="field-pair"> - <input type="checkbox" name="plex_notify_onsnatch" id="plex_notify_onsnatch" #if $sickbeard.PLEX_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="plex_notify_onsnatch"> - <span class="component-title">Notify on Snatch</span> - <span class="component-desc">Send notification when we start a download?</span> - </label> - </div> + <div class="field-pair"> + <input type="checkbox" name="plex_notify_onsnatch" id="plex_notify_onsnatch" #if $sickbeard.PLEX_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="plex_notify_onsnatch"> + <span class="component-title">Notify on Snatch</span> + <span class="component-desc">Send notification when we start a download?</span> + </label> + </div> + <div class="field-pair"> + <input type="checkbox" name="plex_notify_ondownload" id="plex_notify_ondownload" #if $sickbeard.PLEX_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="plex_notify_ondownload"> + <span class="component-title">Notify on Download</span> + <span class="component-desc">Send notification when we finish a download?</span> + </label> + </div> + <div class="field-pair"> + <input type="checkbox" name="plex_update_library" id="plex_update_library" #if $sickbeard.PLEX_UPDATE_LIBRARY then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="plex_update_library"> + <span class="component-title">Update Library</span> + <span class="component-desc">Update Plex Media Server library when we finish a download?</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Plex Media Server IP:Port</span> + <input type="text" name="plex_server_host" id="plex_server_host" value="$sickbeard.PLEX_SERVER_HOST" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Host running Plex Media Server (eg. 192.168.1.100:32400)</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Plex Client IP:Port</span> + <input type="text" name="plex_host" id="plex_host" value="$sickbeard.PLEX_HOST" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Host running Plex Client (eg. 192.168.1.100:3000)</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Plex Client Username</span> + <input type="text" name="plex_username" id="plex_username" value="$sickbeard.PLEX_USERNAME" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Username of your Plex client API (blank for none)</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Plex Client Password</span> + <input type="password" name="plex_password" id="plex_password" value="$sickbeard.PLEX_PASSWORD" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Password of your Plex client API (blank for none)</span> + </label> + </div> + <div class="testNotification" id="testPLEX-result">Click below to test.</div> + <input type="button" value="Test Plex Media Server" id="testPLEX" /> + <input type="submit" class="config_submitter" value="Save Changes" /> + </div><!-- /content_use_plex --> - <div class="field-pair"> - <input type="checkbox" name="plex_notify_ondownload" id="plex_notify_ondownload" #if $sickbeard.PLEX_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="plex_notify_ondownload"> - <span class="component-title">Notify on Download</span> - <span class="component-desc">Send notification when we finish a download?</span> - </label> - </div> - - <div class="field-pair"> - <input type="checkbox" name="plex_update_library" id="plex_update_library" #if $sickbeard.PLEX_UPDATE_LIBRARY then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="plex_update_library"> - <span class="component-title">Update Library</span> - <span class="component-desc">Update Plex Media Server library when we finish a download?</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Plex Media Server IP:Port</span> - <input type="text" name="plex_server_host" id="plex_server_host" value="$sickbeard.PLEX_SERVER_HOST" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Host running Plex Media Server (eg. 192.168.1.100:32400)</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Plex Client IP:Port</span> - <input type="text" name="plex_host" id="plex_host" value="$sickbeard.PLEX_HOST" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Host running Plex Client (eg. 192.168.1.100:3000)</span> - </label> - </div> + </fieldset> + </div><!-- /plex component-group --> - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Plex Client Username</span> - <input type="text" name="plex_username" id="plex_username" value="$sickbeard.PLEX_USERNAME" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Username of your Plex client API (blank for none)</span> - </label> - </div> + <div class="component-group clearfix"> + <div class="component-group-desc"> + <h3><a href="http://www.popcornhour.com/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/nmj.gif" alt="" title="Networked Media Jukebox" width="16" height="16" /> NMJ </a></h3> + <p>The Networked Media Jukebox, or NMJ, is the official media jukebox interface made available for the Popcorn Hour 200-series.</p> + </div> + <fieldset class="component-group-list"> <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Plex Client Password</span> - <input type="password" name="plex_password" id="plex_password" value="$sickbeard.PLEX_PASSWORD" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Password of your Plex client API (blank for none)</span> + <input type="checkbox" class="enabler" name="use_nmj" id="use_nmj" #if $sickbeard.USE_NMJ then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="use_nmj"> + <span class="component-title">Enable</span> + <span class="component-desc">Should Sick Beard send update commands to NMJ?</span> </label> </div> - <div class="testNotification" id="testPLEX-result">Click below to test.</div> - <input type="button" value="Test Plex Media Server" id="testPLEX" /> - <input type="submit" class="config_submitter" value="Save Changes" /> - - </div><!-- /enabler_plex --> + <div id="content_use_nmj"> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Popcorn IP address</span> + <input type="text" name="nmj_host" id="nmj_host" value="$sickbeard.NMJ_HOST" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">IP of Popcorn 200-series (eg. 192.168.1.100)</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Get Settings</span> + <input type="button" value="Get Settings" id="settingsNMJ" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">The Popcorn Hour device must be powered on and NMJ running.</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">NMJ Database</span> + <input type="text" name="nmj_database" id="nmj_database" value="$sickbeard.NMJ_DATABASE" size="35" #if $sickbeard.NMJ_DATABASE then "readonly=\"readonly\"" else ""# /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Automatically filled via the 'Get Settings' button.</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">NMJ Mount URL</span> + <input type="text" name="nmj_mount" id="nmj_mount" value="$sickbeard.NMJ_MOUNT" size="35" #if $sickbeard.NMJ_MOUNT then "readonly=\"readonly\"" else ""# /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Automatically filled via the 'Get Settings' button.</span> + </label> + </div> + <div class="testNotification" id="testNMJ-result">Click below to test.</div> + <input type="button" value="Test NMJ" id="testNMJ" /> + <input type="submit" class="config_submitter" value="Save Changes" /> + </div><!-- /content_use_nmj //--> </fieldset> - </div><!-- /component-group --> + </div><!-- /nmj component-group //--> - <div id="core-component-group3" class="component-group clearfix"> + <div class="component-group clearfix"> <div class="component-group-desc"> - <h3><a href="http://growl.info/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/growl.gif" alt="Growl" title="Growl" width="16" height="16" /> Growl</a></h3> - <p>A cross-platform unobtrusive global notification system.</p> + <h3><a href="http://synology.com/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/synoindex.gif" alt="" title="Synology Indexer" width="16" height="16" /> Synology Indexer </a></h3> + <p>Synology Indexer is the daemon running on the Synology NAS to build its media database.</p> </div> <fieldset class="component-group-list"> <div class="field-pair"> - <input type="checkbox" class="enabler" name="use_growl" id="use_growl" #if $sickbeard.USE_GROWL then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="use_growl"> + <input type="checkbox" class="enabler" name="use_synoindex" id="use_synoindex" #if $sickbeard.USE_SYNOINDEX then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="use_synoindex"> <span class="component-title">Enable</span> - <span class="component-desc">Should Sick Beard send Growl notifications?</span> - </label> - </div> - - <div id="content_use_growl"> - <div class="field-pair"> - <input type="checkbox" name="growl_notify_onsnatch" id="growl_notify_onsnatch" #if $sickbeard.GROWL_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="growl_notify_onsnatch"> - <span class="component-title">Notify on Snatch</span> - <span class="component-desc">Send notification when we start a download?</span> - </label> - </div> - - <div class="field-pair"> - <input type="checkbox" name="growl_notify_ondownload" id="growl_notify_ondownload" #if $sickbeard.GROWL_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="growl_notify_ondownload"> - <span class="component-title">Notify on Download</span> - <span class="component-desc">Send notification when we finish a download?</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Growl IP:Port</span> - <input type="text" name="growl_host" id="growl_host" value="$sickbeard.GROWL_HOST" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Host running Growl (eg. 192.168.1.100:23053)</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Growl Password</span> - <input type="password" name="growl_password" id="growl_password" value="$sickbeard.GROWL_PASSWORD" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">May leave blank if Sick Beard is on the same host.</span> + <span class="component-desc">Should Sick Beard send notifications to the synoindex daemon?<br /><br /> + </span> </label> - <label class="nocheck clearfix"> + <label class="nocheck clearfix" for="use_synoindex"> <span class="component-title"> </span> - <span class="component-desc">Otherwise Growl <b>requires</b> a password to be used.</span> + <span class="component-desc">Note: Requires SB to be running on your Synology NAS.</span> </label> </div> - <div class="testNotification" id="testGrowl-result">Click below to register and test Growl, this is required for Growl notifications to work.</div> - <input type="button" value="Register Growl" id="testGrowl" /> - <input type="submit" class="config_submitter" value="Save Changes" /> - - </div><!-- /content_use_growl //--> + <div id="content_use_synoindex"> + <input type="submit" class="config_submitter" value="Save Changes" /> + </div><!-- /content_use_pytivo //--> </fieldset> - </div><!-- /component-group //--> + </div><!-- /synoindex component-group //--> - <div id="core-component-group4" class="component-group clearfix"> - + + <div class="component-group clearfix"> <div class="component-group-desc"> - <h3><a href="http://www.twitter.com/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/twitter.gif" alt="Twitter" title="Twitter" width="16" height="16" /> Twitter</a></h3> - <p>A social networking and microblogging service, enabling its users to send and read other users' messages called tweets.</p> + <h3><a href="http://pytivo.sourceforge.net/wiki/index.php/PyTivo" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/pytivo.gif" alt="" title="pyTivo" width="16" height="16" /> pyTivo </a></h3> + <p>pyTivo is both an HMO and GoBack server. This notifier will load the completed downloads to your Tivo.</p> </div> - <fieldset class="component-group-list"> <div class="field-pair"> - <input type="checkbox" class="enabler" name="use_twitter" id="use_twitter" #if $sickbeard.USE_TWITTER then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="use_twitter"> + <input type="checkbox" class="enabler" name="use_pytivo" id="use_pytivo" #if $sickbeard.USE_PYTIVO then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="use_pytivo"> <span class="component-title">Enable</span> - <span class="component-desc">Should Sick Beard post tweets on Twitter?</span> + <span class="component-desc">Should Sick Beard send notifications to pyTivo?<br /><br /></span> </label> - <label class="nocheck clearfix" for="use_twitter"> + <label class="nocheck clearfix" for="use_pytivo"> <span class="component-title"> </span> - <span class="component-desc">You may want to use a second account.</span> - </label> - </div> + <span class="component-desc">Requires the downloaded files to be accessible to pyTivo.</span> + </label> + </div> + + <div id="content_use_pytivo"> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">pyTivo IP:Port</span> + <input type="text" name="pytivo_host" id="pytivo_host" value="$sickbeard.PYTIVO_HOST" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Host running pyTivo (eg. 192.168.1.1:9032)</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">pyTivo Share name</span> + <input type="text" name="pytivo_share_name" id="pytivo_share_name" value="$sickbeard.PYTIVO_SHARE_NAME" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Value used in pyTivo Web Configuration to name the share.</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Tivo Name</span> + <input type="text" name="pytivo_tivo_name" id="pytivo_tivo_name" value="$sickbeard.PYTIVO_TIVO_NAME" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Messages & Settings > Account & System Information > System Information > DVR name</span> + </label> + </div> + <input type="submit" class="config_submitter" value="Save Changes" /> + </div><!-- /content_use_pytivo //--> - <div id="content_use_twitter"> - <div class="field-pair"> - <input type="checkbox" name="twitter_notify_onsnatch" id="twitter_notify_onsnatch" #if $sickbeard.TWITTER_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="twitter_notify_onsnatch"> - <span class="component-title">Notify on Snatch</span> - <span class="component-desc">Send notification when we start a download?</span> - </label> - </div> + </fieldset> + </div><!-- /component-group //--> - <div class="field-pair"> - <input type="checkbox" name="twitter_notify_ondownload" id="twitter_notify_ondownload" #if $sickbeard.TWITTER_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="twitter_notify_ondownload"> - <span class="component-title">Notify on Download</span> - <span class="component-desc">Send notification when we finish a download?</span> - </label> - </div> - <div class="field-pair"> - <label class="clearfix"> - <span class="component-title">Step One</span> - </label> - <label class="nocheck clearfix"> - <span class="component-desc">Click the "Request Authorization" button.<br/> This will open a new page containing an auth key.<br/> Note: if nothing happens check your popup blocker.<br/></span> - <input type="button" value="Request Authorization" id="twitterStep1" /> - </label> - </div> + <br /> + <h1>Devices</h1> + <br /> - <div class="field-pair"> - <label class="clearfix"> - <span class="component-title">Step Two</span> - </label> - <label class="nocheck clearfix"> - <span class="component-desc">Enter the key Twitter gave you below, and click "Verify Key".<br/></span> - <input type="text" id="twitter_key" value="" size="35" /><br /> - </label> - <label class="nocheck clearfix"> - <input type="button" value="Verify Key" id="twitterStep2" /> - </label> - </div> + <div class="component-group clearfix"> + <div class="component-group-desc"> + <h3><a href="http://growl.info/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/growl.gif" alt="" title="Growl" width="16" height="16" /> Growl </a></h3> + <p>A cross-platform unobtrusive global notification system.</p> + </div> + <fieldset class="component-group-list"> <div class="field-pair"> - <label class="clearfix"> - <span class="component-title">Step Three</span> + <input type="checkbox" class="enabler" name="use_growl" id="use_growl" #if $sickbeard.USE_GROWL then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="use_growl"> + <span class="component-title">Enable</span> + <span class="component-desc">Should Sick Beard send Growl notifications?</span> </label> </div> - <div class="testNotification" id="testTwitter-result">Click below to test.</div> - <input type="button" value="Test Twitter" id="testTwitter" /> - <input type="submit" class="config_submitter" value="Save Changes" /> - - </div><!-- /content_use_twitter //--> + <div id="content_use_growl"> + <div class="field-pair"> + <input type="checkbox" name="growl_notify_onsnatch" id="growl_notify_onsnatch" #if $sickbeard.GROWL_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="growl_notify_onsnatch"> + <span class="component-title">Notify on Snatch</span> + <span class="component-desc">Send notification when we start a download?</span> + </label> + </div> + <div class="field-pair"> + <input type="checkbox" name="growl_notify_ondownload" id="growl_notify_ondownload" #if $sickbeard.GROWL_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="growl_notify_ondownload"> + <span class="component-title">Notify on Download</span> + <span class="component-desc">Send notification when we finish a download?</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Growl IP:Port</span> + <input type="text" name="growl_host" id="growl_host" value="$sickbeard.GROWL_HOST" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Host running Growl (eg. 192.168.1.100:23053)</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Growl Password</span> + <input type="password" name="growl_password" id="growl_password" value="$sickbeard.GROWL_PASSWORD" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">May leave blank if Sick Beard is on the same host.</span> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Otherwise Growl <b>requires</b> a password to be used.</span> + </label> + </div> + <div class="testNotification" id="testGrowl-result">Click below to register and test Growl, this is required for Growl notifications to work.</div> + <input type="button" value="Register Growl" id="testGrowl" /> + <input type="submit" class="config_submitter" value="Save Changes" /> + </div><!-- /content_use_growl //--> </fieldset> - </div><!-- /component-group //--> - - <div id="core-component-group5" class="component-group clearfix"> + </div><!-- /growl component-group //--> + + <div class="component-group clearfix"> <div class="component-group-desc"> <h3><a href="http://www.prowlapp.com/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/prowl.gif" alt="Prowl" title="Prowl" width="16" height="16" /> Prowl</a></h3> <p>A Growl client for iOS.</p> </div> - <fieldset class="component-group-list"> <div class="field-pair"> <input type="checkbox" class="enabler" name="use_prowl" id="use_prowl" #if $sickbeard.USE_PROWL then "checked=\"checked\"" else ""# /> @@ -366,67 +422,60 @@ </div> <div id="content_use_prowl"> - <div class="field-pair"> - <input type="checkbox" name="prowl_notify_onsnatch" id="prowl_notify_onsnatch" #if $sickbeard.PROWL_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="prowl_notify_onsnatch"> - <span class="component-title">Notify on Snatch</span> - <span class="component-desc">Send notification when we start a download?</span> - </label> - </div> - - <div class="field-pair"> - <input type="checkbox" name="prowl_notify_ondownload" id="prowl_notify_ondownload" #if $sickbeard.PROWL_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="prowl_notify_ondownload"> - <span class="component-title">Notify on Download</span> - <span class="component-desc">Send notification when we finish a download?</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Prowl API key:</span> - <input type="text" name="prowl_api" id="prowl_api" value="$sickbeard.PROWL_API" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Get your key at: <a href="https://www.prowlapp.com/api_settings.php" onclick="window.open(this.href, '_blank'); return false;">https://www.prowlapp.com/api_settings.php</a></span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Prowl priority:</span> - <select id="prowl_priority" name="prowl_priority"> - <option value="-2" #if $sickbeard.PROWL_PRIORITY == "-2" then 'selected="selected"' else ""#>Very Low</option> - <option value="-1" #if $sickbeard.PROWL_PRIORITY == "-1" then 'selected="selected"' else ""#>Moderate</option> - <option value="0" #if $sickbeard.PROWL_PRIORITY == "0" then 'selected="selected"' else ""#>Normal</option> - <option value="1" #if $sickbeard.PROWL_PRIORITY == "1" then 'selected="selected"' else ""#>High</option> - <option value="2" #if $sickbeard.PROWL_PRIORITY == "2" then 'selected="selected"' else ""#>Emergency</option> - </select> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Priority of Prowl messages from Sick-Beard.</span> - </label> - </div> - - <div class="testNotification" id="testProwl-result">Click below to test.</div> - <input type="button" value="Test Prowl" id="testProwl" /> - <input type="submit" class="config_submitter" value="Save Changes" /> - + <div class="field-pair"> + <input type="checkbox" name="prowl_notify_onsnatch" id="prowl_notify_onsnatch" #if $sickbeard.PROWL_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="prowl_notify_onsnatch"> + <span class="component-title">Notify on Snatch</span> + <span class="component-desc">Send notification when we start a download?</span> + </label> + </div> + <div class="field-pair"> + <input type="checkbox" name="prowl_notify_ondownload" id="prowl_notify_ondownload" #if $sickbeard.PROWL_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="prowl_notify_ondownload"> + <span class="component-title">Notify on Download</span> + <span class="component-desc">Send notification when we finish a download?</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Prowl API key:</span> + <input type="text" name="prowl_api" id="prowl_api" value="$sickbeard.PROWL_API" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Get your key at: <a href="https://www.prowlapp.com/api_settings.php" onclick="window.open(this.href, '_blank'); return false;">https://www.prowlapp.com/api_settings.php</a></span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Prowl priority:</span> + <select id="prowl_priority" name="prowl_priority"> + <option value="-2" #if $sickbeard.PROWL_PRIORITY == "-2" then 'selected="selected"' else ""#>Very Low</option> + <option value="-1" #if $sickbeard.PROWL_PRIORITY == "-1" then 'selected="selected"' else ""#>Moderate</option> + <option value="0" #if $sickbeard.PROWL_PRIORITY == "0" then 'selected="selected"' else ""#>Normal</option> + <option value="1" #if $sickbeard.PROWL_PRIORITY == "1" then 'selected="selected"' else ""#>High</option> + <option value="2" #if $sickbeard.PROWL_PRIORITY == "2" then 'selected="selected"' else ""#>Emergency</option> + </select> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Priority of Prowl messages from Sick-Beard.</span> + </label> + </div> + <div class="testNotification" id="testProwl-result">Click below to test.</div> + <input type="button" value="Test Prowl" id="testProwl" /> + <input type="submit" class="config_submitter" value="Save Changes" /> </div><!-- /content_use_prowl //--> </fieldset> - </div><!-- /component-group //--> + </div><!-- /prowl component-group //--> - <div id="core-component-group6" class="component-group clearfix"> - + <div class="component-group clearfix"> <div class="component-group-desc"> - <h3><a href="http://notifo.com/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/notifo.gif" alt="Notifo" title="Notifo" width="16" height="16" /> Notifo</a></h3> + <h3><a href="http://notifo.com/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/notifo.gif" alt="" title="Notifo" width="16" height="16" /> Notifo </a></h3> <p>A platform for push-notifications to either mobile or desktop clients</p> </div> - <fieldset class="component-group-list"> <div class="field-pair"> <input type="checkbox" class="enabler" name="use_notifo" id="use_notifo" #if $sickbeard.USE_NOTIFO then "checked=\"checked\"" else ""# /> @@ -437,61 +486,54 @@ </div> <div id="content_use_notifo"> - <div class="field-pair"> - <input type="checkbox" name="notifo_notify_onsnatch" id="notifo_notify_onsnatch" #if $sickbeard.NOTIFO_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="notifo_notify_onsnatch"> - <span class="component-title">Notify on Snatch</span> - <span class="component-desc">Send notification when we start a download?</span> - </label> - </div> - - <div class="field-pair"> - <input type="checkbox" name="notifo_notify_ondownload" id="notifo_notify_ondownload" #if $sickbeard.NOTIFO_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="notifo_notify_ondownload"> - <span class="component-title">Notify on Download</span> - <span class="component-desc">Send notification when we finish a download?</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Notifo Username</span> - <input type="text" name="notifo_username" id="notifo_username" value="$sickbeard.NOTIFO_USERNAME" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Username of your Notifo account</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Notifo API secret</span> - <input type="password" name="notifo_apisecret" id="notifo_apisecret" value="$sickbeard.NOTIFO_APISECRET" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Get your API key at: <a href="http://notifo.com/user/settings" onclick="window.open(this.href, '_blank'); return false;">http://notifo.com/user/settings</a></span> - </label> - </div> - - <div class="testNotification" id="testNotifo-result">Click below to test.</div> - <input type="button" value="Test Notifo" id="testNotifo" /> - <input type="submit" class="config_submitter" value="Save Changes" /> - + <div class="field-pair"> + <input type="checkbox" name="notifo_notify_onsnatch" id="notifo_notify_onsnatch" #if $sickbeard.NOTIFO_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="notifo_notify_onsnatch"> + <span class="component-title">Notify on Snatch</span> + <span class="component-desc">Send notification when we start a download?</span> + </label> + </div> + <div class="field-pair"> + <input type="checkbox" name="notifo_notify_ondownload" id="notifo_notify_ondownload" #if $sickbeard.NOTIFO_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="notifo_notify_ondownload"> + <span class="component-title">Notify on Download</span> + <span class="component-desc">Send notification when we finish a download?</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Notifo Username</span> + <input type="text" name="notifo_username" id="notifo_username" value="$sickbeard.NOTIFO_USERNAME" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Username of your Notifo account</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Notifo API secret</span> + <input type="password" name="notifo_apisecret" id="notifo_apisecret" value="$sickbeard.NOTIFO_APISECRET" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Get your API key at: <a href="http://notifo.com/user/settings" onclick="window.open(this.href, '_blank'); return false;">http://notifo.com/user/settings</a></span> + </label> + </div> + <div class="testNotification" id="testNotifo-result">Click below to test.</div> + <input type="button" value="Test Notifo" id="testNotifo" /> + <input type="submit" class="config_submitter" value="Save Changes" /> </div><!-- /content_use_notifo //--> </fieldset> - - </div> + </div><!-- /notifo component-group //--> - <div id="core-component-group7" class="component-group clearfix"> + <div class="component-group clearfix"> <div class="component-group-desc"> - <h3><a href="http://library.gnome.org/devel/libnotify/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/libnotify.gif" alt="Libnotify" title="Libnotify" width="16" height="16" /> Libnotify</a></h3> + <h3><a href="http://library.gnome.org/devel/libnotify/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/libnotify.gif" alt="" title="Libnotify" width="16" height="16" /> Libnotify </a></h3> <p>The standard desktop notification API for Linux/*nix systems. This notifier will only function if the pynotify module is installed (Ubuntu/Debian package <a href="apt:python-notify">python-notify</a>).</p> </div> - <fieldset class="component-group-list"> <div class="field-pair"> <input type="checkbox" class="enabler" name="use_libnotify" id="use_libnotify" #if $sickbeard.USE_LIBNOTIFY then "checked=\"checked\"" else ""# /> @@ -502,185 +544,219 @@ </div> <div id="content_use_libnotify"> - <div class="field-pair"> - <input type="checkbox" name="libnotify_notify_onsnatch" id="libnotify_notify_onsnatch" #if $sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="libnotify_notify_onsnatch"> - <span class="component-title">Notify on Snatch</span> - <span class="component-desc">Send notification when we start a download?</span> - </label> - </div> - - <div class="field-pair"> - <input type="checkbox" name="libnotify_notify_ondownload" id="libnotify_notify_ondownload" #if $sickbeard.LIBNOTIFY_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="libnotify_notify_ondownload"> - <span class="component-title">Notify on Download</span> - <span class="component-desc">Send notification when we finish a download?</span> - </label> - </div> - - <div class="testNotification" id="testLibnotify-result">Click below to test.</div> - <input type="button" value="Test Libnotify" id="testLibnotify" /> - <input type="submit" class="config_submitter" value="Save Changes" /> - + <div class="field-pair"> + <input type="checkbox" name="libnotify_notify_onsnatch" id="libnotify_notify_onsnatch" #if $sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="libnotify_notify_onsnatch"> + <span class="component-title">Notify on Snatch</span> + <span class="component-desc">Send notification when we start a download?</span> + </label> + </div> + <div class="field-pair"> + <input type="checkbox" name="libnotify_notify_ondownload" id="libnotify_notify_ondownload" #if $sickbeard.LIBNOTIFY_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="libnotify_notify_ondownload"> + <span class="component-title">Notify on Download</span> + <span class="component-desc">Send notification when we finish a download?</span> + </label> + </div> + <div class="testNotification" id="testLibnotify-result">Click below to test.</div> + <input type="button" value="Test Libnotify" id="testLibnotify" /> + <input type="submit" class="config_submitter" value="Save Changes" /> </div><!-- /content_use_libnotify //--> </fieldset> - </div><!-- /component-group //--> + </div><!-- /libnotify component-group //--> - <div id="core-component-group8" class="component-group clearfix"> + <div class="component-group clearfix"> <div class="component-group-desc"> - <h3><a href="http://www.popcornhour.com/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/nmj.gif" alt="Networked Media Jukebox" title="Networked Media Jukebox" width="16" height="16" /> NMJ</a></h3> - <p>The Networked Media Jukebox, or NMJ, is the official media jukebox interface made available for the Popcorn Hour 200-series.</p> + <h3><a href="http://boxcar.io/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/boxcar.gif" alt="" title="Boxcar" width="16" height="16" /> Boxcar </a></h3> + <p>Read your messages where and when you want them! A subscription will be send if needed.</p> </div> - <fieldset class="component-group-list"> <div class="field-pair"> - <input type="checkbox" class="enabler" name="use_nmj" id="use_nmj" #if $sickbeard.USE_NMJ then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="use_nmj"> + <input type="checkbox" class="enabler" name="use_boxcar" id="use_boxcar" #if $sickbeard.USE_BOXCAR then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="use_boxcar"> <span class="component-title">Enable</span> - <span class="component-desc">Should Sick Beard send update commands to NMJ?</span> - </label> - </div> - - <div id="content_use_nmj"> - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Popcorn IP address</span> - <input type="text" name="nmj_host" id="nmj_host" value="$sickbeard.NMJ_HOST" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">IP of Popcorn 200-series (eg. 192.168.1.100)</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Get Settings</span> - <input type="button" value="Get Settings" id="settingsNMJ" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">The Popcorn Hour device must be powered on and NMJ running.</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">NMJ Database</span> - <input type="text" name="nmj_database" id="nmj_database" value="$sickbeard.NMJ_DATABASE" size="35" #if $sickbeard.NMJ_DATABASE then "readonly=\"readonly\"" else ""# /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Automatically filled via the 'Get Settings' button.</span> + <span class="component-desc">Should Sick Beard send notifications through Boxcar?</span> </label> </div> - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">NMJ Mount URL</span> - <input type="text" name="nmj_mount" id="nmj_mount" value="$sickbeard.NMJ_MOUNT" size="35" #if $sickbeard.NMJ_MOUNT then "readonly=\"readonly\"" else ""# /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Automatically filled via the 'Get Settings' button.</span> - </label> - </div> + <div id="content_use_boxcar"> + <div class="field-pair"> + <input type="checkbox" name="boxcar_notify_onsnatch" id="boxcar_notify_onsnatch" #if $sickbeard.BOXCAR_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="boxcar_notify_onsnatch"> + <span class="component-title">Notify on Snatch</span> + <span class="component-desc">Send notification when we start a download?</span> + </label> + </div> + <div class="field-pair"> + <input type="checkbox" name="boxcar_notify_ondownload" id="boxcar_notify_ondownload" #if $sickbeard.BOXCAR_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="boxcar_notify_ondownload"> + <span class="component-title">Notify on Download</span> + <span class="component-desc">Send notification when we finish a download?</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Boxcar Username</span> + <input type="text" name="boxcar_username" id="boxcar_username" value="$sickbeard.BOXCAR_USERNAME" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Username of your Boxcar account</span> + </label> + </div> + <div class="testNotification" id="testBoxcar-result">Click below to test.</div> + <input type="button" value="Test Boxcar" id="testBoxcar" /> + <input type="submit" class="config_submitter" value="Save Changes" /> + </div><!-- /content_use_boxcar //--> - <div class="testNotification" id="testNMJ-result">Click below to test.</div> - <input type="button" value="Test NMJ" id="testNMJ" /> - <input type="submit" class="config_submitter" value="Save Changes" /> - </div><!-- /content_use_nmj //--> </fieldset> - </div><!-- /component-group //--> + </div><!-- /boxcar component-group //--> - <div id="core-component-group9" class="component-group clearfix"> + + <div class="component-group clearfix"> <div class="component-group-desc"> - <h3><a href="http://synology.com/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/synoindex.gif" alt="Synology Indexer" title="Synology Indexer" width="16" height="16" /> Synology Indexer</a></h3> - <p>Synology Indexer is the daemon running on the Synology NAS to build its media database.</p> + <h3><a href="http://nma.usk.bz" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/nma.jpg" alt="" title="NMA" width="16" height="16" /> Notify My Android </a></h3> + <p>Notify My Android is a Prowl-like Android App and API that offers an easy way to send notifications from your application directly to your Android device.</p> </div> - <fieldset class="component-group-list"> <div class="field-pair"> - <input type="checkbox" name="use_synoindex" id="use_synoindex" #if $sickbeard.USE_SYNOINDEX then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="use_synoindex"> + <input type="checkbox" class="enabler" name="use_nma" id="use_nma" #if $sickbeard.USE_NMA then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="use_nma"> <span class="component-title">Enable</span> - <span class="component-desc">Should Sick Beard send notifications to the synoindex daemon?<br /><br /> - </span> - </label> - <label class="nocheck clearfix" for="use_synoindex"> - <span class="component-title"> </span> - <span class="component-desc">Note: Requires SB to be running on your Synology NAS.</span> - </label> - </div> - <input type="submit" class="config_submitter" value="Save Changes" /> + <span class="component-desc">Should Sick Beard send NMA notifications?</span> + </label> + </div> + + <div id="content_use_nma"> + <div class="field-pair"> + <input type="checkbox" name="nma_notify_onsnatch" id="nma_notify_onsnatch" #if $sickbeard.NMA_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="nma_notify_onsnatch"> + <span class="component-title">Notify on Snatch</span> + <span class="component-desc">Send notification when we start a download?</span> + </label> + </div> + <div class="field-pair"> + <input type="checkbox" name="nma_notify_ondownload" id="nma_notify_ondownload" #if $sickbeard.NMA_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="nma_notify_ondownload"> + <span class="component-title">Notify on Download</span> + <span class="component-desc">Send notification when we finish a download?</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">NMA API key:</span> + <input type="text" name="nma_api" id="nma_api" value="$sickbeard.NMA_API" size="55" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Multiple keys must be seperated by a , (comma). Up to a maximum of 5</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">NMA priority:</span> + <select id="nma_priority" name="nma_priority"> + <option value="-2" #if $sickbeard.NMA_PRIORITY == "-2" then 'selected="selected"' else ""#>Very Low</option> + <option value="-1" #if $sickbeard.NMA_PRIORITY == "-1" then 'selected="selected"' else ""#>Moderate</option> + <option value="0" #if $sickbeard.NMA_PRIORITY == "0" then 'selected="selected"' else ""#>Normal</option> + <option value="1" #if $sickbeard.NMA_PRIORITY == "1" then 'selected="selected"' else ""#>High</option> + <option value="2" #if $sickbeard.NMA_PRIORITY == "2" then 'selected="selected"' else ""#>Emergency</option> + </select> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Priority of NMA messages from Sick-Beard.</span> + </label> + </div> + <div class="testNotification" id="testNMA-result">Click below to test.</div> + <input type="button" value="Test NMA" id="testNMA" /> + <input type="submit" class="config_submitter" value="Save Changes" /> + </div><!-- /content_use_nma //--> + </fieldset> - </div><!-- /component-group //--> + </div><!-- /nma component-group //--> + - <div id="core-component-group10" class="component-group clearfix"> + <br /> + <h1>Online</h1> + <br /> + + <div class="component-group clearfix"> <div class="component-group-desc"> - <h3><a href="http://boxcar.io/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/boxcar.gif" alt="Boxcar" title="Boxcar" width="16" height="16" /> Boxcar</a></h3> - <p>Read your messages where and when you want them! A subscription will be send if needed.</p> + <h3><a href="http://www.twitter.com/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/twitter.gif" alt="" title="Twitter" width="16" height="16" /> Twitter </a></h3> + <p>A social networking and microblogging service, enabling its users to send and read other users' messages called tweets.</p> </div> - <fieldset class="component-group-list"> <div class="field-pair"> - <input type="checkbox" class="enabler" name="use_boxcar" id="use_boxcar" #if $sickbeard.USE_BOXCAR then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="use_boxcar"> + <input type="checkbox" class="enabler" name="use_twitter" id="use_twitter" #if $sickbeard.USE_TWITTER then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="use_twitter"> <span class="component-title">Enable</span> - <span class="component-desc">Should Sick Beard send notifications through Boxcar?</span> - </label> - </div> - - <div id="content_use_boxcar"> - <div class="field-pair"> - <input type="checkbox" name="boxcar_notify_onsnatch" id="boxcar_notify_onsnatch" #if $sickbeard.BOXCAR_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="boxcar_notify_onsnatch"> - <span class="component-title">Notify on Snatch</span> - <span class="component-desc">Send notification when we start a download?</span> - </label> - </div> - - <div class="field-pair"> - <input type="checkbox" name="boxcar_notify_ondownload" id="boxcar_notify_ondownload" #if $sickbeard.BOXCAR_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> - <label class="clearfix" for="boxcar_notify_ondownload"> - <span class="component-title">Notify on Download</span> - <span class="component-desc">Send notification when we finish a download?</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Boxcar Username</span> - <input type="text" name="boxcar_username" id="boxcar_username" value="$sickbeard.BOXCAR_USERNAME" size="35" /> + <span class="component-desc">Should Sick Beard post tweets on Twitter?</span> </label> - <label class="nocheck clearfix"> + <label class="nocheck clearfix" for="use_twitter"> <span class="component-title"> </span> - <span class="component-desc">Username of your Boxcar account</span> + <span class="component-desc">You may want to use a second account.</span> </label> </div> - <div class="testNotification" id="testBoxcar-result">Click below to test.</div> - <input type="button" value="Test Boxcar" id="testBoxcar" /> - <input type="submit" class="config_submitter" value="Save Changes" /> - - </div><!-- /content_use_boxcar //--> + <div id="content_use_twitter"> + <div class="field-pair"> + <input type="checkbox" name="twitter_notify_onsnatch" id="twitter_notify_onsnatch" #if $sickbeard.TWITTER_NOTIFY_ONSNATCH then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="twitter_notify_onsnatch"> + <span class="component-title">Notify on Snatch</span> + <span class="component-desc">Send notification when we start a download?</span> + </label> + </div> + <div class="field-pair"> + <input type="checkbox" name="twitter_notify_ondownload" id="twitter_notify_ondownload" #if $sickbeard.TWITTER_NOTIFY_ONDOWNLOAD then "checked=\"checked\"" else ""# /> + <label class="clearfix" for="twitter_notify_ondownload"> + <span class="component-title">Notify on Download</span> + <span class="component-desc">Send notification when we finish a download?</span> + </label> + </div> + <div class="field-pair"> + <label class="clearfix"> + <span class="component-title">Step One</span> + </label> + <label class="nocheck clearfix"> + <span class="component-desc">Click the "Request Authorization" button.<br/> This will open a new page containing an auth key.<br/> Note: if nothing happens check your popup blocker.<br/></span> + <input type="button" value="Request Authorization" id="twitterStep1" /> + </label> + </div> + <div class="field-pair"> + <label class="clearfix"> + <span class="component-title">Step Two</span> + </label> + <label class="nocheck clearfix"> + <span class="component-desc">Enter the key Twitter gave you below, and click "Verify Key".<br/></span> + <input type="text" id="twitter_key" value="" size="35" /><br /> + </label> + <label class="nocheck clearfix"> + <input type="button" value="Verify Key" id="twitterStep2" /> + </label> + </div> + <div class="field-pair"> + <label class="clearfix"> + <span class="component-title">Step Three</span> + </label> + </div> + <div class="testNotification" id="testTwitter-result">Click below to test.</div> + <input type="button" value="Test Twitter" id="testTwitter" /> + <input type="submit" class="config_submitter" value="Save Changes" /> + </div><!-- /content_use_twitter //--> </fieldset> - - </div> - + </div><!-- /twitter component-group //--> - <div id="core-component-group11" class="component-group clearfix"> - + <div class="component-group clearfix"> <div class="component-group-desc"> - <h3><a href="http://trakt.tv/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/trakt.gif" alt="Trakt" title="Trakt" width="16" height="16" /> Trakt</a></h3> + <h3><a href="http://trakt.tv/" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/notifiers/trakt.gif" alt="" title="Trakt" width="16" height="16" /> Trakt </a></h3> <p>trakt helps keep a record of what TV shows and movies you are watching. Based on your favorites, trakt recommends additional shows and movies you'll enjoy!</p> </div> - <fieldset class="component-group-list"> <div class="field-pair"> <input type="checkbox" class="enabler" name="use_trakt" id="use_trakt" #if $sickbeard.USE_TRAKT then "checked=\"checked\"" else ""# /> @@ -691,59 +767,50 @@ </div> <div id="content_use_trakt"> - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Trakt Username</span> - <input type="text" name="trakt_username" id="trakt_username" value="$sickbeard.TRAKT_USERNAME" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Username of your Trakt account.</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Trakt Password</span> - <input type="password" name="trakt_password" id="trakt_password" value="$sickbeard.TRAKT_PASSWORD" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Password of your Trakt account.</span> - </label> - </div> - - <div class="field-pair"> - <label class="nocheck clearfix"> - <span class="component-title">Trakt API key:</span> - <input type="text" name="trakt_api" id="trakt_api" value="$sickbeard.TRAKT_API" size="35" /> - </label> - <label class="nocheck clearfix"> - <span class="component-title"> </span> - <span class="component-desc">Get your key at: <a href="http://trakt.tv/settings/api" onclick="window.open(this.href, '_blank'); return false;">http://trakt.tv/settings/api</a></span> - </label> - </div> - - <div class="testNotification" id="testTrakt-result">Click below to test.</div> - <input type="button" value="Test Trakt" id="testTrakt" /> - <input type="submit" class="config_submitter" value="Save Changes" /> - - </div><!-- /enabler_trakt //--> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Trakt Username</span> + <input type="text" name="trakt_username" id="trakt_username" value="$sickbeard.TRAKT_USERNAME" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Username of your Trakt account.</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Trakt Password</span> + <input type="password" name="trakt_password" id="trakt_password" value="$sickbeard.TRAKT_PASSWORD" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Password of your Trakt account.</span> + </label> + </div> + <div class="field-pair"> + <label class="nocheck clearfix"> + <span class="component-title">Trakt API key:</span> + <input type="text" name="trakt_api" id="trakt_api" value="$sickbeard.TRAKT_API" size="35" /> + </label> + <label class="nocheck clearfix"> + <span class="component-title"> </span> + <span class="component-desc">Get your key at: <a href="http://trakt.tv/settings/api" onclick="window.open(this.href, '_blank'); return false;">http://trakt.tv/settings/api</a></span> + </label> + </div> + <div class="testNotification" id="testTrakt-result">Click below to test.</div> + <input type="button" value="Test Trakt" id="testTrakt" /> + <input type="submit" class="config_submitter" value="Save Changes" /> + </div><!-- /content_use_trakt //--> </fieldset> - </div><!-- /component-group //--> + </div><!-- /trakt component-group //--> - <br/><input type="submit" class="config_submitter" value="Save Changes" /><br/> + <br/><input type="submit" class="config_submitter" value="Save Changes" /><br/> </div><!-- /config-components //--> - - - - -</form> - - -</div></div> + </form> + </div> +</div> <div class="clearfix"></div> #include $os.path.join($sickbeard.PROG_DIR, "data/interfaces/default/inc_bottom.tmpl") diff --git a/data/interfaces/default/config_providers.tmpl b/data/interfaces/default/config_providers.tmpl old mode 100644 new mode 100755 index 5054118aa2a915b7527272784c500364b9210034..7ec47f65c61476cc0a837c2e33749ec620ddb17c --- a/data/interfaces/default/config_providers.tmpl +++ b/data/interfaces/default/config_providers.tmpl @@ -77,7 +77,7 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#; <span class="component-title jumbo">Configure Provider:</span> <span class="component-desc"> #set $provider_config_list = [] - #for $cur_provider in ("nzbs_org", "nzbs_r_us", "newzbin", "nzbmatrix", "tvtorrents"): + #for $cur_provider in ("nzbs_org", "nzbs_r_us", "newzbin", "nzbmatrix", "tvtorrents", "btn"): #set $cur_provider_obj = $sickbeard.providers.getProviderClass($cur_provider) #if $cur_provider_obj.providerType == $GenericProvider.NZB and not $sickbeard.USE_NZBS: #continue @@ -195,6 +195,33 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#; </div> </div> +<div class="providerDiv" id="btnDiv"> + <div class="field-pair"> + <label class="clearfix"> + <span class="component-title">BTN User ID:</span> + <input class="component-desc" type="text" name="btn_user_id" value="$sickbeard.BTN_USER_ID" /> + </label> + </div> + <div class="field-pair"> + <label class="clearfix"> + <span class="component-title">BTN Auth Token:</span> + <input class="component-desc" type="text" name="btn_auth_token" value="$sickbeard.BTN_AUTH_TOKEN" size="32" /> + </label> + </div> + <div class="field-pair"> + <label class="clearfix"> + <span class="component-title">BTN Passkey:</span> + <input class="component-desc" type="text" name="btn_passkey" value="$sickbeard.BTN_PASSKEY" size="32" /> + </label> + </div> + <div class="field-pair"> + <label class="clearfix"> + <span class="component-title">BTN Authkey:</span> + <input class="component-desc" type="text" name="btn_authkey" value="$sickbeard.BTN_AUTHKEY" size="32" /> + </label> + </div> +</div> + <!-- end div for editing providers --> <input type="submit" class="config_submitter" value="Save Changes" /><br/> diff --git a/data/interfaces/default/inc_top.tmpl b/data/interfaces/default/inc_top.tmpl index 5b3cfd321ec0078d2319c62fd67923712f92ab23..239a55057df91fc7674b17076f7e1ede3d000fc1 100644 --- a/data/interfaces/default/inc_top.tmpl +++ b/data/interfaces/default/inc_top.tmpl @@ -223,4 +223,4 @@ table.tablesorter thead tr .headerSortDown { background-image: url("$sbRoot/imag <div id="contentWrapper"> <div id="content"> -<h1>#if $varExists('header') then $header else $title#</h1> +<h1 class="title">#if $varExists('header') then $header else $title#</h1> diff --git a/data/interfaces/default/restart_bare.tmpl b/data/interfaces/default/restart_bare.tmpl index ad8e1b79feea6f1d70f8e2a73bc5985353f5c4c6..2e7ad405f0eb0180858c4464af7b4ac5cacb8ca1 100644 --- a/data/interfaces/default/restart_bare.tmpl +++ b/data/interfaces/default/restart_bare.tmpl @@ -1,6 +1,9 @@ <script type="text/javascript" charset="utf-8"> <!-- sbRoot = "$sbRoot"; +sbHttpPort = "$sbHttpPort"; +sbHttpsEnabled = "$sbHttpsEnabled"; +sbHost = "$sbHost"; //--> </script> diff --git a/data/js/browser.js b/data/js/browser.js index 62b9c856a56a0b4e3921ccfbc031405b7c7d1163..47e26c27a78a3fa9c2f93b69f61bfc2930300ddb 100644 --- a/data/js/browser.js +++ b/data/js/browser.js @@ -1,44 +1,45 @@ -(function(){ +(function () { $.Browser = { defaults: { title: 'Choose Directory', - url: sbRoot+'/browser/', - autocompleteURL: sbRoot+'/browser/complete' + url: sbRoot + '/browser/', + autocompleteURL: sbRoot + '/browser/complete' } }; - var fileBrowserDialog = null; - var currentBrowserPath = null; - var currentRequest = null; + var fileBrowserDialog, currentBrowserPath, currentRequest = null; function browse(path, endpoint) { - if(currentBrowserPath == path) + if (currentBrowserPath === path) { return; - + } + currentBrowserPath = path; - - if(currentRequest) + + if (currentRequest) { currentRequest.abort(); - + } + fileBrowserDialog.dialog('option', 'dialogClass', 'browserDialog busy'); - - currentRequest = $.getJSON(endpoint, { path: path }, function(data){ + + currentRequest = $.getJSON(endpoint, { path: path }, function (data) { fileBrowserDialog.empty(); var first_val = data[0]; var i = 0; - data = jQuery.grep(data, function(value) { + var list, link = null; + data = $.grep(data, function (value) { return i++ != 0; }); $('<h1>').text(first_val.current_path).appendTo(fileBrowserDialog); list = $('<ul>').appendTo(fileBrowserDialog); - $.each(data, function(i, entry) { - link = $("<a href='javascript:void(0)' />").click(function(){ browse(entry.path, endpoint); }).text(entry.name); + $.each(data, function (i, entry) { + link = $("<a href='javascript:void(0)' />").click(function () { browse(entry.path, endpoint); }).text(entry.name); $('<span class="ui-icon ui-icon-folder-collapsed"></span>').prependTo(link); link.hover( - function(){jQuery("span", this).addClass("ui-icon-folder-open"); }, - function(){jQuery("span", this).removeClass("ui-icon-folder-open"); } + function () {$("span", this).addClass("ui-icon-folder-open"); }, + function () {$("span", this).removeClass("ui-icon-folder-open"); } ); link.appendTo(list); }); @@ -47,95 +48,118 @@ }); } - $.fn.nFileBrowser = function(callback, options){ - + $.fn.nFileBrowser = function (callback, options) { + options = $.extend({}, $.Browser.defaults, options); - + // make a fileBrowserDialog object if one doesn't exist already - if(!fileBrowserDialog) { - + if (!fileBrowserDialog) { + // set up the jquery dialog fileBrowserDialog = $('<div id="fileBrowserDialog" style="display:hidden"></div>').appendTo('body').dialog({ dialogClass: 'browserDialog', title: options.title, position: ['center', 40], - minWidth: Math.min($(document).width()-80, 650), + minWidth: Math.min($(document).width() - 80, 650), minHeight: 320, - height: $(document).height()-80, + height: $(document).height() - 80, modal: true, autoOpen: false }); } - + // add the OK/Close buttons to the dialog - fileBrowserDialog.dialog('option', 'buttons', - { - "Ok": function(){ - + fileBrowserDialog.dialog('option', 'buttons', { + "Ok": function () { // store the browsed path to the associated text field //options.field.val(currentBrowserPath); //alert(currentBrowserPath); callback(currentBrowserPath, options); - fileBrowserDialog.dialog("close"); }, - - "Cancel": function(){ + "Cancel": function () { fileBrowserDialog.dialog("close"); } - }); - + // set up the browser and launch the dialog - if (options.initialDir) + var initialDir = ''; + if (options.initialDir) { initialDir = options.initialDir; - else - initialDir = ''; - browse(initialDir, options.url) + } + browse(initialDir, options.url); fileBrowserDialog.dialog('open'); return false; }; - $.fn.fileBrowser = function(options){ + $.fn.fileBrowser = function (options) { options = $.extend({}, $.Browser.defaults, options); - // text field used for the result options.field = $(this); - - if(options.field.autocomplete && options.autocompleteURL) { - options.field.autocomplete({ - source: options.autocompleteURL, - open: function(event, ui) { + + if (options.field.autocomplete && options.autocompleteURL) { + var query = ''; + options.field.autocomplete({ + source: function (request, response) { + //keep track of user submitted search term + query = $.ui.autocomplete.escapeRegex(request.term); + $.ajax({ + url: options.autocompleteURL, + data: request, + dataType: "json", + success: function (data, item) { + //implement a startsWith filter for the results + var matcher = new RegExp("^" + query, "i"); + var a = $.grep(data, function (item, index) { + return matcher.test(item); + }); + response(a); + } + }); + }, + open: function (event, ui) { $(".ui-autocomplete li.ui-menu-item a").removeClass("ui-corner-all"); $(".ui-autocomplete li.ui-menu-item:odd a").addClass("ui-menu-item-alternate"); } - }); + }) + .data("autocomplete")._renderItem = function (ul, item) { + //highlight the matched search term from the item -- note that this is global and will match anywhere + var result_item = item.label; + var x = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query + ")(?![^<>]*>)(?![^&;]+;)", "gi"); + result_item = result_item.replace(x, function (FullMatch, n) { + return '<b>' + FullMatch + '</b>'; + }); + return $("<li></li>") + .data("item.autocomplete", item) + .append("<a class='nowrap'>" + result_item + "</a>") + .appendTo(ul); + }; } + var initialDir, path, callback = null; // if the text field is empty and we're given a key then populate it with the last browsed value from a cookie - if(options.key && options.field.val().length == 0 && (path = $.cookie('fileBrowser-' + options.key))) + if (options.key && options.field.val().length === 0 && (path = $.cookie('fileBrowser-' + options.key))) { options.field.val(path); - - callback = function(path, options){ + } + + callback = function (path, options) { // store the browsed path to the associated text field options.field.val(path); - + // use a cookie to remember for next time - if(options.key) + if (options.key) { $.cookie('fileBrowser-' + options.key, path); - - } + } + }; initialDir = options.field.val() || (options.key && $.cookie('fileBrowser-' + options.key)) || ''; - - options = $.extend(options, {initialDir: initialDir}) - + + options = $.extend(options, {initialDir: initialDir}); + // append the browse button and give it a click behavior - return options.field.addClass('fileBrowserField').after($('<input type="button" value="Browse…" class="fileBrowser" />').click(function(){ - + return options.field.addClass('fileBrowserField').after($('<input type="button" value="Browse…" class="fileBrowser" />').click(function () { $(this).nFileBrowser(callback, options); - return false; })); }; diff --git a/data/js/configNotifications.js b/data/js/configNotifications.js index 3b8fec5924f3fff12d8f0cb9871125267535bcaf..4f02185a1a787dfb07db7b3fbe7cf40796b134ed 100644 --- a/data/js/configNotifications.js +++ b/data/js/configNotifications.js @@ -127,4 +127,12 @@ $(document).ready(function(){ $.get(sbRoot+"/home/testTrakt", {'api': trakt_api, 'username': trakt_username, 'password': trakt_password}, function (data){ $('#testTrakt-result').html(data); }); }); + + $('#testNMA').click(function(){ + $('#testNMA-result').html(loading); + var nma_api = $("#nma_api").val(); + var nma_priority = $("#nma_priority").val(); + var nma_result = $.get(sbRoot+"/home/testNMA", {'nma_api': nma_api, 'nma_priority': nma_priority}, + function (data){ $('#testNMA-result').html(data); }); + }); }); diff --git a/data/js/restart.js b/data/js/restart.js index 86160c9482b6fdc6bfa61ba2e0bc0895323f0130..715065f857716ad50986fc53266bece93fd0568d 100644 --- a/data/js/restart.js +++ b/data/js/restart.js @@ -1,3 +1,9 @@ +if (sbHttpsEnabled != "False" && sbHttpsEnabled != 0) + var sb_base_url = 'https://'+sbHost+':'+sbHttpPort+sbRoot; +else + var sb_base_url = 'http://'+sbHost+':'+sbHttpPort+sbRoot; + +var base_url = window.location.protocol+'//'+window.location.host+sbRoot; var is_alive_url = sbRoot+'/home/is_alive'; var timeout_id; var current_pid = ''; @@ -8,26 +14,26 @@ function is_alive() { $.get(is_alive_url, function(data) { // if it's still initalizing then just wait and try again - if (data == 'nope') { + if (data.msg == 'nope') { $('#shut_down_loading').hide(); $('#shut_down_success').show(); $('#restart_message').show(); setTimeout('is_alive()', 1000); } else { // if this is before we've even shut down then just try again later - if (current_pid == '' || data == current_pid) { - current_pid = data; + if (current_pid == '' || data.msg == current_pid) { + current_pid = data.msg; setTimeout(is_alive, 1000); - // if we're ready to go then refresh the page which'll forward to /home + // if we're ready to go then redirect to new url } else { $('#restart_loading').hide(); $('#restart_success').show(); $('#refresh_message').show(); - location.reload(); + window.location = sb_base_url+'/home'; } } - }); + }, 'jsonp'); } $(document).ready(function() @@ -36,13 +42,26 @@ $(document).ready(function() is_alive(); $('#shut_down_message').ajaxError(function(e, jqxhr, settings, exception) { - if (settings.url != is_alive_url) - return; num_restart_waits += 1; $('#shut_down_loading').hide(); $('#shut_down_success').show(); $('#restart_message').show(); + is_alive_url = sb_base_url+'/home/is_alive'; + + // if https is enabled or you are currently on https and the port or protocol changed just wait 5 seconds then redirect. + // This is because the ajax will fail if the cert is untrusted or the the http ajax requst from https will fail because of mixed content error. + if ((sbHttpsEnabled != "False" && sbHttpsEnabled != 0) || window.location.protocol == "https:") { + if (base_url != sb_base_url) { + timeout_id = 1; + setTimeout(function(){ + $('#restart_loading').hide(); + $('#restart_success').show(); + $('#refresh_message').show(); + }, 3000); + setTimeout("window.location = sb_base_url+'/home'", 5000); + } + } // if it is taking forever just give up if (num_restart_waits > 90) { diff --git a/init.ubuntu b/init.ubuntu index ce4c274f1cbd77258b0a8d3bd32bf599b2bcf1ec..9765569091cd494f7cd5f5db25d14bd3648cc592 100755 --- a/init.ubuntu +++ b/init.ubuntu @@ -19,7 +19,7 @@ DAEMON=/usr/bin/python # Path to store PID file PID_FILE=/var/run/sickbeard/sickbeard.pid -PID_PATH=$(dirname $PID_FILE) +PID_PATH=`dirname $PID_FILE` # script name NAME=sickbeard @@ -31,10 +31,10 @@ DESC=SickBeard RUN_AS=SICKBEARD_USER # data directory -DATA_DIR=/home/${RUN_AS}/.sickbeard +DATA_DIR=~/.sickbeard # startup args -DAEMON_OPTS=" SickBeard.py -q --daemon --nolaunch --pidfile=${PID_FILE} --datadir=${DATA_DIR}" +DAEMON_OPTS=" SickBeard.py -q --daemon --pidfile=${PID_FILE} --datadir=${DATA_DIR}" ############### END EDIT ME ################## @@ -42,16 +42,27 @@ test -x $DAEMON || exit 0 set -e +if [ ! -d $PID_PATH ]; then + mkdir -p $PID_PATH + chown $RUN_AS $PID_PATH +fi + if [ ! -d $DATA_DIR ]; then mkdir -p $DATA_DIR chown $RUN_AS $DATA_DIR fi +if [ -e $PID_FILE ]; then + PID=`cat $PID_FILE` + if ! kill -0 $PID > /dev/null 2>&1; then + echo "Removing stale $PID_FILE" + rm $PID_FILE + fi +fi + case "$1" in start) echo "Starting $DESC" - rm -rf $PID_PATH || return 1 - install -d --mode=0755 -o $RUN_AS -g $RUN_AS $PID_PATH || return 1 start-stop-daemon -d $APP_PATH -c $RUN_AS --start --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS ;; stop) diff --git a/lib/certgen.py b/lib/certgen.py new file mode 100644 index 0000000000000000000000000000000000000000..1b941161bdb70d549ee86fd7ede62f4f6a7e93c8 --- /dev/null +++ b/lib/certgen.py @@ -0,0 +1,82 @@ +# -*- coding: latin-1 -*- +# +# Copyright (C) Martin Sj�gren and AB Strakt 2001, All rights reserved +# Copyright (C) Jean-Paul Calderone 2008, All rights reserved +# This file is licenced under the GNU LESSER GENERAL PUBLIC LICENSE Version 2.1 or later (aka LGPL v2.1) +# Please see LGPL2.1.txt for more information +""" +Certificate generation module. +""" + +from OpenSSL import crypto +import time + +TYPE_RSA = crypto.TYPE_RSA +TYPE_DSA = crypto.TYPE_DSA + +serial = int(time.time()) + + +def createKeyPair(type, bits): + """ + Create a public/private key pair. + + Arguments: type - Key type, must be one of TYPE_RSA and TYPE_DSA + bits - Number of bits to use in the key + Returns: The public/private key pair in a PKey object + """ + pkey = crypto.PKey() + pkey.generate_key(type, bits) + return pkey + +def createCertRequest(pkey, digest="md5", **name): + """ + Create a certificate request. + + Arguments: pkey - The key to associate with the request + digest - Digestion method to use for signing, default is md5 + **name - The name of the subject of the request, possible + arguments are: + C - Country name + ST - State or province name + L - Locality name + O - Organization name + OU - Organizational unit name + CN - Common name + emailAddress - E-mail address + Returns: The certificate request in an X509Req object + """ + req = crypto.X509Req() + subj = req.get_subject() + + for (key,value) in name.items(): + setattr(subj, key, value) + + req.set_pubkey(pkey) + req.sign(pkey, digest) + return req + +def createCertificate(req, (issuerCert, issuerKey), serial, (notBefore, notAfter), digest="md5"): + """ + Generate a certificate given a certificate request. + + Arguments: req - Certificate reqeust to use + issuerCert - The certificate of the issuer + issuerKey - The private key of the issuer + serial - Serial number for the certificate + notBefore - Timestamp (relative to now) when the certificate + starts being valid + notAfter - Timestamp (relative to now) when the certificate + stops being valid + digest - Digest method to use for signing, default is md5 + Returns: The signed certificate in an X509 object + """ + cert = crypto.X509() + cert.set_serial_number(serial) + cert.gmtime_adj_notBefore(notBefore) + cert.gmtime_adj_notAfter(notAfter) + cert.set_issuer(issuerCert.get_subject()) + cert.set_subject(req.get_subject()) + cert.set_pubkey(req.get_pubkey()) + cert.sign(issuerKey, digest) + return cert diff --git a/lib/pynma/__init__.py b/lib/pynma/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f90424eb70e977fa6f417a29fc944a6fba32e65d --- /dev/null +++ b/lib/pynma/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/python + +from pynma import PyNMA + diff --git a/lib/pynma/pynma.py b/lib/pynma/pynma.py new file mode 100644 index 0000000000000000000000000000000000000000..fc7d8de2eeb4cf90ec5e6ceae4fb60c6925fd3ac --- /dev/null +++ b/lib/pynma/pynma.py @@ -0,0 +1,137 @@ +#!/usr/bin/python + +from xml.dom.minidom import parseString +from httplib import HTTPSConnection +from urllib import urlencode + +__version__ = "0.1" + +API_SERVER = 'nma.usk.bz' +ADD_PATH = '/publicapi/notify' + +USER_AGENT="PyNMA/v%s"%__version__ + +def uniq_preserve(seq): # Dave Kirby + # Order preserving + seen = set() + return [x for x in seq if x not in seen and not seen.add(x)] + +def uniq(seq): + # Not order preserving + return {}.fromkeys(seq).keys() + +class PyNMA(object): + """PyNMA(apikey=[], developerkey=None) + takes 2 optional arguments: + - (opt) apykey: might me a string containing 1 key or an array of keys + - (opt) developerkey: where you can store your developer key + """ + + def __init__(self, apikey=[], developerkey=None): + self._developerkey = None + self.developerkey(developerkey) + if apikey: + if type(apikey) == str: + apikey = [apikey] + self._apikey = uniq(apikey) + + def addkey(self, key): + "Add a key (register ?)" + if type(key) == str: + if not key in self._apikey: + self._apikey.append(key) + elif type(key) == list: + for k in key: + if not k in self._apikey: + self._apikey.append(k) + + def delkey(self, key): + "Removes a key (unregister ?)" + if type(key) == str: + if key in self._apikey: + self._apikey.remove(key) + elif type(key) == list: + for k in key: + if key in self._apikey: + self._apikey.remove(k) + + def developerkey(self, developerkey): + "Sets the developer key (and check it has the good length)" + if type(developerkey) == str and len(developerkey) == 48: + self._developerkey = developerkey + + def push(self, application="", event="", description="", url="", priority=0, batch_mode=False): + """Pushes a message on the registered API keys. + takes 5 arguments: + - (req) application: application name [256] + - (req) event: event name [1000] + - (req) description: description [10000] + - (opt) url: url [512] + - (opt) priority: from -2 (lowest) to 2 (highest) (def:0) + - (opt) batch_mode: call API 5 by 5 (def:False) + + Warning: using batch_mode will return error only if all API keys are bad + cf: http://nma.usk.bz/api.php + """ + datas = { + 'application': application[:256].encode('utf8'), + 'event': event[:1024].encode('utf8'), + 'description': description[:10000].encode('utf8'), + 'priority': priority + } + + if url: + datas['url'] = url[:512] + + if self._developerkey: + datas['developerkey'] = self._developerkey + + results = {} + + if not batch_mode: + for key in self._apikey: + datas['apikey'] = key + res = self.callapi('POST', ADD_PATH, datas) + results[key] = res + else: + for i in range(0, len(self._apikey), 5): + datas['apikey'] = ",".join(self._apikey[i:i+5]) + res = self.callapi('POST', ADD_PATH, datas) + results[datas['apikey']] = res + return results + + def callapi(self, method, path, args): + headers = { 'User-Agent': USER_AGENT } + if method == "POST": + headers['Content-type'] = "application/x-www-form-urlencoded" + http_handler = HTTPSConnection(API_SERVER) + http_handler.request(method, path, urlencode(args), headers) + resp = http_handler.getresponse() + + try: + res = self._parse_reponse(resp.read()) + except Exception, e: + res = {'type': "pynmaerror", + 'code': 600, + 'message': str(e) + } + pass + + return res + + def _parse_reponse(self, response): + root = parseString(response).firstChild + for elem in root.childNodes: + if elem.nodeType == elem.TEXT_NODE: continue + if elem.tagName == 'success': + res = dict(elem.attributes.items()) + res['message'] = "" + res['type'] = elem.tagName + return res + if elem.tagName == 'error': + res = dict(elem.attributes.items()) + res['message'] = elem.firstChild.nodeValue + res['type'] = elem.tagName + return res + + diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 69bc38fe67bdca83b6a9ad9daff33dba739fd287..ce061acc28c63f6b0314fe2ea23444bea08e5149 100755 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -30,7 +30,7 @@ from threading import Lock # apparently py2exe won't build these unless they're imported somewhere from sickbeard import providers, metadata -from providers import ezrss, tvtorrents, nzbs_org, nzbmatrix, nzbsrus, newznab, womble, newzbin +from providers import ezrss, tvtorrents, btn, nzbs_org, nzbmatrix, nzbsrus, newznab, womble, newzbin from sickbeard import searchCurrent, searchBacklog, showUpdater, versionChecker, properFinder, autoPostProcesser from sickbeard import helpers, db, exceptions, show_queue, search_queue, scheduler @@ -99,6 +99,10 @@ WEB_IPV6 = None USE_API = False API_KEY = None +ENABLE_HTTPS = False +HTTPS_CERT = None +HTTPS_KEY = None + LAUNCH_BROWSER = None CACHE_DIR = None ACTUAL_CACHE_DIR = None @@ -151,6 +155,11 @@ TVTORRENTS = False TVTORRENTS_DIGEST = None TVTORRENTS_HASH = None +BTN = False +BTN_USER_ID = None +BTN_AUTH_TOKEN = None +BTN_PASSKEY = None +BTN_AUTHKEY = None TORRENT_DIR = None @@ -197,10 +206,10 @@ XBMC_HOST = '' XBMC_USERNAME = None XBMC_PASSWORD = None -USE_PLEX = False +USE_PLEX = False PLEX_NOTIFY_ONSNATCH = False PLEX_NOTIFY_ONDOWNLOAD = False -PLEX_UPDATE_LIBRARY = False +PLEX_UPDATE_LIBRARY = False PLEX_SERVER_HOST = None PLEX_HOST = None PLEX_USERNAME = None @@ -231,13 +240,13 @@ NOTIFO_NOTIFY_ONDOWNLOAD = False NOTIFO_USERNAME = None NOTIFO_APISECRET = None NOTIFO_PREFIX = None - -USE_BOXCAR = False -BOXCAR_NOTIFY_ONSNATCH = False -BOXCAR_NOTIFY_ONDOWNLOAD = False -BOXCAR_USERNAME = None -BOXCAR_PASSWORD = None -BOXCAR_PREFIX = None + +USE_BOXCAR = False +BOXCAR_NOTIFY_ONSNATCH = False +BOXCAR_NOTIFY_ONDOWNLOAD = False +BOXCAR_USERNAME = None +BOXCAR_PASSWORD = None +BOXCAR_PREFIX = None USE_LIBNOTIFY = False LIBNOTIFY_NOTIFY_ONSNATCH = False @@ -253,7 +262,21 @@ USE_SYNOINDEX = False USE_TRAKT = False TRAKT_USERNAME = None TRAKT_PASSWORD = None -TRAKT_API = '' +TRAKT_API = '' + +USE_PYTIVO = False +PYTIVO_NOTIFY_ONSNATCH = False +PYTIVO_NOTIFY_ONDOWNLOAD = False +PYTIVO_UPDATE_LIBRARY = False +PYTIVO_HOST = '' +PYTIVO_SHARE_NAME = '' +PYTIVO_TIVO_NAME = '' + +USE_NMA = False +NMA_NOTIFY_ONSNATCH = False +NMA_NOTIFY_ONDOWNLOAD = False +NMA_API = None +NMA_PRIORITY = 0 COMING_EPS_LAYOUT = None COMING_EPS_DISPLAY_PAUSED = None @@ -354,21 +377,23 @@ def initialize(consoleLogging=True): with INIT_LOCK: - global LOG_DIR, WEB_PORT, WEB_LOG, WEB_ROOT, WEB_USERNAME, WEB_PASSWORD, WEB_HOST, WEB_IPV6, USE_API, API_KEY, \ + global LOG_DIR, WEB_PORT, WEB_LOG, WEB_ROOT, WEB_USERNAME, WEB_PASSWORD, WEB_HOST, WEB_IPV6, USE_API, API_KEY, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \ USE_NZBS, USE_TORRENTS, NZB_METHOD, NZB_DIR, DOWNLOAD_PROPERS, \ SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, SAB_HOST, \ NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_HOST, currentSearchScheduler, backlogSearchScheduler, \ USE_XBMC, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_UPDATE_FULL, \ - XBMC_UPDATE_LIBRARY, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, \ - USE_TRAKT, TRAKT_USERNAME, TRAKT_PASSWORD, TRAKT_API, \ + XBMC_UPDATE_LIBRARY, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, \ + USE_TRAKT, TRAKT_USERNAME, TRAKT_PASSWORD, TRAKT_API, \ USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_UPDATE_LIBRARY, \ PLEX_SERVER_HOST, PLEX_HOST, PLEX_USERNAME, PLEX_PASSWORD, \ showUpdateScheduler, __INITIALIZED__, LAUNCH_BROWSER, showList, loadingShowList, \ - NZBS, NZBS_UID, NZBS_HASH, EZRSS, TVTORRENTS, TVTORRENTS_DIGEST, TVTORRENTS_HASH, TORRENT_DIR, USENET_RETENTION, SOCKET_TIMEOUT, \ + NZBS, NZBS_UID, NZBS_HASH, EZRSS, TVTORRENTS, TVTORRENTS_DIGEST, TVTORRENTS_HASH, BTN, BTN_USER_ID, BTN_AUTH_TOKEN, BTN_PASSKEY, BTN_AUTHKEY, TORRENT_DIR, USENET_RETENTION, SOCKET_TIMEOUT, \ SEARCH_FREQUENCY, DEFAULT_SEARCH_FREQUENCY, BACKLOG_SEARCH_FREQUENCY, \ QUALITY_DEFAULT, SEASON_FOLDERS_FORMAT, SEASON_FOLDERS_DEFAULT, STATUS_DEFAULT, \ GROWL_NOTIFY_ONSNATCH, GROWL_NOTIFY_ONDOWNLOAD, TWITTER_NOTIFY_ONSNATCH, TWITTER_NOTIFY_ONDOWNLOAD, \ USE_GROWL, GROWL_HOST, GROWL_PASSWORD, USE_PROWL, PROWL_NOTIFY_ONSNATCH, PROWL_NOTIFY_ONDOWNLOAD, PROWL_API, PROWL_PRIORITY, PROG_DIR, NZBMATRIX, NZBMATRIX_USERNAME, \ + USE_PYTIVO, PYTIVO_NOTIFY_ONSNATCH, PYTIVO_NOTIFY_ONDOWNLOAD, PYTIVO_UPDATE_LIBRARY, PYTIVO_HOST, PYTIVO_SHARE_NAME, PYTIVO_TIVO_NAME, \ + USE_NMA, NMA_NOTIFY_ONSNATCH, NMA_NOTIFY_ONDOWNLOAD, NMA_API, NMA_PRIORITY, \ NZBMATRIX_APIKEY, versionCheckScheduler, VERSION_NOTIFY, PROCESS_AUTOMATICALLY, \ KEEP_PROCESSED_DIR, TV_DOWNLOAD_DIR, TVDB_BASE_URL, MIN_SEARCH_FREQUENCY, \ showQueueScheduler, searchQueueScheduler, ROOT_DIRS, \ @@ -378,7 +403,7 @@ def initialize(consoleLogging=True): NZBSRUS, NZBSRUS_UID, NZBSRUS_HASH, NAMING_QUALITY, providerList, newznabProviderList, \ NAMING_DATES, EXTRA_SCRIPTS, USE_TWITTER, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_PREFIX, \ USE_NOTIFO, NOTIFO_USERNAME, NOTIFO_APISECRET, NOTIFO_NOTIFY_ONDOWNLOAD, NOTIFO_NOTIFY_ONSNATCH, \ - USE_BOXCAR, BOXCAR_USERNAME, BOXCAR_PASSWORD, BOXCAR_NOTIFY_ONDOWNLOAD, BOXCAR_NOTIFY_ONSNATCH, \ + USE_BOXCAR, BOXCAR_USERNAME, BOXCAR_PASSWORD, BOXCAR_NOTIFY_ONDOWNLOAD, BOXCAR_NOTIFY_ONSNATCH, \ USE_LIBNOTIFY, LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, USE_SYNOINDEX, \ USE_BANNER, USE_LISTVIEW, METADATA_XBMC, METADATA_MEDIABROWSER, METADATA_PS3, metadata_provider_dict, \ NEWZBIN, NEWZBIN_USERNAME, NEWZBIN_PASSWORD, GIT_PATH, MOVE_ASSOCIATED_FILES, \ @@ -395,12 +420,14 @@ def initialize(consoleLogging=True): CheckSection('SABnzbd') CheckSection('NZBget') CheckSection('XBMC') - CheckSection('PLEX') + CheckSection('PLEX') CheckSection('Growl') CheckSection('Prowl') CheckSection('Twitter') CheckSection('NMJ') CheckSection('Synology') + CheckSection('pyTivo') + CheckSection('NMA') LOG_DIR = check_setting_str(CFG, 'General', 'log_dir', 'Logs') if not helpers.makeDir(LOG_DIR): @@ -424,6 +451,11 @@ def initialize(consoleLogging=True): USE_API = bool(check_setting_int(CFG, 'General', 'use_api', 0)) API_KEY = check_setting_str(CFG, 'General', 'api_key', '') + + ENABLE_HTTPS = bool(check_setting_int(CFG, 'General', 'enable_https', 0)) + + HTTPS_CERT = check_setting_str(CFG, 'General', 'https_cert', 'server.crt') + HTTPS_KEY = check_setting_str(CFG, 'General', 'https_key', 'server.key') ACTUAL_CACHE_DIR = check_setting_str(CFG, 'General', 'cache_dir', 'cache') # fix bad configs due to buggy code @@ -512,6 +544,12 @@ def initialize(consoleLogging=True): TVTORRENTS_DIGEST = check_setting_str(CFG, 'TVTORRENTS', 'tvtorrents_digest', '') TVTORRENTS_HASH = check_setting_str(CFG, 'TVTORRENTS', 'tvtorrents_hash', '') + BTN = bool(check_setting_int(CFG, 'BTN', 'btn', 0)) + BTN_USER_ID = check_setting_str(CFG, 'BTN', 'btn_user_id', '') + BTN_AUTH_TOKEN = check_setting_str(CFG, 'BTN', 'btn_auth_token', '') + BTN_AUTHKEY = check_setting_str(CFG, 'BTN', 'btn_authkey', '') + BTN_PASSKEY = check_setting_str(CFG, 'BTN', 'btn_passkey', '') + NZBS = bool(check_setting_int(CFG, 'NZBs', 'nzbs', 0)) NZBS_UID = check_setting_str(CFG, 'NZBs', 'nzbs_uid', '') NZBS_HASH = check_setting_str(CFG, 'NZBs', 'nzbs_hash', '') @@ -549,10 +587,10 @@ def initialize(consoleLogging=True): XBMC_USERNAME = check_setting_str(CFG, 'XBMC', 'xbmc_username', '') XBMC_PASSWORD = check_setting_str(CFG, 'XBMC', 'xbmc_password', '') - USE_PLEX = bool(check_setting_int(CFG, 'Plex', 'use_plex', 0)) + USE_PLEX = bool(check_setting_int(CFG, 'Plex', 'use_plex', 0)) PLEX_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Plex', 'plex_notify_onsnatch', 0)) PLEX_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Plex', 'plex_notify_ondownload', 0)) - PLEX_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'Plex', 'plex_update_library', 0)) + PLEX_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'Plex', 'plex_update_library', 0)) PLEX_SERVER_HOST = check_setting_str(CFG, 'Plex', 'plex_server_host', '') PLEX_HOST = check_setting_str(CFG, 'Plex', 'plex_host', '') PLEX_USERNAME = check_setting_str(CFG, 'Plex', 'plex_username', '') @@ -582,11 +620,11 @@ def initialize(consoleLogging=True): NOTIFO_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Notifo', 'notifo_notify_ondownload', 0)) NOTIFO_USERNAME = check_setting_str(CFG, 'Notifo', 'notifo_username', '') NOTIFO_APISECRET = check_setting_str(CFG, 'Notifo', 'notifo_apisecret', '') - - USE_BOXCAR = bool(check_setting_int(CFG, 'Boxcar', 'use_boxcar', 0)) - BOXCAR_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_notify_onsnatch', 0)) - BOXCAR_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_notify_ondownload', 0)) - BOXCAR_USERNAME = check_setting_str(CFG, 'Boxcar', 'boxcar_username', '') + + USE_BOXCAR = bool(check_setting_int(CFG, 'Boxcar', 'use_boxcar', 0)) + BOXCAR_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_notify_onsnatch', 0)) + BOXCAR_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_notify_ondownload', 0)) + BOXCAR_USERNAME = check_setting_str(CFG, 'Boxcar', 'boxcar_username', '') USE_LIBNOTIFY = bool(check_setting_int(CFG, 'Libnotify', 'use_libnotify', 0)) LIBNOTIFY_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Libnotify', 'libnotify_notify_onsnatch', 0)) @@ -597,12 +635,26 @@ def initialize(consoleLogging=True): NMJ_DATABASE = check_setting_str(CFG, 'NMJ', 'nmj_database', '') NMJ_MOUNT = check_setting_str(CFG, 'NMJ', 'nmj_mount', '') - USE_SYNOINDEX = bool(check_setting_int(CFG, 'Synology', 'use_synoindex', 0)) - - USE_TRAKT = bool(check_setting_int(CFG, 'Trakt', 'use_trakt', 0)) - TRAKT_USERNAME = check_setting_str(CFG, 'Trakt', 'trakt_username', '') - TRAKT_PASSWORD = check_setting_str(CFG, 'Trakt', 'trakt_password', '') + USE_SYNOINDEX = bool(check_setting_int(CFG, 'Synology', 'use_synoindex', 0)) + + USE_TRAKT = bool(check_setting_int(CFG, 'Trakt', 'use_trakt', 0)) + TRAKT_USERNAME = check_setting_str(CFG, 'Trakt', 'trakt_username', '') + TRAKT_PASSWORD = check_setting_str(CFG, 'Trakt', 'trakt_password', '') TRAKT_API = check_setting_str(CFG, 'Trakt', 'trakt_api', '') + + USE_PYTIVO = bool(check_setting_int(CFG, 'pyTivo', 'use_pytivo', 0)) + PYTIVO_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'pyTivo', 'pytivo_notify_onsnatch', 0)) + PYTIVO_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'pyTivo', 'pytivo_notify_ondownload', 0)) + PYTIVO_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'pyTivo', 'pyTivo_update_library', 0)) + PYTIVO_HOST = check_setting_str(CFG, 'pyTivo', 'pytivo_host', '') + PYTIVO_SHARE_NAME = check_setting_str(CFG, 'pyTivo', 'pytivo_share_name', '') + PYTIVO_TIVO_NAME = check_setting_str(CFG, 'pyTivo', 'pytivo_tivo_name', '') + + USE_NMA = bool(check_setting_int(CFG, 'NMA', 'use_nma', 0)) + NMA_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'NMA', 'nma_notify_onsnatch', 0)) + NMA_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'NMA', 'nma_notify_ondownload', 0)) + NMA_API = check_setting_str(CFG, 'NMA', 'nma_api', '') + NMA_PRIORITY = check_setting_str(CFG, 'NMA', 'nma_priority', "0") GIT_PATH = check_setting_str(CFG, 'General', 'git_path', '') @@ -952,6 +1004,9 @@ def save_config(): new_config['General']['web_password'] = WEB_PASSWORD new_config['General']['use_api'] = int(USE_API) new_config['General']['api_key'] = API_KEY + new_config['General']['enable_https'] = int(ENABLE_HTTPS) + new_config['General']['https_cert'] = HTTPS_CERT + new_config['General']['https_key'] = HTTPS_KEY new_config['General']['use_nzbs'] = int(USE_NZBS) new_config['General']['use_torrents'] = int(USE_TORRENTS) new_config['General']['nzb_method'] = NZB_METHOD @@ -1006,6 +1061,13 @@ def save_config(): new_config['TVTORRENTS']['tvtorrents_digest'] = TVTORRENTS_DIGEST new_config['TVTORRENTS']['tvtorrents_hash'] = TVTORRENTS_HASH + new_config['BTN'] = {} + new_config['BTN']['btn'] = int(BTN) + new_config['BTN']['btn_user_id'] = BTN_USER_ID + new_config['BTN']['btn_auth_token'] = BTN_AUTH_TOKEN + new_config['BTN']['btn_authkey'] = BTN_AUTHKEY + new_config['BTN']['btn_passkey'] = BTN_PASSKEY + new_config['NZBs'] = {} new_config['NZBs']['nzbs'] = int(NZBS) new_config['NZBs']['nzbs_uid'] = NZBS_UID @@ -1050,12 +1112,12 @@ def save_config(): new_config['XBMC']['xbmc_host'] = XBMC_HOST new_config['XBMC']['xbmc_username'] = XBMC_USERNAME new_config['XBMC']['xbmc_password'] = XBMC_PASSWORD - - new_config['Plex'] = {} - new_config['Plex']['use_plex'] = int(USE_PLEX) + + new_config['Plex'] = {} + new_config['Plex']['use_plex'] = int(USE_PLEX) new_config['Plex']['plex_notify_onsnatch'] = int(PLEX_NOTIFY_ONSNATCH) new_config['Plex']['plex_notify_ondownload'] = int(PLEX_NOTIFY_ONDOWNLOAD) - new_config['Plex']['plex_update_library'] = int(PLEX_UPDATE_LIBRARY) + new_config['Plex']['plex_update_library'] = int(PLEX_UPDATE_LIBRARY) new_config['Plex']['plex_server_host'] = PLEX_SERVER_HOST new_config['Plex']['plex_host'] = PLEX_HOST new_config['Plex']['plex_username'] = PLEX_USERNAME @@ -1089,12 +1151,12 @@ def save_config(): new_config['Notifo']['notifo_notify_ondownload'] = int(NOTIFO_NOTIFY_ONDOWNLOAD) new_config['Notifo']['notifo_username'] = NOTIFO_USERNAME new_config['Notifo']['notifo_apisecret'] = NOTIFO_APISECRET - - new_config['Boxcar'] = {} - new_config['Boxcar']['use_boxcar'] = int(USE_BOXCAR) - new_config['Boxcar']['boxcar_notify_onsnatch'] = int(BOXCAR_NOTIFY_ONSNATCH) - new_config['Boxcar']['boxcar_notify_ondownload'] = int(BOXCAR_NOTIFY_ONDOWNLOAD) - new_config['Boxcar']['boxcar_username'] = BOXCAR_USERNAME + + new_config['Boxcar'] = {} + new_config['Boxcar']['use_boxcar'] = int(USE_BOXCAR) + new_config['Boxcar']['boxcar_notify_onsnatch'] = int(BOXCAR_NOTIFY_ONSNATCH) + new_config['Boxcar']['boxcar_notify_ondownload'] = int(BOXCAR_NOTIFY_ONDOWNLOAD) + new_config['Boxcar']['boxcar_username'] = BOXCAR_USERNAME new_config['Libnotify'] = {} new_config['Libnotify']['use_libnotify'] = int(USE_LIBNOTIFY) @@ -1108,14 +1170,30 @@ def save_config(): new_config['NMJ']['nmj_mount'] = NMJ_MOUNT new_config['Synology'] = {} - new_config['Synology']['use_synoindex'] = int(USE_SYNOINDEX) - - new_config['Trakt'] = {} - new_config['Trakt']['use_trakt'] = int(USE_TRAKT) - new_config['Trakt']['trakt_username'] = TRAKT_USERNAME - new_config['Trakt']['trakt_password'] = TRAKT_PASSWORD + new_config['Synology']['use_synoindex'] = int(USE_SYNOINDEX) + + new_config['Trakt'] = {} + new_config['Trakt']['use_trakt'] = int(USE_TRAKT) + new_config['Trakt']['trakt_username'] = TRAKT_USERNAME + new_config['Trakt']['trakt_password'] = TRAKT_PASSWORD new_config['Trakt']['trakt_api'] = TRAKT_API + new_config['pyTivo'] = {} + new_config['pyTivo']['use_pytivo'] = int(USE_PYTIVO) + new_config['pyTivo']['pytivo_notify_onsnatch'] = int(PYTIVO_NOTIFY_ONSNATCH) + new_config['pyTivo']['pytivo_notify_ondownload'] = int(PYTIVO_NOTIFY_ONDOWNLOAD) + new_config['pyTivo']['pyTivo_update_library'] = int(PYTIVO_UPDATE_LIBRARY) + new_config['pyTivo']['pytivo_host'] = PYTIVO_HOST + new_config['pyTivo']['pytivo_share_name'] = PYTIVO_SHARE_NAME + new_config['pyTivo']['pytivo_tivo_name'] = PYTIVO_TIVO_NAME + + new_config['NMA'] = {} + new_config['NMA']['use_nma'] = int(USE_NMA) + new_config['NMA']['nma_notify_onsnatch'] = int(NMA_NOTIFY_ONSNATCH) + new_config['NMA']['nma_notify_ondownload'] = int(NMA_NOTIFY_ONDOWNLOAD) + new_config['NMA']['nma_api'] = NMA_API + new_config['NMA']['nma_priority'] = NMA_PRIORITY + new_config['Newznab'] = {} new_config['Newznab']['newznab_data'] = '!!!'.join([x.configStr() for x in newznabProviderList]) @@ -1130,7 +1208,10 @@ def save_config(): def launchBrowser(startPort=None): if not startPort: startPort = WEB_PORT - browserURL = 'http://localhost:%d%s' % (startPort, WEB_ROOT) + if ENABLE_HTTPS: + browserURL = 'https://localhost:%d%s' % (startPort, WEB_ROOT) + else: + browserURL = 'http://localhost:%d%s' % (startPort, WEB_ROOT) try: webbrowser.open(browserURL, 2, 1) except: diff --git a/sickbeard/browser.py b/sickbeard/browser.py index efbb3c0697a904ea136b588272191d0ba74deb74..a2dbbaea48ee0e95d46a92ff5f892aec95598c81 100644 --- a/sickbeard/browser.py +++ b/sickbeard/browser.py @@ -52,7 +52,6 @@ def foldersAtPath(path, includeParent = False): Give the empty string as the path to list the contents of the root path under Unix this means "/", on Windows this will be a list of drive letters) """ - assert os.path.isabs(path) or path == "" # walk up the tree until we find a valid path while path and not os.path.isdir(path): diff --git a/sickbeard/common.py b/sickbeard/common.py index 51f2bf86c98cd94292a711e0d67349be73c19be0..def5f0bf2f89eb22f94006029a9f160d84d05c73 100644 --- a/sickbeard/common.py +++ b/sickbeard/common.py @@ -124,7 +124,7 @@ class Quality: checkName = lambda list, func: func([re.search(x, name, re.I) for x in list]) - if checkName(["pdtv.xvid", "hdtv.xvid", "dsr.xvid"], any) and not checkName(["720p"], all): + if checkName(["pdtv.xvid", "hdtv.xvid", "dsr.xvid", "hdtv.x264"], any) and not checkName(["720p", "1080p", "1080i"], any): return Quality.SDTV elif checkName(["dvdrip.xvid", "bdrip.xvid", "dvdrip.divx", "dvdrip.ws.xvid"], any) and not checkName(["720p"], all): return Quality.SDDVD diff --git a/sickbeard/config.py b/sickbeard/config.py index e7683b5861c7b10c2f6af8001b05d9f288a77add..118e5a2fc99a7fb186da500efec220c8a860b23b 100644 --- a/sickbeard/config.py +++ b/sickbeard/config.py @@ -41,6 +41,36 @@ naming_multi_ep_type_text = ("extend", "duplicate", "repeat") naming_sep_type = (" - ", " ") naming_sep_type_text = (" - ", "space") +def change_HTTPS_CERT(https_cert): + + if https_cert == '': + sickbeard.HTTPS_CERT = '' + return True + + if os.path.normpath(sickbeard.HTTPS_CERT) != os.path.normpath(https_cert): + if helpers.makeDir(os.path.dirname(os.path.abspath(https_cert))): + sickbeard.HTTPS_CERT = os.path.normpath(https_cert) + logger.log(u"Changed https cert path to " + https_cert) + else: + return False + + return True + +def change_HTTPS_KEY(https_key): + + if https_key == '': + sickbeard.HTTPS_KEY = '' + return True + + if os.path.normpath(sickbeard.HTTPS_KEY) != os.path.normpath(https_key): + if helpers.makeDir(os.path.dirname(os.path.abspath(https_key))): + sickbeard.HTTPS_KEY = os.path.normpath(https_key) + logger.log(u"Changed https key path to " + https_key) + else: + return False + + return True + def change_LOG_DIR(log_dir): if os.path.normpath(sickbeard.LOG_DIR) != os.path.normpath(log_dir): diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py index f5aeb5e0f7ba5e2b75e4dab65edf12da3e89f6eb..a0c6d47335053f22376c4f57535fdaed34ad4a46 100644 --- a/sickbeard/helpers.py +++ b/sickbeard/helpers.py @@ -502,6 +502,35 @@ def sanitizeSceneName (name, ezrss=False): return name +def create_https_certificates(ssl_cert, ssl_key): + """ Create self-signed HTTPS certificares and store in paths 'ssl_cert' and 'ssl_key' + """ + try: + from OpenSSL import crypto #@UnresolvedImport + from lib.certgen import createKeyPair, createCertRequest, createCertificate, TYPE_RSA, serial #@UnresolvedImport + except: + logger.log(u"pyopenssl module missing, please install for https access", logger.WARNING) + return False + + # Create the CA Certificate + cakey = createKeyPair(TYPE_RSA, 1024) + careq = createCertRequest(cakey, CN='Certificate Authority') + cacert = createCertificate(careq, (careq, cakey), serial, (0, 60*60*24*365*10)) # ten years + + cname = 'SickBeard' + pkey = createKeyPair(TYPE_RSA, 1024) + req = createCertRequest(pkey, CN=cname) + cert = createCertificate(req, (cacert, cakey), serial, (0, 60*60*24*365*10)) # ten years + + # Save the key and certificate to disk + try: + open(ssl_key, 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)) + open(ssl_cert, 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) + except: + logger.log(u"Error creating SSL key and certificate", logger.ERROR) + return False + + return True if __name__ == '__main__': import doctest diff --git a/sickbeard/metadata/__init__.py b/sickbeard/metadata/__init__.py index e80ff53424fc045b324a71f771c031dd4675ec72..db6601589d28817ff5191acc31c2c3baee77935d 100644 --- a/sickbeard/metadata/__init__.py +++ b/sickbeard/metadata/__init__.py @@ -16,10 +16,10 @@ # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. -__all__ = ['generic', 'helpers', 'xbmc', 'mediabrowser', 'ps3', 'wdtv', 'tivo'] +__all__ = ['generic', 'helpers', 'xbmc', 'mediabrowser', 'synology', 'ps3', 'wdtv', 'tivo'] import sys -import xbmc, mediabrowser, ps3, wdtv, tivo +import xbmc, mediabrowser, synology, ps3, wdtv, tivo def available_generators(): return filter(lambda x: x not in ('generic', 'helpers'), __all__) diff --git a/sickbeard/metadata/synology.py b/sickbeard/metadata/synology.py new file mode 100644 index 0000000000000000000000000000000000000000..8fa0d2fc40b741ab42937b46aa14003298521abe --- /dev/null +++ b/sickbeard/metadata/synology.py @@ -0,0 +1,410 @@ +# Author: Frans Kool <big_cabbage@hotmail.com> +# Created for Synology NAS, based on mediabrowser +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of Sick Beard. +# +# Sick Beard is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Sick Beard is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. + +import datetime +import os +import re + +import sickbeard + +import generic + +from sickbeard.common import XML_NSMAP +from sickbeard import logger, exceptions, helpers +from sickbeard import encodingKludge as ek +from lib.tvdb_api import tvdb_api, tvdb_exceptions +from sickbeard.exceptions import ex + +import xml.etree.cElementTree as etree + +class SynologyMetadata(generic.GenericMetadata): + """ + Metadata generation class for Synology. All xml formatting and + file naming information was contributed by users in the following + ticket's comments: + + http://code.google.com/p/sickbeard/issues/detail?id=311 + + The following file structure is used: + + show_root/series.xml (show metadata) + show_root/folder.jpg (poster) + show_root/backdrop.jpg (fanart) + show_root/Season 01/folder.jpg (season thumb) + show_root/Season 01/show - 1x01 - episode.avi (* example of existing ep of course) + show_root/Season 01/show - 1x01 - episode.xml (episode metadata) + show_root/Season 01/show - 1x01 - episode.jpg (episode thumb) + """ + + def __init__(self, + show_metadata=False, + episode_metadata=False, + poster=False, + fanart=False, + episode_thumbnails=False, + season_thumbnails=False): + + generic.GenericMetadata.__init__(self, + show_metadata, + episode_metadata, + poster, + fanart, + episode_thumbnails, + season_thumbnails) + + self.fanart_name = "backdrop.jpg" + self._show_file_name = 'series.xml' + self._ep_nfo_extension = 'xml' + + self.name = 'Synology' + + self.eg_show_metadata = "series.xml" + self.eg_episode_metadata = "Season##\\<i>filename</i>.xml" + self.eg_fanart = "backdrop.jpg" + self.eg_poster = "folder.jpg" + self.eg_episode_thumbnails = "Season##\\<i>filename</i>.jpg" + self.eg_season_thumbnails = "Season##\\folder.jpg" + + def get_episode_file_path(self, ep_obj): + """ + Returns a full show dir/episode.xml path for Synology + episode metadata files + + ep_obj: a TVEpisode object to get the path for + """ + + if ek.ek(os.path.isfile, ep_obj.location): + xml_file_name = helpers.replaceExtension(ek.ek(os.path.basename, ep_obj.location), self._ep_nfo_extension) + metadata_dir_name = ek.ek(os.path.join, ek.ek(os.path.dirname, ep_obj.location), '') + xml_file_path = ek.ek(os.path.join, metadata_dir_name, xml_file_name) + else: + logger.log(u"Episode location doesn't exist: "+str(ep_obj.location), logger.DEBUG) + return '' + + return xml_file_path + + def get_episode_thumb_path(self, ep_obj): + """ + Returns a full show dir/episode.jpg path for Synology + episode thumbs. + + ep_obj: a TVEpisode object to get the path from + """ + + if ek.ek(os.path.isfile, ep_obj.location): + tbn_file_name = helpers.replaceExtension(ek.ek(os.path.basename, ep_obj.location), 'jpg') + metadata_dir_name = ek.ek(os.path.join, ek.ek(os.path.dirname, ep_obj.location), '') + tbn_file_path = ek.ek(os.path.join, metadata_dir_name, tbn_file_name) + else: + return None + + return tbn_file_path + + def get_season_thumb_path(self, show_obj, season): + """ + Season thumbs for Synology go in Show Dir/Season X/folder.jpg + + If no season folder exists, None is returned + """ + + dir_list = [x for x in ek.ek(os.listdir, show_obj.location) if ek.ek(os.path.isdir, ek.ek(os.path.join, show_obj.location, x))] + + season_dir_regex = '^Season\s+(\d+)$' + + season_dir = None + + for cur_dir in dir_list: + if season == 0 and cur_dir == 'Specials': + season_dir = cur_dir + break + + match = re.match(season_dir_regex, cur_dir, re.I) + if not match: + continue + + cur_season = int(match.group(1)) + + if cur_season == season: + season_dir = cur_dir + break + + if not season_dir: + logger.log(u"Unable to find a season dir for season "+str(season), logger.DEBUG) + return None + + logger.log(u"Using "+str(season_dir)+"/folder.jpg as season dir for season "+str(season), logger.DEBUG) + + return ek.ek(os.path.join, show_obj.location, season_dir, 'folder.jpg') + + def _show_data(self, show_obj): + """ + Creates an elementTree XML structure for a Synology-style series.xml + returns the resulting data object. + + show_obj: a TVShow instance to create the NFO for + """ + + tvdb_lang = show_obj.lang + # There's gotta be a better way of doing this but we don't wanna + # change the language value elsewhere + ltvdb_api_parms = sickbeard.TVDB_API_PARMS.copy() + + if tvdb_lang and not tvdb_lang == 'en': + ltvdb_api_parms['language'] = tvdb_lang + + t = tvdb_api.Tvdb(actors=True, **ltvdb_api_parms) + + tv_node = etree.Element("Series") + for ns in XML_NSMAP.keys(): + tv_node.set(ns, XML_NSMAP[ns]) + + try: + myShow = t[int(show_obj.tvdbid)] + except tvdb_exceptions.tvdb_shownotfound: + logger.log("Unable to find show with id " + str(show_obj.tvdbid) + " on tvdb, skipping it", logger.ERROR) + raise + + except tvdb_exceptions.tvdb_error: + logger.log("TVDB is down, can't use its data to make the NFO", logger.ERROR) + raise + + # check for title and id + try: + if myShow["seriesname"] == None or myShow["seriesname"] == "" or myShow["id"] == None or myShow["id"] == "": + logger.log("Incomplete info for show with id " + str(show_obj.tvdbid) + " on tvdb, skipping it", logger.ERROR) + return False + except tvdb_exceptions.tvdb_attributenotfound: + logger.log("Incomplete info for show with id " + str(show_obj.tvdbid) + " on tvdb, skipping it", logger.ERROR) + + return False + + tvdbid = etree.SubElement(tv_node, "id") + if myShow["id"] != None: + tvdbid.text = myShow["id"] + + Actors = etree.SubElement(tv_node, "Actors") + if myShow["actors"] != None: + Actors.text = myShow["actors"] + + ContentRating = etree.SubElement(tv_node, "ContentRating") + if myShow["contentrating"] != None: + ContentRating.text = myShow["contentrating"] + + premiered = etree.SubElement(tv_node, "FirstAired") + if myShow["firstaired"] != None: + premiered.text = myShow["firstaired"] + + genre = etree.SubElement(tv_node, "genre") + if myShow["genre"] != None: + genre.text = myShow["genre"] + + IMDBId = etree.SubElement(tv_node, "IMDBId") + if myShow["imdb_id"] != None: + IMDBId.text = myShow["imdb_id"] + + IMDB_ID = etree.SubElement(tv_node, "IMDB_ID") + if myShow["imdb_id"] != None: + IMDB_ID.text = myShow["imdb_id"] + + Overview = etree.SubElement(tv_node, "Overview") + if myShow["overview"] != None: + Overview.text = myShow["overview"] + + Network = etree.SubElement(tv_node, "Network") + if myShow["network"] != None: + Network.text = myShow["network"] + + Runtime = etree.SubElement(tv_node, "Runtime") + if myShow["runtime"] != None: + Runtime.text = myShow["runtime"] + + + Rating = etree.SubElement(tv_node, "Rating") + if myShow["rating"] != None: + Rating.text = myShow["rating"] + + SeriesID = etree.SubElement(tv_node, "SeriesID") + if myShow["seriesid"] != None: + SeriesID.text = myShow["seriesid"] + + SeriesName = etree.SubElement(tv_node, "SeriesName") + if myShow["seriesname"] != None: + SeriesName.text = myShow["seriesname"] + + rating = etree.SubElement(tv_node, "Status") + if myShow["status"] != None: + rating.text = myShow["status"] + + helpers.indentXML(tv_node) + + data = etree.ElementTree(tv_node) + + return data + + + def _ep_data(self, ep_obj): + """ + Creates an elementTree XML structure for a Synology style episode.xml + and returns the resulting data object. + + show_obj: a TVShow instance to create the NFO for + """ + + eps_to_write = [ep_obj] + ep_obj.relatedEps + + tvdb_lang = ep_obj.show.lang + + try: + # There's gotta be a better way of doing this but we don't wanna + # change the language value elsewhere + ltvdb_api_parms = sickbeard.TVDB_API_PARMS.copy() + + if tvdb_lang and not tvdb_lang == 'en': + ltvdb_api_parms['language'] = tvdb_lang + + t = tvdb_api.Tvdb(actors=True, **ltvdb_api_parms) + myShow = t[ep_obj.show.tvdbid] + except tvdb_exceptions.tvdb_shownotfound, e: + raise exceptions.ShowNotFoundException(e.message) + except tvdb_exceptions.tvdb_error, e: + logger.log("Unable to connect to TVDB while creating meta files - skipping - "+ex(e), logger.ERROR) + return False + + rootNode = etree.Element("Item") + + # Set our namespace correctly + for ns in XML_NSMAP.keys(): + rootNode.set(ns, XML_NSMAP[ns]) + + # write an Synology XML containing info for all matching episodes + for curEpToWrite in eps_to_write: + + try: + myEp = myShow[curEpToWrite.season][curEpToWrite.episode] + except (tvdb_exceptions.tvdb_episodenotfound, tvdb_exceptions.tvdb_seasonnotfound): + logger.log("Unable to find episode " + str(curEpToWrite.season) + "x" + str(curEpToWrite.episode) + " on tvdb... has it been removed? Should I delete from db?") + return None + + if myEp["firstaired"] == None and ep_obj.season == 0: + myEp["firstaired"] = str(datetime.date.fromordinal(1)) + + if myEp["episodename"] == None or myEp["firstaired"] == None: + return None + + if len(eps_to_write) > 1: + episode = etree.SubElement(rootNode, "Item") + else: + episode = rootNode + + ID = etree.SubElement(episode, "ID") + ID.text = str(curEpToWrite.episode) + + #To do get right EpisodeID + episodeID = etree.SubElement(episode, "EpisodeID") + episodeID.text = str(curEpToWrite.tvdbid) + + title = etree.SubElement(episode, "EpisodeName") + if curEpToWrite.name != None: + title.text = curEpToWrite.name + + episodenum = etree.SubElement(episode, "EpisodeNumber") + episodenum.text = str(curEpToWrite.episode) + + FirstAired = etree.SubElement(episode, "FirstAired") + if curEpToWrite.airdate != datetime.date.fromordinal(1): + FirstAired.text = str(curEpToWrite.airdate) + else: + FirstAired.text = '' + + Overview = etree.SubElement(episode, "Overview") + if curEpToWrite.description != None: + Overview.text = curEpToWrite.description + + DVD_chapter = etree.SubElement(episode, "DVD_chapter") + DVD_chapter.text = '' + + DVD_discid = etree.SubElement(episode, "DVD_discid") + DVD_discid.text = '' + + DVD_episodenumber = etree.SubElement(episode, "DVD_episodenumber") + DVD_episodenumber.text = '' + + DVD_season = etree.SubElement(episode, "DVD_season") + DVD_season.text = '' + + director = etree.SubElement(episode, "Director") + director_text = myEp['director'] + if director_text != None: + director.text = director_text + + gueststar = etree.SubElement(episode, "GuestStars") + gueststar_text = myEp['gueststars'] + if gueststar_text != None: + gueststar.text = gueststar_text + + IMDB_ID = etree.SubElement(episode, "IMDB_ID") + IMDB_ID.text = myEp['imdb_id'] + + Language = etree.SubElement(episode, "Language") + Language.text = myEp['language'] + + ProductionCode = etree.SubElement(episode, "ProductionCode") + ProductionCode.text = myEp['productioncode'] + + Rating = etree.SubElement(episode, "Rating") + rating_text = myEp['rating'] + if rating_text != None: + Rating.text = rating_text + + Writer = etree.SubElement(episode, "Writer") + Writer_text = myEp['writer'] + if Writer_text != None: + Writer.text = Writer_text + + SeasonNumber = etree.SubElement(episode, "SeasonNumber") + SeasonNumber.text = str(curEpToWrite.season) + + absolute_number = etree.SubElement(episode, "absolute_number") + absolute_number.text = myEp['absolute_number'] + + seasonid = etree.SubElement(episode, "seasonid") + seasonid.text = myEp['seasonid'] + + seriesid = etree.SubElement(episode, "seriesid") + seriesid.text = str(curEpToWrite.show.tvdbid) + + thumb = etree.SubElement(episode, "filename") + + # just write this to the NFO regardless of whether it actually exists or not + # note: renaming files after nfo generation will break this, tough luck + thumb_text = self.get_episode_thumb_path(ep_obj) + if thumb_text: + thumb.text = thumb_text + + # Make it purdy + helpers.indentXML(rootNode) + data = etree.ElementTree(rootNode) + + return data + + def retrieveShowMetadata(self, dir): + return (None, None) + +# present a standard "interface" +metadata_class = SynologyMetadata diff --git a/sickbeard/metadata/tivo.py b/sickbeard/metadata/tivo.py old mode 100644 new mode 100755 index bd940103ac8576ac0682e9bb8ca7c6c5c8906641..5328cdf472fffc7fa6f06403ebe6e9203f9fd1ba --- a/sickbeard/metadata/tivo.py +++ b/sickbeard/metadata/tivo.py @@ -26,6 +26,7 @@ import sickbeard from sickbeard import logger, exceptions, helpers from sickbeard.metadata import generic from sickbeard import encodingKludge as ek +from sickbeard import config from lib.tvdb_api import tvdb_api, tvdb_exceptions @@ -174,7 +175,9 @@ class TIVOMetadata(generic.GenericMetadata): # Title of the episode (Pilot, Homer's Night Out, Episode 02, etc.) Should be included for episodic shows. # Leave blank or omit for movies. - data += ("episodeTitle : " + curEpToWrite.name + "\n") + # + # Added season episode to title, so that the shows will sort correctly, as often the date information is wrong. + data += ("episodeTitle : " + config.naming_ep_type[sickbeard.NAMING_EP_TYPE] % {'seasonnumber': curEpToWrite.season, 'episodenumber': curEpToWrite.episode} + " " + curEpToWrite.name + "\n") # This should be entered for episodic shows and omitted for movies. The standard tivo format is to enter diff --git a/sickbeard/metadata/wdtv.py b/sickbeard/metadata/wdtv.py index 94c5dced06350fecfb1a73c255fec3f08510fdf7..f0adac1af49f45d6b0e2d5db109fbadb46574414 100644 --- a/sickbeard/metadata/wdtv.py +++ b/sickbeard/metadata/wdtv.py @@ -1,129 +1,232 @@ -# Author: Nic Wolfe <nic@wolfeden.ca> -# URL: http://code.google.com/p/sickbeard/ -# -# This file is part of Sick Beard. -# -# Sick Beard is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Sick Beard is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. - -import os -import re - -import generic - -from sickbeard import logger, helpers - -from sickbeard import encodingKludge as ek - -class WDTVMetadata(generic.GenericMetadata): - """ - Metadata generation class for WDTV - - The following file structure is used: - - show_root/folder.jpg (poster) - show_root/Season 01/folder.jpg (episode thumb) - show_root/Season 01/show - 1x01 - episode.jpg (existing video) - """ - - def __init__(self, - show_metadata=False, - episode_metadata=False, - poster=False, - fanart=False, - episode_thumbnails=False, - season_thumbnails=False): - - generic.GenericMetadata.__init__(self, - show_metadata, - episode_metadata, - poster, - fanart, - episode_thumbnails, - season_thumbnails) - - self.name = 'WDTV' - - self.eg_show_metadata = "<i>not supported</i>" - self.eg_episode_metadata = "<i>not supported</i>" - self.eg_fanart = "<i>not supported</i>" - self.eg_poster = "folder.jpg" - self.eg_episode_thumbnails = "Season##\\<i>filename</i>.jpg" - self.eg_season_thumbnails = "Season##\\folder.jpg" - - # all of the following are not supported, so do nothing - def create_show_metadata(self, show_obj): - pass - - def create_episode_metadata(self, ep_obj): - pass - - def create_fanart(self, show_obj): - pass - - def get_episode_thumb_path(self, ep_obj): - """ - Returns the path where the episode thumbnail should be stored. Defaults to - the same path as the episode file but with a .cover.jpg extension. - - ep_obj: a TVEpisode instance for which to create the thumbnail - """ - if ek.ek(os.path.isfile, ep_obj.location): - tbn_filename = helpers.replaceExtension(ep_obj.location, 'jpg') - else: - return None - - return tbn_filename - - def get_season_thumb_path(self, show_obj, season): - """ - Season thumbs for MediaBrowser go in Show Dir/Season X/folder.jpg - - If no season folder exists, None is returned - """ - - dir_list = [x for x in ek.ek(os.listdir, show_obj.location) if ek.ek(os.path.isdir, ek.ek(os.path.join, show_obj.location, x))] - - season_dir_regex = '^Season\s+(\d+)$' - - season_dir = None - - for cur_dir in dir_list: - if season == 0 and cur_dir == 'Specials': - season_dir = cur_dir - break - - match = re.match(season_dir_regex, cur_dir, re.I) - if not match: - continue - - cur_season = int(match.group(1)) - - if cur_season == season: - season_dir = cur_dir - break - - if not season_dir: - logger.log(u"Unable to find a season dir for season "+str(season), logger.DEBUG) - return None - - logger.log(u"Using "+str(season_dir)+"/folder.jpg as season dir for season "+str(season), logger.DEBUG) - - return ek.ek(os.path.join, show_obj.location, season_dir, 'folder.jpg') - - def retrieveShowMetadata(self, dir): - return (None, None) - -# present a standard "interface" -metadata_class = WDTVMetadata - +# Author: Nic Wolfe <nic@wolfeden.ca> +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of Sick Beard. +# +# Sick Beard is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Sick Beard is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. + +import datetime +import os +import re + +import sickbeard + +import generic + +from sickbeard import logger, exceptions, helpers +from sickbeard import encodingKludge as ek +from lib.tvdb_api import tvdb_api, tvdb_exceptions +from sickbeard.exceptions import ex + +import xml.etree.cElementTree as etree + +class WDTVMetadata(generic.GenericMetadata): + """ + Metadata generation class for WDTV + + The following file structure is used: + + show_root/folder.jpg (poster) + show_root/Season 01/folder.jpg (season thumb) + show_root/Season 01/show - 1x01 - episode.metathumb (episode thumb) + show_root/Season 01/show - 1x01 - episode.xml (episode metadata) + """ + + def __init__(self, + show_metadata=False, + episode_metadata=False, + poster=False, + fanart=False, + episode_thumbnails=False, + season_thumbnails=False): + + generic.GenericMetadata.__init__(self, + show_metadata, + episode_metadata, + poster, + fanart, + episode_thumbnails, + season_thumbnails) + + self._ep_nfo_extension = 'xml' + + self.name = 'WDTV' + + self.eg_show_metadata = "<i>not supported</i>" + self.eg_episode_metadata = "Season##\\<i>filename</i>.xml" + self.eg_fanart = "<i>not supported</i>" + self.eg_poster = "folder.jpg" + self.eg_episode_thumbnails = "Season##\\<i>filename</i>.metathumb" + self.eg_season_thumbnails = "Season##\\folder.jpg" + + # all of the following are not supported, so do nothing + def create_show_metadata(self, show_obj): + pass + + def create_fanart(self, show_obj): + pass + + def get_episode_thumb_path(self, ep_obj): + """ + Returns the path where the episode thumbnail should be stored. Defaults to + the same path as the episode file but with a .metathumb extension. + + ep_obj: a TVEpisode instance for which to create the thumbnail + """ + if ek.ek(os.path.isfile, ep_obj.location): + tbn_filename = helpers.replaceExtension(ep_obj.location, 'metathumb') + else: + return None + + return tbn_filename + + def get_season_thumb_path(self, show_obj, season): + """ + Season thumbs for WDTV go in Show Dir/Season X/folder.jpg + + If no season folder exists, None is returned + """ + + dir_list = [x for x in ek.ek(os.listdir, show_obj.location) if ek.ek(os.path.isdir, ek.ek(os.path.join, show_obj.location, x))] + + season_dir_regex = '^Season\s+(\d+)$' + + season_dir = None + + for cur_dir in dir_list: + if season == 0 and cur_dir == 'Specials': + season_dir = cur_dir + break + + match = re.match(season_dir_regex, cur_dir, re.I) + if not match: + continue + + cur_season = int(match.group(1)) + + if cur_season == season: + season_dir = cur_dir + break + + if not season_dir: + logger.log(u"Unable to find a season dir for season "+str(season), logger.DEBUG) + return None + + logger.log(u"Using "+str(season_dir)+"/folder.jpg as season dir for season "+str(season), logger.DEBUG) + + return ek.ek(os.path.join, show_obj.location, season_dir, 'folder.jpg') + + def _ep_data(self, ep_obj): + """ + Creates an elementTree XML structure for a WDTV style episode.xml + and returns the resulting data object. + + ep_obj: a TVShow instance to create the NFO for + """ + + eps_to_write = [ep_obj] + ep_obj.relatedEps + + tvdb_lang = ep_obj.show.lang + + try: + # There's gotta be a better way of doing this but we don't wanna + # change the language value elsewhere + ltvdb_api_parms = sickbeard.TVDB_API_PARMS.copy() + + if tvdb_lang and not tvdb_lang == 'en': + ltvdb_api_parms['language'] = tvdb_lang + + t = tvdb_api.Tvdb(actors=True, **ltvdb_api_parms) + myShow = t[ep_obj.show.tvdbid] + except tvdb_exceptions.tvdb_shownotfound, e: + raise exceptions.ShowNotFoundException(e.message) + except tvdb_exceptions.tvdb_error, e: + logger.log("Unable to connect to TVDB while creating meta files - skipping - "+ex(e), logger.ERROR) + return False + + rootNode = etree.Element("details") + + # write an WDTV XML containing info for all matching episodes + for curEpToWrite in eps_to_write: + + try: + myEp = myShow[curEpToWrite.season][curEpToWrite.episode] + except (tvdb_exceptions.tvdb_episodenotfound, tvdb_exceptions.tvdb_seasonnotfound): + logger.log("Unable to find episode " + str(curEpToWrite.season) + "x" + str(curEpToWrite.episode) + " on tvdb... has it been removed? Should I delete from db?") + return None + + if myEp["firstaired"] == None and ep_obj.season == 0: + myEp["firstaired"] = str(datetime.date.fromordinal(1)) + + if myEp["episodename"] == None or myEp["firstaired"] == None: + return None + + if len(eps_to_write) > 1: + episode = etree.SubElement(rootNode, "details") + else: + episode = rootNode + + #To do get right EpisodeID + episodeID = etree.SubElement(episode, "id") + episodeID.text = str(curEpToWrite.tvdbid) + + title = etree.SubElement(episode, "title") + title.text = ep_obj.prettyName() + + seriesName = etree.SubElement(episode, "series_name") + if myShow["seriesname"] != None: + seriesName.text = myShow["seriesname"] + + episodeName = etree.SubElement(episode, "episode_name") + if curEpToWrite.name != None: + episodeName.text = curEpToWrite.name + + seasonNumber = etree.SubElement(episode, "season_number") + seasonNumber.text = str(curEpToWrite.season) + + episodeNum = etree.SubElement(episode, "episode_number") + episodeNum.text = str(curEpToWrite.episode) + + firstAired = etree.SubElement(episode, "firstaired") + if curEpToWrite.airdate != datetime.date.fromordinal(1): + firstAired.text = str(curEpToWrite.airdate) + + genre = etree.SubElement(episode, "genre") + if myShow["genre"] != None: + genre.text = " / ".join([x for x in myShow["genre"].split('|') if x]) + + director = etree.SubElement(episode, "director") + director_text = myEp['director'] + if director_text != None: + director.text = director_text + + actor = etree.SubElement(episode, "actor") + if myShow["actors"] != None: + actor.text = " / ".join([x for x in myShow["actors"].split('|') if x]) + + overview = etree.SubElement(episode, "overview") + if curEpToWrite.description != None: + overview.text = curEpToWrite.description + + # Make it purdy + helpers.indentXML(rootNode) + data = etree.ElementTree(rootNode) + + return data + + def retrieveShowMetadata(self, dir): + return (None, None) + +# present a standard "interface" +metadata_class = WDTVMetadata diff --git a/sickbeard/notifiers/__init__.py b/sickbeard/notifiers/__init__.py index a522ff05702b3fe420cfb4b8a042f46493b739d0..03a4eae9c3f87942ed7af0071cd4bec739bad2d1 100755 --- a/sickbeard/notifiers/__init__.py +++ b/sickbeard/notifiers/__init__.py @@ -18,54 +18,60 @@ import sickbeard -import xbmc -import plex +import xbmc +import plex import growl import prowl import tweet from . import libnotify import notifo -import boxcar +import boxcar import nmj import synoindex import trakt +import pytivo +import nma from sickbeard.common import * -xbmc_notifier = xbmc.XBMCNotifier() -plex_notifier = plex.PLEXNotifier() +xbmc_notifier = xbmc.XBMCNotifier() +plex_notifier = plex.PLEXNotifier() growl_notifier = growl.GrowlNotifier() prowl_notifier = prowl.ProwlNotifier() twitter_notifier = tweet.TwitterNotifier() notifo_notifier = notifo.NotifoNotifier() -boxcar_notifier = boxcar.BoxcarNotifier() +boxcar_notifier = boxcar.BoxcarNotifier() libnotify_notifier = libnotify.LibnotifyNotifier() nmj_notifier = nmj.NMJNotifier() synoindex_notifier = synoindex.synoIndexNotifier() trakt_notifier = trakt.TraktNotifier() +pytivo_notifier = pytivo.pyTivoNotifier() +nma_notifier = nma.NMA_Notifier() notifiers = [ - # Libnotify notifier goes first because it doesn't involve blocking on - # network activity. + # Libnotify notifier goes first because it doesn't involve blocking on + # network activity. libnotify_notifier, - xbmc_notifier, - plex_notifier, + xbmc_notifier, + plex_notifier, growl_notifier, - prowl_notifier, + prowl_notifier, twitter_notifier, nmj_notifier, synoindex_notifier, boxcar_notifier, trakt_notifier, + pytivo_notifier, + nma_notifier, ] def notify_download(ep_name): for n in notifiers: n.notify_download(ep_name) - notifo_notifier.notify_download(ep_name) + notifo_notifier.notify_download(ep_name) def notify_snatch(ep_name): for n in notifiers: n.notify_snatch(ep_name) - notifo_notifier.notify_snatch(ep_name) - + notifo_notifier.notify_snatch(ep_name) + diff --git a/sickbeard/notifiers/nma.py b/sickbeard/notifiers/nma.py new file mode 100644 index 0000000000000000000000000000000000000000..08a5db7925da5c4b686cf77eb8f619b26c50b07f --- /dev/null +++ b/sickbeard/notifiers/nma.py @@ -0,0 +1,55 @@ +from httplib import HTTPSConnection +from urllib import urlencode + +import sickbeard + +from sickbeard import logger, common +from lib.pynma import pynma + +class NMA_Notifier: + + def test_notify(self, nma_api, nma_priority): + return self._sendNMA(nma_api, nma_priority, event="Test", message="Testing NMA settings from Sick Beard", force=True) + + def notify_snatch(self, ep_name): + if sickbeard.NMA_NOTIFY_ONSNATCH: + self._sendNMA(nma_api=None, nma_priority=None, event=common.notifyStrings[common.NOTIFY_SNATCH], message=ep_name) + + def notify_download(self, ep_name): + if sickbeard.NMA_NOTIFY_ONDOWNLOAD: + self._sendNMA(nma_api=None, nma_priority=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD], message=ep_name) + + def _sendNMA(self, nma_api=None, nma_priority=None, event=None, message=None, force=False): + + title = 'Sick-Beard' + + if not sickbeard.USE_NMA and not force: + return False + + if nma_api == None: + nma_api = sickbeard.NMA_API + + if nma_priority == None: + nma_priority = sickbeard.NMA_PRIORITY + + logger.log(u"NMA title: " + title, logger.DEBUG) + logger.log(u"NMA event: " + event, logger.DEBUG) + logger.log(u"NMA message: " + message, logger.DEBUG) + + batch = False + + p = pynma.PyNMA() + keys = nma_api.split(',') + p.addkey(keys) + + if len(keys) > 1: batch = True + + response = p.push(title, event, message, priority=nma_priority, batch_mode=batch) + + if not response[nma_api][u'code'] == u'200': + logger.log(u'Could not send notification to NotifyMyAndroid', logger.ERROR) + return False + else: + return True + +notifier = NMA_Notifier \ No newline at end of file diff --git a/sickbeard/notifiers/notifo.py b/sickbeard/notifiers/notifo.py index f525c4f777a27f0985176a7dcdda9727fbaf2c7a..59c0d1ee2032f615e011f78ee7641be69cc213ed 100644 --- a/sickbeard/notifiers/notifo.py +++ b/sickbeard/notifiers/notifo.py @@ -22,6 +22,7 @@ import urllib import sickbeard from sickbeard import logger +from sickbeard.exceptions import ex try: import lib.simplejson as json #@UnusedImport @@ -41,13 +42,17 @@ class NotifoNotifier: data = urllib.urlencode({ "title": title, "label": label, - "msg": msg + "msg": msg.encode(sickbeard.SYS_ENCODING) }) try: data = urllib.urlopen(apiurl, data) result = json.load(data) - except IOError: + except ValueError, e: + logger.log(u"Unable to decode JSON: "+data, logger.ERROR) + return False + except IOError, e: + logger.log(u"Error trying to communicate with notifo: "+ex(e), logger.ERROR) return False data.close() diff --git a/sickbeard/notifiers/prowl.py b/sickbeard/notifiers/prowl.py index 01e5447de4d87f74703653679582e02c916046e8..709ea95ca5c3a297242ed2238cffc5152d461d76 100644 --- a/sickbeard/notifiers/prowl.py +++ b/sickbeard/notifiers/prowl.py @@ -16,9 +16,17 @@ # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. -from httplib import HTTPSConnection +from httplib import HTTPSConnection, HTTPException from urllib import urlencode +try: + # this only exists in 2.6 + from ssl import SSLError +except ImportError: + # make a fake one since I don't know what it is supposed to be in 2.5 + class SSLError(Exception): + pass + import sickbeard from sickbeard import logger, common @@ -64,10 +72,14 @@ class ProwlNotifier: 'description': message.encode('utf-8'), 'priority': prowl_priority } - http_handler.request("POST", - "/publicapi/add", - headers = {'Content-type': "application/x-www-form-urlencoded"}, - body = urlencode(data)) + try: + http_handler.request("POST", + "/publicapi/add", + headers = {'Content-type': "application/x-www-form-urlencoded"}, + body = urlencode(data)) + except (SSLError, HTTPException): + logger.log(u"Prowl notification failed.", logger.ERROR) + return False response = http_handler.getresponse() request_status = response.status @@ -81,4 +93,4 @@ class ProwlNotifier: logger.log(u"Prowl notification failed.", logger.ERROR) return False -notifier = ProwlNotifier \ No newline at end of file +notifier = ProwlNotifier diff --git a/sickbeard/notifiers/pytivo.py b/sickbeard/notifiers/pytivo.py new file mode 100644 index 0000000000000000000000000000000000000000..3f4aa30b482d62c4c8e58ced9db45a410fd0d319 --- /dev/null +++ b/sickbeard/notifiers/pytivo.py @@ -0,0 +1,99 @@ +# Author: Nic Wolfe <nic@wolfeden.ca> +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of Sick Beard. +# +# Sick Beard is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Sick Beard is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. + +import os +import sickbeard + +from urllib import urlencode +from urllib2 import Request, urlopen, URLError + +from sickbeard import logger +from sickbeard import encodingKludge as ek + +class pyTivoNotifier: + + def notify_snatch(self, ep_name): + pass + + def notify_download(self, ep_name): + pass + + def update_library(self, ep_obj): + + # Values from config + + if not sickbeard.USE_PYTIVO: + return False + + host = sickbeard.PYTIVO_HOST + shareName = sickbeard.PYTIVO_SHARE_NAME + tsn = sickbeard.PYTIVO_TIVO_NAME + + # There are two more values required, the container and file. + # + # container: The share name, show name and season + # + # file: The file name + # + # Some slicing and dicing of variables is required to get at these values. + # + # There might be better ways to arrive at the values, but this is the best I have been able to + # come up with. + # + + + # Calculated values + + showPath = ep_obj.show.location + showName = ep_obj.show.name + rootShowAndSeason = ek.ek(os.path.dirname, ep_obj.location) + absPath = ep_obj.location + + # Some show names have colons in them which are illegal in a path location, so strip them out. + # (Are there other characters?) + showName = showName.replace(":","") + + root = showPath.replace(showName, "") + showAndSeason = rootShowAndSeason.replace(root, "") + + container = shareName + "/" + showAndSeason + file = "/" + absPath.replace(root, "") + + # Finally create the url and make request + requestUrl = "http://" + host + "/TiVoConnect?" + urlencode( {'Command':'Push', 'Container':container, 'File':file, 'tsn':tsn} ) + + logger.log(u"pyTivo notification: Requesting " + requestUrl) + + request = Request( requestUrl ) + + try: + response = urlopen(request) #@UnusedVariable + except URLError, e: + if hasattr(e, 'reason'): + logger.log(u"pyTivo notification: Error, failed to reach a server") + logger.log(u"'Error reason: " + e.reason) + return False + elif hasattr(e, 'code'): + logger.log(u"pyTivo notification: Error, the server couldn't fulfill the request") + logger.log(u"Error code: " + e.code) + return False + else: + logger.log(u"pyTivo notification: Successfully requested transfer of file") + return True + +notifier = pyTivoNotifier diff --git a/sickbeard/notifiers/xbmc.py b/sickbeard/notifiers/xbmc.py index 6adc3d861282df07d538b5d06128574db21f646c..6d2e74fd0bd61a4b78c1b89e37e8f44d6e9fdd63 100644 --- a/sickbeard/notifiers/xbmc.py +++ b/sickbeard/notifiers/xbmc.py @@ -103,7 +103,7 @@ class XBMCNotifier: logger.log(u"Contacting XBMC via url: " + url, logger.DEBUG) handle = urllib2.urlopen(req) - response = handle.read() + response = handle.read().decode(sickbeard.SYS_ENCODING) logger.log(u"response: " + response, logger.DEBUG) except IOError, e: logger.log(u"Warning: Couldn't contact XBMC HTTP server at " + fixStupidEncodings(host) + ": " + ex(e)) @@ -141,7 +141,7 @@ class XBMCNotifier: def _update_library(self, host, showName=None): - if not sickbeard.USE_XBMC: + if not self._use_me(): logger.log("Notifications for XBMC not enabled, skipping library update", logger.DEBUG) return False @@ -191,7 +191,7 @@ class XBMCNotifier: for path in paths: # Don't need it double-encoded, gawd this is dumb - unEncPath = urllib.unquote(path.text) + unEncPath = urllib.unquote(path.text).decode(sickbeard.SYS_ENCODING) logger.log(u"XBMC Updating " + showName + " on " + host + " at " + unEncPath, logger.DEBUG) updateCommand = {'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video, %s)' % (unEncPath)} request = self._sendToXBMC(updateCommand, host) diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py index f2f4b49262752201095b57bcad907997bd492d5e..0d7c8c1f34e5ad8e76c1d1765466b722a133392c 100755 --- a/sickbeard/postProcessor.py +++ b/sickbeard/postProcessor.py @@ -719,8 +719,10 @@ class PostProcessor(object): # do the library update for trakt notifiers.trakt_notifier.update_library(ep_obj) - - # run extra_scripts + + # do the library update for pyTivo + notifiers.pytivo_notifier.update_library(ep_obj) + self._run_extra_scripts(ep_obj) return True diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py old mode 100644 new mode 100755 index 7f01e092f6f76a472ba69270011663951770b36e..9538ab5c4ccc1c5ae59db60af2aac81255240e3c --- a/sickbeard/providers/__init__.py +++ b/sickbeard/providers/__init__.py @@ -23,6 +23,7 @@ __all__ = ['ezrss', 'nzbsrus', 'womble', 'newzbin', + 'btn', ] import sickbeard diff --git a/sickbeard/providers/btn.py b/sickbeard/providers/btn.py new file mode 100644 index 0000000000000000000000000000000000000000..5ff5f0a91b9fa32f996a35f1c5f17b2657961588 --- /dev/null +++ b/sickbeard/providers/btn.py @@ -0,0 +1,75 @@ +# Author: Nic Wolfe <nic@wolfeden.ca> +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of Sick Beard. +# +# Sick Beard is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Sick Beard is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. + +import xml.etree.cElementTree as etree + +import sickbeard +import generic + +from sickbeard import logger +from sickbeard import tvcache + +class BTNProvider(generic.TorrentProvider): + + def __init__(self): + + generic.TorrentProvider.__init__(self, "BTN") + + self.supportsBacklog = False + + self.cache = BTNCache(self) + + self.url = 'http://broadcasthe.net/' + + def isEnabled(self): + return True + + def imageName(self): + return 'btn.gif' + +class BTNCache(tvcache.TVCache): + + def __init__(self, provider): + + tvcache.TVCache.__init__(self, provider) + + # only poll BTN every 15 minutes max + self.minTime = 15 + + def _getRSSData(self): + url = 'https://broadcasthe.net/feeds.php?feed=torrents_all&user='+ sickbeard.BTN_USER_ID +'&auth='+ sickbeard.BTN_AUTH_TOKEN +'&passkey='+ sickbeard.BTN_PASSKEY +'&authkey='+ sickbeard.BTN_AUTHKEY + logger.log(u"BTN cache update URL: "+ url, logger.DEBUG) + + data = self.provider.getURL(url) + + return data + + def _parseItem(self, item): + + title = item.findtext('title') + url = item.findtext('link') + + if not title or not url: + logger.log(u"The XML returned from the BTN RSS feed is incomplete, this result is unusable", logger.ERROR) + return + + logger.log(u"Adding item from RSS to cache: "+title, logger.DEBUG) + + self._addCacheEntry(title, url) + +provider = BTNProvider() \ No newline at end of file diff --git a/sickbeard/providers/newzbin.py b/sickbeard/providers/newzbin.py index 99ddb9985a853a37d2a0efb91d5841e7037e0af8..ad230b712dd84d6254b09ace071669a1f39059b7 100644 --- a/sickbeard/providers/newzbin.py +++ b/sickbeard/providers/newzbin.py @@ -284,7 +284,7 @@ class NewzbinProvider(generic.NZBProvider): for cur_item in items: title = cur_item.findtext('title') - if title == 'Feed Error': + if title == 'Feeds Error': raise exceptions.AuthException("The feed wouldn't load, probably because of invalid auth info") if sickbeard.USENET_RETENTION is not None: try: diff --git a/sickbeard/providers/nzbmatrix.py b/sickbeard/providers/nzbmatrix.py index d84446c79b91b81481c433c23f4c6b682ff11c0a..effadd63063b0caf1266a11a2f5e3ef4358a3e99 100644 --- a/sickbeard/providers/nzbmatrix.py +++ b/sickbeard/providers/nzbmatrix.py @@ -65,7 +65,7 @@ class NZBMatrixProvider(generic.NZBProvider): term = "\""+term+"\"" params = {"term": term, - "age": sickbeard.USENET_RETENTION, + "maxage": sickbeard.USENET_RETENTION, "page": "download", "username": sickbeard.NZBMATRIX_USERNAME, "apikey": sickbeard.NZBMATRIX_APIKEY, @@ -74,6 +74,10 @@ class NZBMatrixProvider(generic.NZBProvider): "ssl": 1, "scenename": 1} + # don't allow it to be missing + if not params['maxage']: + params['maxage'] = '0' + # if the show is a documentary use those cats on nzbmatrix if show and show.genre and 'documentary' in show.genre.lower(): params['subcat'] = params['subcat'] + ',53,9' diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 9b7ad1d2b2424c79686d20fc5ff96851e4364436..6e79d7604ad19b6739b153e3c57dfd0366d55f6d 100755 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -63,6 +63,10 @@ class PageTemplate (Template): KWs['file'] = os.path.join(sickbeard.PROG_DIR, "data/interfaces/default/", KWs['file']) super(PageTemplate, self).__init__(*args, **KWs) self.sbRoot = sickbeard.WEB_ROOT + self.sbHttpPort = sickbeard.WEB_PORT + self.sbHttpsPort = sickbeard.WEB_PORT + self.sbHttpsEnabled = sickbeard.ENABLE_HTTPS + self.sbHost = re.match("[^:]+", cherrypy.request.headers['Host'], re.X|re.M|re.S).group(0) self.projectHomePage = "http://code.google.com/p/sickbeard/" logPageTitle = 'Logs & Errors' @@ -661,7 +665,7 @@ class ConfigGeneral: @cherrypy.expose def saveGeneral(self, log_dir=None, web_port=None, web_log=None, web_ipv6=None, launch_browser=None, web_username=None, use_api=None, api_key=None, - web_password=None, version_notify=None): + web_password=None, version_notify=None, enable_https=None, https_cert=None, https_key=None): results = [] @@ -703,6 +707,19 @@ class ConfigGeneral: sickbeard.USE_API = use_api sickbeard.API_KEY = api_key + + if enable_https == "on": + enable_https = 1 + else: + enable_https = 0 + + sickbeard.ENABLE_HTTPS = enable_https + + if not config.change_HTTPS_CERT(https_cert): + results += ["Unable to create directory " + os.path.normpath(https_cert) + ", https cert dir not changed."] + + if not config.change_HTTPS_KEY(https_key): + results += ["Unable to create directory " + os.path.normpath(https_key) + ", https key dir not changed."] config.change_VERSION_NOTIFY(version_notify) @@ -812,7 +829,7 @@ class ConfigPostProcessing: def savePostProcessing(self, season_folders_format=None, naming_show_name=None, naming_ep_type=None, naming_multi_ep_type=None, naming_ep_name=None, naming_use_periods=None, naming_sep_type=None, naming_quality=None, naming_dates=None, - xbmc_data=None, mediabrowser_data=None, sony_ps3_data=None, wdtv_data=None, tivo_data=None, + xbmc_data=None, mediabrowser_data=None, synology_data=None, sony_ps3_data=None, wdtv_data=None, tivo_data=None, use_banner=None, keep_processed_dir=None, process_automatically=None, rename_episodes=None, move_associated_files=None, tv_download_dir=None): @@ -878,6 +895,7 @@ class ConfigPostProcessing: sickbeard.metadata_provider_dict['XBMC'].set_config(xbmc_data) sickbeard.metadata_provider_dict['MediaBrowser'].set_config(mediabrowser_data) + sickbeard.metadata_provider_dict['Synology'].set_config(synology_data) sickbeard.metadata_provider_dict['Sony PS3'].set_config(sony_ps3_data) sickbeard.metadata_provider_dict['WDTV'].set_config(wdtv_data) sickbeard.metadata_provider_dict['TIVO'].set_config(tivo_data) @@ -1059,7 +1077,8 @@ class ConfigProviders: def saveProviders(self, nzbs_org_uid=None, nzbs_org_hash=None, nzbmatrix_username=None, nzbmatrix_apikey=None, nzbs_r_us_uid=None, nzbs_r_us_hash=None, newznab_string=None, - tvtorrents_digest=None, tvtorrents_hash=None, + tvtorrents_digest=None, tvtorrents_hash=None, + btn_user_id=None, btn_auth_token=None, btn_passkey=None, btn_authkey=None, newzbin_username=None, newzbin_password=None, provider_order=None): @@ -1123,6 +1142,8 @@ class ConfigProviders: sickbeard.EZRSS = curEnabled elif curProvider == 'tvtorrents': sickbeard.TVTORRENTS = curEnabled + elif curProvider == 'btn': + sickbeard.BTN = curEnabled elif curProvider in newznabProviderDict: newznabProviderDict[curProvider].enabled = bool(curEnabled) else: @@ -1131,6 +1152,11 @@ class ConfigProviders: sickbeard.TVTORRENTS_DIGEST = tvtorrents_digest.strip() sickbeard.TVTORRENTS_HASH = tvtorrents_hash.strip() + sickbeard.BTN_USER_ID = btn_user_id.strip() + sickbeard.BTN_AUTH_TOKEN = btn_auth_token.strip() + sickbeard.BTN_PASSKEY = btn_passkey.strip() + sickbeard.BTN_AUTHKEY = btn_authkey.strip() + sickbeard.NZBS_UID = nzbs_org_uid.strip() sickbeard.NZBS_HASH = nzbs_org_hash.strip() @@ -1174,10 +1200,13 @@ class ConfigNotifications: use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None, prowl_api=None, prowl_priority=0, use_twitter=None, twitter_notify_onsnatch=None, twitter_notify_ondownload=None, use_notifo=None, notifo_notify_onsnatch=None, notifo_notify_ondownload=None, notifo_username=None, notifo_apisecret=None, - use_boxcar=None, boxcar_notify_onsnatch=None, boxcar_notify_ondownload=None, boxcar_username=None, + use_boxcar=None, boxcar_notify_onsnatch=None, boxcar_notify_ondownload=None, boxcar_username=None, use_libnotify=None, libnotify_notify_onsnatch=None, libnotify_notify_ondownload=None, - use_nmj=None, nmj_host=None, nmj_database=None, nmj_mount=None, use_synoindex=None, - use_trakt=None, trakt_username=None, trakt_password=None, trakt_api=None): + use_nmj=None, nmj_host=None, nmj_database=None, nmj_mount=None, use_synoindex=None, + use_trakt=None, trakt_username=None, trakt_password=None, trakt_api=None, + use_pytivo=None, pytivo_notify_onsnatch=None, pytivo_notify_ondownload=None, pytivo_update_library=None, + pytivo_host=None, pytivo_share_name=None, pytivo_tivo_name=None, + use_nma=None, nma_notify_onsnatch=None, nma_notify_ondownload=None, nma_api=None, nma_priority=0 ): results = [] @@ -1282,20 +1311,20 @@ class ConfigNotifications: use_notifo = 1 else: use_notifo = 0 - - if boxcar_notify_onsnatch == "on": - boxcar_notify_onsnatch = 1 - else: - boxcar_notify_onsnatch = 0 - - if boxcar_notify_ondownload == "on": - boxcar_notify_ondownload = 1 - else: - boxcar_notify_ondownload = 0 - if use_boxcar == "on": - use_boxcar = 1 - else: - use_boxcar = 0 + + if boxcar_notify_onsnatch == "on": + boxcar_notify_onsnatch = 1 + else: + boxcar_notify_onsnatch = 0 + + if boxcar_notify_ondownload == "on": + boxcar_notify_ondownload = 1 + else: + boxcar_notify_ondownload = 0 + if use_boxcar == "on": + use_boxcar = 1 + else: + use_boxcar = 0 if use_nmj == "on": use_nmj = 1 @@ -1305,13 +1334,48 @@ class ConfigNotifications: if use_synoindex == "on": use_synoindex = 1 else: - use_synoindex = 0 - - if use_trakt == "on": - use_trakt = 1 - else: + use_synoindex = 0 + + if use_trakt == "on": + use_trakt = 1 + else: use_trakt = 0 + if use_pytivo == "on": + use_pytivo = 1 + else: + use_pytivo = 0 + + if pytivo_notify_onsnatch == "on": + pytivo_notify_onsnatch = 1 + else: + pytivo_notify_onsnatch = 0 + + if pytivo_notify_ondownload == "on": + pytivo_notify_ondownload = 1 + else: + pytivo_notify_ondownload = 0 + + if pytivo_update_library == "on": + pytivo_update_library = 1 + else: + pytivo_update_library = 0 + + if use_nma == "on": + use_nma = 1 + else: + use_nma = 0 + + if nma_notify_onsnatch == "on": + nma_notify_onsnatch = 1 + else: + nma_notify_onsnatch = 0 + + if nma_notify_ondownload == "on": + nma_notify_ondownload = 1 + else: + nma_notify_ondownload = 0 + sickbeard.USE_XBMC = use_xbmc sickbeard.XBMC_NOTIFY_ONSNATCH = xbmc_notify_onsnatch sickbeard.XBMC_NOTIFY_ONDOWNLOAD = xbmc_notify_ondownload @@ -1351,11 +1415,11 @@ class ConfigNotifications: sickbeard.NOTIFO_NOTIFY_ONDOWNLOAD = notifo_notify_ondownload sickbeard.NOTIFO_USERNAME = notifo_username sickbeard.NOTIFO_APISECRET = notifo_apisecret - - sickbeard.USE_BOXCAR = use_boxcar - sickbeard.BOXCAR_NOTIFY_ONSNATCH = boxcar_notify_onsnatch - sickbeard.BOXCAR_NOTIFY_ONDOWNLOAD = boxcar_notify_ondownload - sickbeard.BOXCAR_USERNAME = boxcar_username + + sickbeard.USE_BOXCAR = use_boxcar + sickbeard.BOXCAR_NOTIFY_ONSNATCH = boxcar_notify_onsnatch + sickbeard.BOXCAR_NOTIFY_ONDOWNLOAD = boxcar_notify_ondownload + sickbeard.BOXCAR_USERNAME = boxcar_username sickbeard.USE_LIBNOTIFY = use_libnotify == "on" sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH = libnotify_notify_onsnatch == "on" @@ -1366,13 +1430,27 @@ class ConfigNotifications: sickbeard.NMJ_DATABASE = nmj_database sickbeard.NMJ_MOUNT = nmj_mount - sickbeard.USE_SYNOINDEX = use_synoindex - - sickbeard.USE_TRAKT = use_trakt - sickbeard.TRAKT_USERNAME = trakt_username - sickbeard.TRAKT_PASSWORD = trakt_password + sickbeard.USE_SYNOINDEX = use_synoindex + + sickbeard.USE_TRAKT = use_trakt + sickbeard.TRAKT_USERNAME = trakt_username + sickbeard.TRAKT_PASSWORD = trakt_password sickbeard.TRAKT_API = trakt_api + sickbeard.USE_PYTIVO = use_pytivo + sickbeard.PYTIVO_NOTIFY_ONSNATCH = pytivo_notify_onsnatch == "off" + sickbeard.PYTIVO_NOTIFY_ONDOWNLOAD = pytivo_notify_ondownload == "off" + sickbeard.PYTIVO_UPDATE_LIBRARY = pytivo_update_library + sickbeard.PYTIVO_HOST = pytivo_host + sickbeard.PYTIVO_SHARE_NAME = pytivo_share_name + sickbeard.PYTIVO_TIVO_NAME = pytivo_tivo_name + + sickbeard.USE_NMA = use_nma + sickbeard.NMA_NOTIFY_ONSNATCH = nma_notify_onsnatch + sickbeard.NMA_NOTIFY_ONDOWNLOAD = nma_notify_ondownload + sickbeard.NMA_API = nma_api + sickbeard.NMA_PRIORITY = nma_priority + sickbeard.save_config() if len(results) > 0: @@ -1405,18 +1483,18 @@ class Config: notifications = ConfigNotifications() -def haveXBMC(): - return sickbeard.XBMC_HOST - -def havePLEX(): - return sickbeard.PLEX_SERVER_HOST - +def haveXBMC(): + return sickbeard.XBMC_HOST + +def havePLEX(): + return sickbeard.PLEX_SERVER_HOST + def HomeMenu(): return [ { 'title': 'Add Shows', 'path': 'home/addShows/', }, { 'title': 'Manual Post-Processing', 'path': 'home/postprocess/' }, - { 'title': 'Update XBMC', 'path': 'home/updateXBMC/', 'requires': haveXBMC }, - { 'title': 'Update Plex', 'path': 'home/updatePLEX/', 'requires': havePLEX }, + { 'title': 'Update XBMC', 'path': 'home/updateXBMC/', 'requires': haveXBMC }, + { 'title': 'Update Plex', 'path': 'home/updatePLEX/', 'requires': havePLEX }, { 'title': 'Restart', 'path': 'home/restart/?pid='+str(sickbeard.PID), 'confirm': True }, { 'title': 'Shutdown', 'path': 'home/shutdown/', 'confirm': True }, ] @@ -1858,13 +1936,18 @@ class ErrorLogs: class Home: @cherrypy.expose - def is_alive(self): + def is_alive(self, *args, **kwargs): + if 'callback' in kwargs and '_' in kwargs: + callback, _ = kwargs['callback'], kwargs['_'] + else: + return "Error: Unsupported Request. Send jsonp request with 'callback' variable in the query stiring." cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" + cherrypy.response.headers['Content-Type'] = 'text/javascript' if sickbeard.started: - return str(sickbeard.PID) + return callback+'('+json.dumps({"msg": str(sickbeard.PID)})+');' else: - return "nope" + return callback+'('+json.dumps({"msg": "nope"})+');' @cherrypy.expose def index(self): @@ -1879,8 +1962,8 @@ class Home: @cherrypy.expose def testSABnzbd(self, host=None, username=None, password=None, apikey=None): - if not host.endswith("/"): - host = host + "/" + if not host.endswith("/"): + host = host + "/" connection, accesMsg = sab.getSabAccesMethod(host, username, password, apikey) if connection: authed, authMsg = sab.testAuthentication(host, username, password, apikey) #@UnusedVariable @@ -1925,16 +2008,16 @@ class Home: return "Notifo notification succeeded. Check your Notifo clients to make sure it worked" else: return "Error sending Notifo notification" - - @cherrypy.expose - def testBoxcar(self, username=None): - cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" - - result = notifiers.boxcar_notifier.test_notify(username) - if result: - return "Boxcar notification succeeded. Check your Boxcar clients to make sure it worked" - else: - return "Error sending Boxcar notification" + + @cherrypy.expose + def testBoxcar(self, username=None): + cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" + + result = notifiers.boxcar_notifier.test_notify(username) + if result: + return "Boxcar notification succeeded. Check your Boxcar clients to make sure it worked" + else: + return "Error sending Boxcar notification" @cherrypy.expose def twitterStep1(self): @@ -2010,18 +2093,27 @@ class Home: if result: return '{"message": "Got settings from %(host)s", "database": "%(database)s", "mount": "%(mount)s"}' % {"host": host, "database": sickbeard.NMJ_DATABASE, "mount": sickbeard.NMJ_MOUNT} else: - return '{"message": "Failed! Make sure your Popcorn is on and NMJ is running. (see Log & Errors -> Debug for detailed info)", "database": "", "mount": ""}' - - @cherrypy.expose - def testTrakt(self, api=None, username=None, password=None): - cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" - - result = notifiers.trakt_notifier.test_notify(api, username, password) - if result: - return "Test notice sent successfully to Trakt" - else: + return '{"message": "Failed! Make sure your Popcorn is on and NMJ is running. (see Log & Errors -> Debug for detailed info)", "database": "", "mount": ""}' + + @cherrypy.expose + def testTrakt(self, api=None, username=None, password=None): + cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" + + result = notifiers.trakt_notifier.test_notify(api, username, password) + if result: + return "Test notice sent successfully to Trakt" + else: return "Test notice failed to Trakt" + @cherrypy.expose + def testNMA(self, nma_api=None, nma_priority=0): + cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" + + result = notifiers.nma_notifier.test_notify(nma_api, nma_priority) + if result: + return "Test NMA notice sent successfully" + else: + return "Test NMA notice failed" @cherrypy.expose def shutdown(self): @@ -2237,7 +2329,7 @@ class Home: # if we change location clear the db of episodes, change it, write to db, and rescan if os.path.normpath(showObj._location) != os.path.normpath(location): - logger.log(os.path.normpath(showObj._location)+" != "+os.path.normpath(location)) + logger.log(os.path.normpath(showObj._location)+" != "+os.path.normpath(location), logger.DEBUG) if not ek.ek(os.path.isdir, location): errors.append("New location <tt>%s</tt> does not exist" % location) @@ -2341,27 +2433,27 @@ class Home: redirect("/home/displayShow?show="+str(showObj.tvdbid)) - @cherrypy.expose - def updateXBMC(self, showName=None): - - for curHost in [x.strip() for x in sickbeard.XBMC_HOST.split(",")]: - if notifiers.xbmc_notifier._update_library(curHost, showName=showName): - ui.notifications.message("Command sent to XBMC host " + curHost + " to update library") - else: - ui.notifications.error("Unable to contact XBMC host " + curHost) - redirect('/home') - - - @cherrypy.expose - def updatePLEX(self): - - if notifiers.plex_notifier._update_library(): - ui.notifications.message("Command sent to Plex Media Server host " + sickbeard.PLEX_HOST + " to update library") - logger.log(u"Plex library update initiated for host " + sickbeard.PLEX_HOST, logger.DEBUG) - else: - ui.notifications.error("Unable to contact Plex Media Server host " + sickbeard.PLEX_HOST) - logger.log(u"Plex library update failed for host " + sickbeard.PLEX_HOST, logger.ERROR) - redirect('/home') + @cherrypy.expose + def updateXBMC(self, showName=None): + + for curHost in [x.strip() for x in sickbeard.XBMC_HOST.split(",")]: + if notifiers.xbmc_notifier._update_library(curHost, showName=showName): + ui.notifications.message("Command sent to XBMC host " + curHost + " to update library") + else: + ui.notifications.error("Unable to contact XBMC host " + curHost) + redirect('/home') + + + @cherrypy.expose + def updatePLEX(self): + + if notifiers.plex_notifier._update_library(): + ui.notifications.message("Command sent to Plex Media Server host " + sickbeard.PLEX_HOST + " to update library") + logger.log(u"Plex library update initiated for host " + sickbeard.PLEX_HOST, logger.DEBUG) + else: + ui.notifications.error("Unable to contact Plex Media Server host " + sickbeard.PLEX_HOST) + logger.log(u"Plex library update failed for host " + sickbeard.PLEX_HOST, logger.ERROR) + redirect('/home') @cherrypy.expose diff --git a/sickbeard/webserveInit.py b/sickbeard/webserveInit.py index 1736be45582d6dccadc4d3ac8e8ea5d51c58d9d6..58efbda7e17b82d902edbd751981a76ef3961c8d 100644 --- a/sickbeard/webserveInit.py +++ b/sickbeard/webserveInit.py @@ -23,6 +23,10 @@ import os.path from sickbeard import logger from sickbeard.webserve import WebInterface +from sickbeard.helpers import create_https_certificates +from cherrypy import _cpserver +from cherrypy import _cpwsgi_server + def initWebServer(options = {}): options.setdefault('port', 8081) options.setdefault('host', '0.0.0.0') @@ -69,13 +73,33 @@ def initWebServer(options = {}): ''' % '/' # cherrypy setup - cherrypy.config.update({ - 'server.socket_port': options['port'], - 'server.socket_host': options['host'], - 'log.screen': False, - 'error_page.401': http_error_401_hander, - 'error_page.404': http_error_404_hander, - }) + enable_https = options['enable_https'] + https_cert = options['https_cert'] + https_key = options['https_key'] + + if enable_https: + # If either the HTTPS certificate or key do not exist, make some self-signed ones. + if not (https_cert and os.path.exists(https_cert)) or not (https_key and os.path.exists(https_key)): + create_https_certificates(https_cert, https_key) + + if not (os.path.exists(https_cert) and os.path.exists(https_key)): + logger.log(u"Disabled HTTPS because of missing CERT and KEY files", logger.WARNING) + enable_https = False + + options_dict = { + 'server.socket_port': options['port'], + 'server.socket_host': options['host'], + 'log.screen': False, + 'error_page.401': http_error_401_hander, + 'error_page.404': http_error_404_hander, + } + + if enable_https: + options_dict['server.ssl_certificate'] = https_cert + options_dict['server.ssl_private_key'] = https_key + + logger.log(u"Starting Sick Beard on http://" + str(options['host']) + ":" + str(options['port']) + "/") + cherrypy.config.update(options_dict) # setup cherrypy logging if options['log_dir'] and os.path.isdir(options['log_dir']): @@ -122,6 +146,7 @@ def initWebServer(options = {}): } }) + cherrypy.server.start() cherrypy.server.wait()