Change overhaul Notifications, add Notifier Factory and DRY refactoring.

Notifiers are now loaded into memory on demand.
Add bubble links to Notifications config tabs.
Add Discordapp notifier to Notifications config/Social.
Add Gitter notifier to Notifications config/Social.
Change order of notifiers in Notifications config tabs.
Remove Pushalot notifier.
Remove XBMC notifier.

Refactor update_library, notify, test notify and test results functions.
Change most IDs and vars consistent for HTML, CSS, JS, and Python - related to notifications, camelCase for JS, underscore separated lower_case for python, hyphen separated-lowercase for CSS. A couple of exceptions have been left untouched in this clean up.
Change commented out some unused vars in preparation for later removal.
This commit is contained in:
JackDandy 2017-10-17 16:43:28 +01:00
parent 5299dd3702
commit 3fa750651c
44 changed files with 4038 additions and 4035 deletions

View file

@ -110,13 +110,21 @@
* Add warn icon indicator of abandoned IDs to "Manage" menu bar and "Manage/Show Processes" menu item
* Add shows that have no replacement ID can be ignored at "Manage/Show Processes", the menu bar warn icon hides if all are ignored
* Change FreeBSD initscript to use command_interpreter
* Add Slack notifier
* Add Slack notifier to Notifications config/Social
* Change allow Cheetah template engine version 2 and newer
* Change improve handling of relative download links from providers
* Change enable TorrentBytes provider
* Change after SG is updated, don't attempt to send a Plex client notifications if there is no client host set
* Add file name to possible names in history lookup post processing
* Add garbage name handling to name parser
* Change overhaul Notifications, add Notifier Factory and DRY refactoring
* Notifiers are now loaded into memory on demand
* Add bubble links to Notifications config tabs
* Add Discordapp notifier to Notifications config/Social
* Add Gitter notifier to Notifications config/Social
* Change order of notifiers in Notifications config tabs
* Remove Pushalot notifier
* Remove XBMC notifier
[develop changelog]

View file

@ -644,7 +644,7 @@ config*.tmpl
color:#ddd
}
.testNotification{
.test-notification{
border:1px dotted #ccc
}
@ -935,6 +935,7 @@ fieldset[disabled] .navbar-default .btn-link:focus{
color:#ddd
}
.component-group.typelist .bgcol,
.dropdown-menu{
background-color:#333;
border:1px solid rgba(0, 0, 0, 0.15);

View file

@ -633,7 +633,7 @@ config*.tmpl
color:#666
}
.testNotification{
.test-notification{
border:1px dotted #ccc
}
@ -907,6 +907,7 @@ fieldset[disabled] .navbar-default .btn-link:focus{
background-color:#333
}
.component-group.typelist .bgcol,
.dropdown-menu{
background-color:#f5f1e4;
border:1px solid rgba(0, 0, 0, 0.15);

View file

@ -2839,7 +2839,7 @@ select .selected:before{
line-height:normal
}
.testNotification{
.test-notification{
padding:5px;
margin-bottom:10px;
line-height:20px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 632 B

After

Width:  |  Height:  |  Size: 570 B

File diff suppressed because it is too large Load diff

View file

@ -410,7 +410,7 @@
</div>
</div>
<div class="testNotification" id="test-nzb-result">Click below to test</div>
<div class="test-notification" id="test-nzb-result">Click below to test</div>
<input type="button" value="Test SABnzbd" id="test_sabnzbd" class="btn test-button sabnzbd">
<input type="button" value="Test NZBget" id="test_nzbget" class="btn test-button nzbget">
<input type="submit" class="btn config_submitter" value="Save Changes"><br />
@ -573,7 +573,7 @@
</label>
</div>
<div class="testNotification" id="test-torrent-result">Click below to test</div>
<div class="test-notification" id="test-torrent-result">Click below to test</div>
<input type="button" value="Test Connection" id="test_torrent" class="btn test-button">
<input type="submit" class="btn config_submitter" value="Save Changes"><br />
</div>

View file

@ -160,17 +160,17 @@
<li><a href="$sbRoot/manage/showProcesses/" tabindex="$tab#set $tab += 1#"><span class="snf item"><i class="sgicon-warning"></i><i class="sgicon-showqueue"></i></span>Show Processes</a></li>
<li><a href="$sbRoot/manage/episodeStatuses/" tabindex="$tab#set $tab += 1#"><i class="sgicon-episodestatus"></i>Episode Status</a></li>
#if hasattr($sickbeard, 'USE_EMBY') and $sg_var('USE_EMBY') and $sg_str('EMBY_HOST') != '' and $sg_str('EMBY_APIKEY') != ''
<li><a href="$sbRoot/home/updateEMBY/" tabindex="$tab#set $tab += 1#"><i class="sgicon-emby"></i>Update Emby</a></li>
<li><a href="$sbRoot/home/update_emby/" tabindex="$tab#set $tab += 1#"><i class="sgicon-emby"></i>Update Emby</a></li>
#end if
#if hasattr($sickbeard, 'USE_KODI') and $sg_var('USE_KODI') and $sg_str('KODI_HOST') != ''
<li><a href="$sbRoot/home/updateKODI/" tabindex="$tab#set $tab += 1#"><i class="sgicon-kodi"></i>Update Kodi</a></li>
#end if
#if hasattr($sickbeard, 'USE_XBMC') and $sg_var('USE_XBMC') and $sg_str('XBMC_HOST') != ''
<li><a href="$sbRoot/home/updateXBMC/" tabindex="$tab#set $tab += 1#"><i class="sgicon-xbmc"></i>Update XBMC</a></li>
<li><a href="$sbRoot/home/update_kodi/" tabindex="$tab#set $tab += 1#"><i class="sgicon-kodi"></i>Update Kodi</a></li>
#end if
#if hasattr($sickbeard, 'USE_PLEX') and $sg_var('USE_PLEX') and $sg_str('PLEX_SERVER_HOST') != ''
<li><a href="$sbRoot/home/updatePLEX/" tabindex="$tab#set $tab += 1#"><i class="sgicon-plex"></i>Update PLEX</a></li>
<li><a href="$sbRoot/home/update_plex/" tabindex="$tab#set $tab += 1#"><i class="sgicon-plex"></i>Update PLEX</a></li>
#end if
## #if hasattr($sickbeard, 'USE_XBMC') and $sg_var('USE_XBMC') and $sg_str('XBMC_HOST') != ''
## <li><a href="$sbRoot/home/update_xbmc/" tabindex="$tab#set $tab += 1#"><i class="sgicon-xbmc"></i>Update XBMC</a></li>
## #end if
#if hasattr($sickbeard, 'USE_FAILED_DOWNLOADS') and $sg_var('USE_FAILED_DOWNLOADS')
<li><a href="$sbRoot/manage/failedDownloads/" tabindex="$tab#set $tab += 1#"><i class="sgicon-failed"></i>Failed Downloads</a></li>
#end if

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@ function initActions() {
menu$.find('a[href$="/errorlogs/clearerrors/"]').addClass('btn').html('<i class="sgicon-delete"></i>Clear Errors');
menu$.find('a:contains("Re-scan")').addClass('btn').html('<i class="sgicon-refresh"></i>Re-scan');
menu$.find('a:contains("Backlog Overview")').addClass('btn').html('<i class="sgicon-backlog"></i>Backlog Overview');
menu$.find('a[href$="/home/updatePLEX/"]').addClass('btn').html('<i class="sgicon-plex"></i>Update PLEX');
menu$.find('a[href$="/home/update_plex/"]').addClass('btn').html('<i class="sgicon-plex"></i>Update PLEX');
menu$.find('a:contains("Force")').addClass('btn').html('<i class="sgicon-fullupdate"></i>Force Full Update');
menu$.find('a:contains("Rename")').addClass('btn').html('<i class="sgicon-rename"></i>Media Renamer');
menu$.find('a[href$="/config/subtitles/"]').addClass('btn').html('<i class="sgicon-subtitles"></i>Subtitles');
@ -29,12 +29,12 @@ function initActions() {
menu$.find('a:contains("Show Processes")').addClass('btn').html('<i class="sgicon-showqueue"></i>Show Processes');
menu$.find('a[href$="/manage/failedDownloads/"]').addClass('btn').html('<i class="sgicon-failed"></i>Failed Downloads');
menu$.find('a:contains("Notification")').addClass('btn').html('<i class="sgicon-notification"></i>Notifications');
menu$.find('a[href$="/home/updateEMBY/"]').addClass('btn').html('<i class="sgicon-emby"></i>Update Emby');
menu$.find('a[href$="/home/updateKODI/"]').addClass('btn').html('<i class="sgicon-kodi"></i>Update Kodi');
menu$.find('a[href$="/home/updateXBMC/"]').addClass('btn').html('<i class="sgicon-xbmc"></i>Update XBMC');
menu$.find('a[href$="/home/update_emby/"]').addClass('btn').html('<i class="sgicon-emby"></i>Update Emby');
menu$.find('a[href$="/home/update_kodi/"]').addClass('btn').html('<i class="sgicon-kodi"></i>Update Kodi');
// menu$.find('a[href$="/home/update_xbmc/"]').addClass('btn').html('<i class="sgicon-xbmc"></i>Update XBMC');
menu$.find('a:contains("Update show in Emby")').addClass('btn').html('<i class="sgicon-emby"></i>Update show in Emby');
menu$.find('a:contains("Update show in Kodi")').addClass('btn').html('<i class="sgicon-kodi"></i>Update show in Kodi');
menu$.find('a:contains("Update show in XBMC")').addClass('btn').html('<i class="sgicon-xbmc"></i>Update show in XBMC');
// menu$.find('a:contains("Update show in XBMC")').addClass('btn').html('<i class="sgicon-xbmc"></i>Update show in XBMC');
}
$(document).ready(function(){

View file

@ -249,12 +249,6 @@ TV_DOWNLOAD_DIR = None
UNPACK = False
SKIP_REMOVED_FILES = False
SAB_USERNAME = None
SAB_PASSWORD = None
SAB_APIKEY = None
SAB_CATEGORY = None
SAB_HOST = ''
NZBGET_USERNAME = None
NZBGET_PASSWORD = None
NZBGET_CATEGORY = None
@ -262,6 +256,12 @@ NZBGET_HOST = None
NZBGET_USE_HTTPS = False
NZBGET_PRIORITY = 100
SAB_USERNAME = None
SAB_PASSWORD = None
SAB_APIKEY = None
SAB_CATEGORY = None
SAB_HOST = ''
TORRENT_USERNAME = None
TORRENT_PASSWORD = None
TORRENT_HOST = ''
@ -289,6 +289,16 @@ KODI_HOST = ''
KODI_USERNAME = None
KODI_PASSWORD = None
USE_PLEX = False
PLEX_NOTIFY_ONSNATCH = False
PLEX_NOTIFY_ONDOWNLOAD = False
PLEX_NOTIFY_ONSUBTITLEDOWNLOAD = False
PLEX_UPDATE_LIBRARY = False
PLEX_SERVER_HOST = None
PLEX_HOST = None
PLEX_USERNAME = None
PLEX_PASSWORD = None
USE_XBMC = False
XBMC_ALWAYS_ON = True
XBMC_NOTIFY_ONSNATCH = False
@ -301,15 +311,57 @@ XBMC_HOST = ''
XBMC_USERNAME = None
XBMC_PASSWORD = None
USE_PLEX = False
PLEX_NOTIFY_ONSNATCH = False
PLEX_NOTIFY_ONDOWNLOAD = False
PLEX_NOTIFY_ONSUBTITLEDOWNLOAD = False
PLEX_UPDATE_LIBRARY = False
PLEX_SERVER_HOST = None
PLEX_HOST = None
PLEX_USERNAME = None
PLEX_PASSWORD = None
USE_NMJ = False
NMJ_HOST = None
NMJ_DATABASE = None
NMJ_MOUNT = None
USE_NMJv2 = False
NMJv2_HOST = None
NMJv2_DATABASE = None
NMJv2_DBLOC = None
USE_SYNOINDEX = False
SYNOINDEX_UPDATE_LIBRARY = True
USE_SYNOLOGYNOTIFIER = False
SYNOLOGYNOTIFIER_NOTIFY_ONSNATCH = False
SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD = False
SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD = False
USE_PYTIVO = False
PYTIVO_HOST = ''
PYTIVO_SHARE_NAME = ''
PYTIVO_TIVO_NAME = ''
PYTIVO_UPDATE_LIBRARY = True
# PYTIVO_NOTIFY_ONSNATCH = False
# PYTIVO_NOTIFY_ONDOWNLOAD = False
# PYTIVO_NOTIFY_ONSUBTITLEDOWNLOAD = False
# PYTIVO_UPDATE_LIBRARY = False
USE_BOXCAR2 = False
BOXCAR2_NOTIFY_ONSNATCH = False
BOXCAR2_NOTIFY_ONDOWNLOAD = False
BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD = False
BOXCAR2_ACCESSTOKEN = None
BOXCAR2_SOUND = None
USE_PUSHBULLET = False
PUSHBULLET_NOTIFY_ONSNATCH = False
PUSHBULLET_NOTIFY_ONDOWNLOAD = False
PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD = False
PUSHBULLET_ACCESS_TOKEN = None
PUSHBULLET_DEVICE_IDEN = None
USE_PUSHOVER = False
PUSHOVER_NOTIFY_ONSNATCH = False
PUSHOVER_NOTIFY_ONDOWNLOAD = False
PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD = False
PUSHOVER_USERKEY = None
PUSHOVER_APIKEY = None
PUSHOVER_PRIORITY = 0
PUSHOVER_DEVICE = None
PUSHOVER_SOUND = None
USE_GROWL = False
GROWL_NOTIFY_ONSNATCH = False
@ -325,59 +377,23 @@ PROWL_NOTIFY_ONSUBTITLEDOWNLOAD = False
PROWL_API = None
PROWL_PRIORITY = 0
USE_TWITTER = False
TWITTER_NOTIFY_ONSNATCH = False
TWITTER_NOTIFY_ONDOWNLOAD = False
TWITTER_NOTIFY_ONSUBTITLEDOWNLOAD = False
TWITTER_USERNAME = None
TWITTER_PASSWORD = None
TWITTER_PREFIX = None
USE_BOXCAR2 = False
BOXCAR2_NOTIFY_ONSNATCH = False
BOXCAR2_NOTIFY_ONDOWNLOAD = False
BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD = False
BOXCAR2_ACCESSTOKEN = None
BOXCAR2_SOUND = None
USE_PUSHOVER = False
PUSHOVER_NOTIFY_ONSNATCH = False
PUSHOVER_NOTIFY_ONDOWNLOAD = False
PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD = False
PUSHOVER_USERKEY = None
PUSHOVER_APIKEY = None
PUSHOVER_PRIORITY = 0
PUSHOVER_DEVICE = None
PUSHOVER_SOUND = None
USE_NMA = False
NMA_NOTIFY_ONSNATCH = False
NMA_NOTIFY_ONDOWNLOAD = False
NMA_NOTIFY_ONSUBTITLEDOWNLOAD = False
NMA_API = None
NMA_PRIORITY = 0
USE_LIBNOTIFY = False
LIBNOTIFY_NOTIFY_ONSNATCH = False
LIBNOTIFY_NOTIFY_ONDOWNLOAD = False
LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD = False
USE_NMJ = False
NMJ_HOST = None
NMJ_DATABASE = None
NMJ_MOUNT = None
USE_ANIDB = False
ANIDB_USERNAME = None
ANIDB_PASSWORD = None
ANIDB_USE_MYLIST = False
ADBA_CONNECTION = None
ANIME_TREAT_AS_HDTV = False
USE_SYNOINDEX = False
USE_NMJv2 = False
NMJv2_HOST = None
NMJv2_DATABASE = None
NMJv2_DBLOC = None
USE_SYNOLOGYNOTIFIER = False
SYNOLOGYNOTIFIER_NOTIFY_ONSNATCH = False
SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD = False
SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD = False
USE_PUSHALOT = False
PUSHALOT_NOTIFY_ONSNATCH = False
PUSHALOT_NOTIFY_ONDOWNLOAD = False
PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD = False
PUSHALOT_AUTHORIZATIONTOKEN = None
USE_TRAKT = False
TRAKT_REMOVE_WATCHLIST = False
@ -389,45 +405,41 @@ TRAKT_SYNC = False
TRAKT_DEFAULT_INDEXER = None
TRAKT_UPDATE_COLLECTION = {}
USE_PYTIVO = False
PYTIVO_NOTIFY_ONSNATCH = False
PYTIVO_NOTIFY_ONDOWNLOAD = False
PYTIVO_NOTIFY_ONSUBTITLEDOWNLOAD = 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_NOTIFY_ONSUBTITLEDOWNLOAD = False
NMA_API = None
NMA_PRIORITY = 0
USE_PUSHALOT = False
PUSHALOT_NOTIFY_ONSNATCH = False
PUSHALOT_NOTIFY_ONDOWNLOAD = False
PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD = False
PUSHALOT_AUTHORIZATIONTOKEN = None
USE_PUSHBULLET = False
PUSHBULLET_NOTIFY_ONSNATCH = False
PUSHBULLET_NOTIFY_ONDOWNLOAD = False
PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD = False
PUSHBULLET_ACCESS_TOKEN = None
PUSHBULLET_DEVICE_IDEN = None
USE_SLACK = False
SLACK_NOTIFY_ONSNATCH = False
SLACK_NOTIFY_ONDOWNLOAD = False
SLACK_NOTIFY_ONSUBTITLEDOWNLOAD = False
SLACK_CHANNEL = None
SLACK_AS_USER = False
SLACK_AS_AUTHED = False
SLACK_BOT_NAME = None
SLACK_ICON_URL = None
SLACK_ACCESS_TOKEN = None
USE_DISCORDAPP = False
DISCORDAPP_NOTIFY_ONSNATCH = False
DISCORDAPP_NOTIFY_ONDOWNLOAD = False
DISCORDAPP_NOTIFY_ONSUBTITLEDOWNLOAD = False
DISCORDAPP_AS_AUTHED = False
DISCORDAPP_USERNAME = None
DISCORDAPP_ICON_URL = None
DISCORDAPP_AS_TTS = None
DISCORDAPP_ACCESS_TOKEN = None
USE_GITTER = False
GITTER_NOTIFY_ONSNATCH = False
GITTER_NOTIFY_ONDOWNLOAD = False
GITTER_NOTIFY_ONSUBTITLEDOWNLOAD = False
GITTER_ROOM = None
GITTER_ACCESS_TOKEN = None
USE_TWITTER = False
TWITTER_NOTIFY_ONSNATCH = False
TWITTER_NOTIFY_ONDOWNLOAD = False
TWITTER_NOTIFY_ONSUBTITLEDOWNLOAD = False
TWITTER_USERNAME = None
TWITTER_PASSWORD = None
TWITTER_PREFIX = None
USE_EMAIL = False
EMAIL_OLD_SUBJECTS = None
EMAIL_NOTIFY_ONSNATCH = False
@ -441,6 +453,13 @@ EMAIL_PASSWORD = None
EMAIL_FROM = None
EMAIL_LIST = None
USE_ANIDB = False
ANIDB_USERNAME = None
ANIDB_PASSWORD = None
ANIDB_USE_MYLIST = False
ADBA_CONNECTION = None
ANIME_TREAT_AS_HDTV = False
GUI_NAME = None
DEFAULT_HOME = None
FANART_LIMIT = None
@ -617,8 +636,9 @@ def initialize(console_logging=True):
USE_SYNOINDEX, \
USE_SYNOLOGYNOTIFIER, SYNOLOGYNOTIFIER_NOTIFY_ONSNATCH, \
SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD, SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD, \
USE_PYTIVO, PYTIVO_HOST, PYTIVO_SHARE_NAME, PYTIVO_TIVO_NAME, \
PYTIVO_NOTIFY_ONSNATCH, PYTIVO_NOTIFY_ONDOWNLOAD, PYTIVO_NOTIFY_ONSUBTITLEDOWNLOAD, PYTIVO_UPDATE_LIBRARY
USE_PYTIVO, PYTIVO_HOST, PYTIVO_SHARE_NAME, PYTIVO_TIVO_NAME
# , \
# PYTIVO_NOTIFY_ONSNATCH, PYTIVO_NOTIFY_ONDOWNLOAD, PYTIVO_NOTIFY_ONSUBTITLEDOWNLOAD, PYTIVO_UPDATE_LIBRARY
# Notification Settings/Devices
global USE_GROWL, GROWL_NOTIFY_ONSNATCH, GROWL_NOTIFY_ONDOWNLOAD, GROWL_NOTIFY_ONSUBTITLEDOWNLOAD, \
GROWL_HOST, GROWL_PASSWORD, \
@ -642,7 +662,12 @@ def initialize(console_logging=True):
TRAKT_USE_WATCHLIST, TRAKT_REMOVE_WATCHLIST, TRAKT_TIMEOUT, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, \
TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, TRAKT_UPDATE_COLLECTION, \
USE_SLACK, SLACK_NOTIFY_ONSNATCH, SLACK_NOTIFY_ONDOWNLOAD, SLACK_NOTIFY_ONSUBTITLEDOWNLOAD, \
SLACK_CHANNEL, SLACK_AS_USER, SLACK_BOT_NAME, SLACK_ICON_URL, SLACK_ACCESS_TOKEN, \
SLACK_CHANNEL, SLACK_AS_AUTHED, SLACK_BOT_NAME, SLACK_ICON_URL, SLACK_ACCESS_TOKEN, \
USE_DISCORDAPP, DISCORDAPP_NOTIFY_ONSNATCH, DISCORDAPP_NOTIFY_ONDOWNLOAD, \
DISCORDAPP_NOTIFY_ONSUBTITLEDOWNLOAD, \
DISCORDAPP_AS_AUTHED, DISCORDAPP_USERNAME, DISCORDAPP_ICON_URL, DISCORDAPP_AS_TTS, DISCORDAPP_ACCESS_TOKEN,\
USE_GITTER, GITTER_NOTIFY_ONSNATCH, GITTER_NOTIFY_ONDOWNLOAD, GITTER_NOTIFY_ONSUBTITLEDOWNLOAD,\
GITTER_ROOM, GITTER_ACCESS_TOKEN, \
USE_EMAIL, EMAIL_NOTIFY_ONSNATCH, EMAIL_NOTIFY_ONDOWNLOAD, EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD, EMAIL_FROM, \
EMAIL_HOST, EMAIL_PORT, EMAIL_TLS, EMAIL_USER, EMAIL_PASSWORD, EMAIL_LIST, EMAIL_OLD_SUBJECTS
# Anime Settings
@ -652,7 +677,8 @@ def initialize(console_logging=True):
return False
for stanza in ('General', 'Blackhole', 'SABnzbd', 'NZBget', 'Emby', 'Kodi', 'XBMC', 'PLEX',
'Growl', 'Prowl', 'Twitter', 'Slack', 'Boxcar2', 'NMJ', 'NMJv2', 'Synology', 'SynologyNotifier',
'Growl', 'Prowl', 'Twitter', 'Slack', 'Discordapp', 'Boxcar2', 'NMJ', 'NMJv2',
'Synology', 'SynologyNotifier',
'pyTivo', 'NMA', 'Pushalot', 'Pushbullet', 'Subtitles'):
CheckSection(CFG, stanza)
@ -1015,13 +1041,13 @@ def initialize(console_logging=True):
CheckSection(CFG, 'pyTivo')
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_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'pyTivo', 'pytivo_notify_onsubtitledownload', 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', '')
# 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_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'pyTivo', 'pytivo_notify_onsubtitledownload', 0))
# PYTIVO_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'pyTivo', 'pyTivo_update_library', 0))
USE_NMA = bool(check_setting_int(CFG, 'NMA', 'use_nma', 0))
NMA_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'NMA', 'nma_notify_onsnatch', 0))
@ -1050,11 +1076,29 @@ def initialize(console_logging=True):
SLACK_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Slack', 'slack_notify_ondownload', 0))
SLACK_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Slack', 'slack_notify_onsubtitledownload', 0))
SLACK_CHANNEL = check_setting_str(CFG, 'Slack', 'slack_channel', '')
SLACK_AS_USER = bool(check_setting_int(CFG, 'Slack', 'slack_as_user', 0))
SLACK_AS_AUTHED = bool(check_setting_int(CFG, 'Slack', 'slack_as_authed', 0))
SLACK_BOT_NAME = check_setting_str(CFG, 'Slack', 'slack_bot_name', '')
SLACK_ICON_URL = check_setting_str(CFG, 'Slack', 'slack_icon_url', '')
SLACK_ACCESS_TOKEN = check_setting_str(CFG, 'Slack', 'slack_access_token', '')
USE_DISCORDAPP = bool(check_setting_int(CFG, 'Discordapp', 'use_discordapp', 0))
DISCORDAPP_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Discordapp', 'discordapp_notify_onsnatch', 0))
DISCORDAPP_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Discordapp', 'discordapp_notify_ondownload', 0))
DISCORDAPP_NOTIFY_ONSUBTITLEDOWNLOAD = bool(
check_setting_int(CFG, 'Discordapp', 'discordapp_notify_onsubtitledownload', 0))
DISCORDAPP_AS_AUTHED = bool(check_setting_int(CFG, 'Discordapp', 'discordapp_as_authed', 0))
DISCORDAPP_USERNAME = check_setting_str(CFG, 'Discordapp', 'discordapp_username', '')
DISCORDAPP_ICON_URL = check_setting_str(CFG, 'Discordapp', 'discordapp_icon_url', '')
DISCORDAPP_AS_TTS = bool(check_setting_str(CFG, 'Discordapp', 'discordapp_as_tts', 0))
DISCORDAPP_ACCESS_TOKEN = check_setting_str(CFG, 'Discordapp', 'discordapp_access_token', '')
USE_GITTER = bool(check_setting_int(CFG, 'Gitter', 'use_gitter', 0))
GITTER_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Gitter', 'gitter_notify_onsnatch', 0))
GITTER_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Gitter', 'gitter_notify_ondownload', 0))
GITTER_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Gitter', 'gitter_notify_onsubtitledownload', 0))
GITTER_ROOM = check_setting_str(CFG, 'Gitter', 'gitter_room', '')
GITTER_ACCESS_TOKEN = check_setting_str(CFG, 'Gitter', 'gitter_access_token', '')
USE_EMAIL = bool(check_setting_int(CFG, 'Email', 'use_email', 0))
EMAIL_OLD_SUBJECTS = bool(check_setting_int(CFG, 'Email', 'email_old_subjects',
None is not EMAIL_HOST and any(EMAIL_HOST)))
@ -1691,90 +1735,40 @@ def save_config():
new_config['Kodi'] = {}
new_config['Kodi']['use_kodi'] = int(USE_KODI)
new_config['Kodi']['kodi_always_on'] = int(KODI_ALWAYS_ON)
new_config['Kodi']['kodi_notify_onsnatch'] = int(KODI_NOTIFY_ONSNATCH)
new_config['Kodi']['kodi_notify_ondownload'] = int(KODI_NOTIFY_ONDOWNLOAD)
new_config['Kodi']['kodi_notify_onsubtitledownload'] = int(KODI_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Kodi']['kodi_update_library'] = int(KODI_UPDATE_LIBRARY)
new_config['Kodi']['kodi_update_full'] = int(KODI_UPDATE_FULL)
new_config['Kodi']['kodi_update_onlyfirst'] = int(KODI_UPDATE_ONLYFIRST)
new_config['Kodi']['kodi_host'] = KODI_HOST
new_config['Kodi']['kodi_username'] = KODI_USERNAME
new_config['Kodi']['kodi_password'] = helpers.encrypt(KODI_PASSWORD, ENCRYPTION_VERSION)
new_config['Kodi']['kodi_notify_onsnatch'] = int(KODI_NOTIFY_ONSNATCH)
new_config['Kodi']['kodi_notify_ondownload'] = int(KODI_NOTIFY_ONDOWNLOAD)
new_config['Kodi']['kodi_notify_onsubtitledownload'] = int(KODI_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Plex'] = {}
new_config['Plex']['use_plex'] = int(USE_PLEX)
new_config['Plex']['plex_username'] = PLEX_USERNAME
new_config['Plex']['plex_password'] = helpers.encrypt(PLEX_PASSWORD, ENCRYPTION_VERSION)
new_config['Plex']['plex_update_library'] = int(PLEX_UPDATE_LIBRARY)
new_config['Plex']['plex_server_host'] = PLEX_SERVER_HOST
new_config['Plex']['plex_notify_onsnatch'] = int(PLEX_NOTIFY_ONSNATCH)
new_config['Plex']['plex_notify_ondownload'] = int(PLEX_NOTIFY_ONDOWNLOAD)
new_config['Plex']['plex_notify_onsubtitledownload'] = int(PLEX_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Plex']['plex_host'] = PLEX_HOST
new_config['XBMC'] = {}
new_config['XBMC']['use_xbmc'] = int(USE_XBMC)
new_config['XBMC']['xbmc_always_on'] = int(XBMC_ALWAYS_ON)
new_config['XBMC']['xbmc_notify_onsnatch'] = int(XBMC_NOTIFY_ONSNATCH)
new_config['XBMC']['xbmc_notify_ondownload'] = int(XBMC_NOTIFY_ONDOWNLOAD)
new_config['XBMC']['xbmc_notify_onsubtitledownload'] = int(XBMC_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['XBMC']['xbmc_update_library'] = int(XBMC_UPDATE_LIBRARY)
new_config['XBMC']['xbmc_update_full'] = int(XBMC_UPDATE_FULL)
new_config['XBMC']['xbmc_update_onlyfirst'] = int(XBMC_UPDATE_ONLYFIRST)
new_config['XBMC']['xbmc_notify_onsnatch'] = int(XBMC_NOTIFY_ONSNATCH)
new_config['XBMC']['xbmc_notify_ondownload'] = int(XBMC_NOTIFY_ONDOWNLOAD)
new_config['XBMC']['xbmc_notify_onsubtitledownload'] = int(XBMC_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['XBMC']['xbmc_host'] = XBMC_HOST
new_config['XBMC']['xbmc_username'] = XBMC_USERNAME
new_config['XBMC']['xbmc_password'] = helpers.encrypt(XBMC_PASSWORD, ENCRYPTION_VERSION)
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_notify_onsubtitledownload'] = int(PLEX_NOTIFY_ONSUBTITLEDOWNLOAD)
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
new_config['Plex']['plex_password'] = helpers.encrypt(PLEX_PASSWORD, ENCRYPTION_VERSION)
new_config['Growl'] = {}
new_config['Growl']['use_growl'] = int(USE_GROWL)
new_config['Growl']['growl_notify_onsnatch'] = int(GROWL_NOTIFY_ONSNATCH)
new_config['Growl']['growl_notify_ondownload'] = int(GROWL_NOTIFY_ONDOWNLOAD)
new_config['Growl']['growl_notify_onsubtitledownload'] = int(GROWL_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Growl']['growl_host'] = GROWL_HOST
new_config['Growl']['growl_password'] = helpers.encrypt(GROWL_PASSWORD, ENCRYPTION_VERSION)
new_config['Prowl'] = {}
new_config['Prowl']['use_prowl'] = int(USE_PROWL)
new_config['Prowl']['prowl_notify_onsnatch'] = int(PROWL_NOTIFY_ONSNATCH)
new_config['Prowl']['prowl_notify_ondownload'] = int(PROWL_NOTIFY_ONDOWNLOAD)
new_config['Prowl']['prowl_notify_onsubtitledownload'] = int(PROWL_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Prowl']['prowl_api'] = PROWL_API
new_config['Prowl']['prowl_priority'] = PROWL_PRIORITY
new_config['Twitter'] = {}
new_config['Twitter']['use_twitter'] = int(USE_TWITTER)
new_config['Twitter']['twitter_notify_onsnatch'] = int(TWITTER_NOTIFY_ONSNATCH)
new_config['Twitter']['twitter_notify_ondownload'] = int(TWITTER_NOTIFY_ONDOWNLOAD)
new_config['Twitter']['twitter_notify_onsubtitledownload'] = int(TWITTER_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Twitter']['twitter_username'] = TWITTER_USERNAME
new_config['Twitter']['twitter_password'] = helpers.encrypt(TWITTER_PASSWORD, ENCRYPTION_VERSION)
new_config['Twitter']['twitter_prefix'] = TWITTER_PREFIX
new_config['Boxcar2'] = {}
new_config['Boxcar2']['use_boxcar2'] = int(USE_BOXCAR2)
new_config['Boxcar2']['boxcar2_notify_onsnatch'] = int(BOXCAR2_NOTIFY_ONSNATCH)
new_config['Boxcar2']['boxcar2_notify_ondownload'] = int(BOXCAR2_NOTIFY_ONDOWNLOAD)
new_config['Boxcar2']['boxcar2_notify_onsubtitledownload'] = int(BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Boxcar2']['boxcar2_accesstoken'] = BOXCAR2_ACCESSTOKEN
new_config['Boxcar2']['boxcar2_sound'] = BOXCAR2_SOUND
new_config['Pushover'] = {}
new_config['Pushover']['use_pushover'] = int(USE_PUSHOVER)
new_config['Pushover']['pushover_notify_onsnatch'] = int(PUSHOVER_NOTIFY_ONSNATCH)
new_config['Pushover']['pushover_notify_ondownload'] = int(PUSHOVER_NOTIFY_ONDOWNLOAD)
new_config['Pushover']['pushover_notify_onsubtitledownload'] = int(PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Pushover']['pushover_userkey'] = PUSHOVER_USERKEY
new_config['Pushover']['pushover_apikey'] = PUSHOVER_APIKEY
new_config['Pushover']['pushover_priority'] = int(PUSHOVER_PRIORITY)
new_config['Pushover']['pushover_device'] = PUSHOVER_DEVICE
new_config['Pushover']['pushover_sound'] = PUSHOVER_SOUND
new_config['Libnotify'] = {}
new_config['Libnotify']['use_libnotify'] = int(USE_LIBNOTIFY)
new_config['Libnotify']['libnotify_notify_onsnatch'] = int(LIBNOTIFY_NOTIFY_ONSNATCH)
new_config['Libnotify']['libnotify_notify_ondownload'] = int(LIBNOTIFY_NOTIFY_ONDOWNLOAD)
new_config['Libnotify']['libnotify_notify_onsubtitledownload'] = int(LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['NMJ'] = {}
new_config['NMJ']['use_nmj'] = int(USE_NMJ)
new_config['NMJ']['nmj_host'] = NMJ_HOST
@ -1797,6 +1791,80 @@ def save_config():
new_config['SynologyNotifier']['synologynotifier_notify_onsubtitledownload'] = int(
SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['pyTivo'] = {}
new_config['pyTivo']['use_pytivo'] = int(USE_PYTIVO)
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['pyTivo']['pytivo_notify_onsnatch'] = int(PYTIVO_NOTIFY_ONSNATCH)
# new_config['pyTivo']['pytivo_notify_ondownload'] = int(PYTIVO_NOTIFY_ONDOWNLOAD)
# new_config['pyTivo']['pytivo_notify_onsubtitledownload'] = int(PYTIVO_NOTIFY_ONSUBTITLEDOWNLOAD)
# new_config['pyTivo']['pyTivo_update_library'] = int(PYTIVO_UPDATE_LIBRARY)
new_config['Boxcar2'] = {}
new_config['Boxcar2']['use_boxcar2'] = int(USE_BOXCAR2)
new_config['Boxcar2']['boxcar2_notify_onsnatch'] = int(BOXCAR2_NOTIFY_ONSNATCH)
new_config['Boxcar2']['boxcar2_notify_ondownload'] = int(BOXCAR2_NOTIFY_ONDOWNLOAD)
new_config['Boxcar2']['boxcar2_notify_onsubtitledownload'] = int(BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Boxcar2']['boxcar2_accesstoken'] = BOXCAR2_ACCESSTOKEN
new_config['Boxcar2']['boxcar2_sound'] = BOXCAR2_SOUND
new_config['Pushbullet'] = {}
new_config['Pushbullet']['use_pushbullet'] = int(USE_PUSHBULLET)
new_config['Pushbullet']['pushbullet_notify_onsnatch'] = int(PUSHBULLET_NOTIFY_ONSNATCH)
new_config['Pushbullet']['pushbullet_notify_ondownload'] = int(PUSHBULLET_NOTIFY_ONDOWNLOAD)
new_config['Pushbullet']['pushbullet_notify_onsubtitledownload'] = int(PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Pushbullet']['pushbullet_access_token'] = PUSHBULLET_ACCESS_TOKEN
new_config['Pushbullet']['pushbullet_device_iden'] = PUSHBULLET_DEVICE_IDEN
new_config['Pushover'] = {}
new_config['Pushover']['use_pushover'] = int(USE_PUSHOVER)
new_config['Pushover']['pushover_notify_onsnatch'] = int(PUSHOVER_NOTIFY_ONSNATCH)
new_config['Pushover']['pushover_notify_ondownload'] = int(PUSHOVER_NOTIFY_ONDOWNLOAD)
new_config['Pushover']['pushover_notify_onsubtitledownload'] = int(PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Pushover']['pushover_userkey'] = PUSHOVER_USERKEY
new_config['Pushover']['pushover_apikey'] = PUSHOVER_APIKEY
new_config['Pushover']['pushover_priority'] = int(PUSHOVER_PRIORITY)
new_config['Pushover']['pushover_device'] = PUSHOVER_DEVICE
new_config['Pushover']['pushover_sound'] = PUSHOVER_SOUND
new_config['Growl'] = {}
new_config['Growl']['use_growl'] = int(USE_GROWL)
new_config['Growl']['growl_notify_onsnatch'] = int(GROWL_NOTIFY_ONSNATCH)
new_config['Growl']['growl_notify_ondownload'] = int(GROWL_NOTIFY_ONDOWNLOAD)
new_config['Growl']['growl_notify_onsubtitledownload'] = int(GROWL_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Growl']['growl_host'] = GROWL_HOST
new_config['Growl']['growl_password'] = helpers.encrypt(GROWL_PASSWORD, ENCRYPTION_VERSION)
new_config['Prowl'] = {}
new_config['Prowl']['use_prowl'] = int(USE_PROWL)
new_config['Prowl']['prowl_notify_onsnatch'] = int(PROWL_NOTIFY_ONSNATCH)
new_config['Prowl']['prowl_notify_ondownload'] = int(PROWL_NOTIFY_ONDOWNLOAD)
new_config['Prowl']['prowl_notify_onsubtitledownload'] = int(PROWL_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Prowl']['prowl_api'] = PROWL_API
new_config['Prowl']['prowl_priority'] = PROWL_PRIORITY
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_notify_onsubtitledownload'] = int(NMA_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['NMA']['nma_api'] = NMA_API
new_config['NMA']['nma_priority'] = NMA_PRIORITY
new_config['Libnotify'] = {}
new_config['Libnotify']['use_libnotify'] = int(USE_LIBNOTIFY)
new_config['Libnotify']['libnotify_notify_onsnatch'] = int(LIBNOTIFY_NOTIFY_ONSNATCH)
new_config['Libnotify']['libnotify_notify_ondownload'] = int(LIBNOTIFY_NOTIFY_ONDOWNLOAD)
new_config['Libnotify']['libnotify_notify_onsubtitledownload'] = int(LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Pushalot'] = {}
new_config['Pushalot']['use_pushalot'] = int(USE_PUSHALOT)
new_config['Pushalot']['pushalot_notify_onsnatch'] = int(PUSHALOT_NOTIFY_ONSNATCH)
new_config['Pushalot']['pushalot_notify_ondownload'] = int(PUSHALOT_NOTIFY_ONDOWNLOAD)
new_config['Pushalot']['pushalot_notify_onsubtitledownload'] = int(PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Pushalot']['pushalot_authorizationtoken'] = PUSHALOT_AUTHORIZATIONTOKEN
new_config['Trakt'] = {}
new_config['Trakt']['use_trakt'] = int(USE_TRAKT)
new_config['Trakt']['trakt_remove_watchlist'] = int(TRAKT_REMOVE_WATCHLIST)
@ -1810,50 +1878,45 @@ def save_config():
new_config['Trakt']['trakt_accounts'] = TraktAPI.build_config_string(TRAKT_ACCOUNTS)
new_config['Trakt']['trakt_mru'] = TRAKT_MRU
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_notify_onsubtitledownload'] = int(PYTIVO_NOTIFY_ONSUBTITLEDOWNLOAD)
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_notify_onsubtitledownload'] = int(NMA_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['NMA']['nma_api'] = NMA_API
new_config['NMA']['nma_priority'] = NMA_PRIORITY
new_config['Pushalot'] = {}
new_config['Pushalot']['use_pushalot'] = int(USE_PUSHALOT)
new_config['Pushalot']['pushalot_notify_onsnatch'] = int(PUSHALOT_NOTIFY_ONSNATCH)
new_config['Pushalot']['pushalot_notify_ondownload'] = int(PUSHALOT_NOTIFY_ONDOWNLOAD)
new_config['Pushalot']['pushalot_notify_onsubtitledownload'] = int(PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Pushalot']['pushalot_authorizationtoken'] = PUSHALOT_AUTHORIZATIONTOKEN
new_config['Pushbullet'] = {}
new_config['Pushbullet']['use_pushbullet'] = int(USE_PUSHBULLET)
new_config['Pushbullet']['pushbullet_notify_onsnatch'] = int(PUSHBULLET_NOTIFY_ONSNATCH)
new_config['Pushbullet']['pushbullet_notify_ondownload'] = int(PUSHBULLET_NOTIFY_ONDOWNLOAD)
new_config['Pushbullet']['pushbullet_notify_onsubtitledownload'] = int(PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Pushbullet']['pushbullet_access_token'] = PUSHBULLET_ACCESS_TOKEN
new_config['Pushbullet']['pushbullet_device_iden'] = PUSHBULLET_DEVICE_IDEN
new_config['Slack'] = {}
new_config['Slack']['use_slack'] = int(USE_SLACK)
new_config['Slack']['slack_notify_onsnatch'] = int(SLACK_NOTIFY_ONSNATCH)
new_config['Slack']['slack_notify_ondownload'] = int(SLACK_NOTIFY_ONDOWNLOAD)
new_config['Slack']['slack_notify_onsubtitledownload'] = int(SLACK_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Slack']['slack_channel'] = SLACK_CHANNEL
new_config['Slack']['slack_as_user'] = int(SLACK_AS_USER)
new_config['Slack']['slack_as_authed'] = int(SLACK_AS_AUTHED)
new_config['Slack']['slack_bot_name'] = SLACK_BOT_NAME
new_config['Slack']['slack_icon_url'] = SLACK_ICON_URL
new_config['Slack']['slack_access_token'] = SLACK_ACCESS_TOKEN
new_config['Discordapp'] = {}
new_config['Discordapp']['use_discordapp'] = int(USE_DISCORDAPP)
new_config['Discordapp']['discordapp_notify_onsnatch'] = int(DISCORDAPP_NOTIFY_ONSNATCH)
new_config['Discordapp']['discordapp_notify_ondownload'] = int(DISCORDAPP_NOTIFY_ONDOWNLOAD)
new_config['Discordapp']['discordapp_notify_onsubtitledownload'] = int(DISCORDAPP_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Discordapp']['discordapp_as_authed'] = int(DISCORDAPP_AS_AUTHED)
new_config['Discordapp']['discordapp_username'] = DISCORDAPP_USERNAME
new_config['Discordapp']['discordapp_icon_url'] = DISCORDAPP_ICON_URL
new_config['Discordapp']['discordapp_as_tts'] = int(DISCORDAPP_AS_TTS)
new_config['Discordapp']['discordapp_access_token'] = DISCORDAPP_ACCESS_TOKEN
new_config['Gitter'] = {}
new_config['Gitter']['use_gitter'] = int(USE_GITTER)
new_config['Gitter']['gitter_notify_onsnatch'] = int(GITTER_NOTIFY_ONSNATCH)
new_config['Gitter']['gitter_notify_ondownload'] = int(GITTER_NOTIFY_ONDOWNLOAD)
new_config['Gitter']['gitter_notify_onsubtitledownload'] = int(GITTER_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Gitter']['gitter_room'] = GITTER_ROOM
new_config['Gitter']['gitter_access_token'] = GITTER_ACCESS_TOKEN
new_config['Twitter'] = {}
new_config['Twitter']['use_twitter'] = int(USE_TWITTER)
new_config['Twitter']['twitter_notify_onsnatch'] = int(TWITTER_NOTIFY_ONSNATCH)
new_config['Twitter']['twitter_notify_ondownload'] = int(TWITTER_NOTIFY_ONDOWNLOAD)
new_config['Twitter']['twitter_notify_onsubtitledownload'] = int(TWITTER_NOTIFY_ONSUBTITLEDOWNLOAD)
new_config['Twitter']['twitter_username'] = TWITTER_USERNAME
new_config['Twitter']['twitter_password'] = helpers.encrypt(TWITTER_PASSWORD, ENCRYPTION_VERSION)
new_config['Twitter']['twitter_prefix'] = TWITTER_PREFIX
new_config['Email'] = {}
new_config['Email']['use_email'] = int(USE_EMAIL)
new_config['Email']['email_old_subjects'] = int(EMAIL_OLD_SUBJECTS)

View file

@ -224,7 +224,7 @@ def makeDir(path):
try:
ek.ek(os.makedirs, path)
# do the library update for synoindex
notifiers.synoindex_notifier.addFolder(path)
notifiers.NotifierFactory().get('SYNOINDEX').addFolder(path)
except OSError:
return False
return True
@ -394,7 +394,7 @@ def make_dirs(path):
# use normpath to remove end separator, otherwise checks permissions against itself
chmodAsParent(ek.ek(os.path.normpath, sofar))
# do the library update for synoindex
notifiers.synoindex_notifier.addFolder(sofar)
notifiers.NotifierFactory().get('SYNOINDEX').addFolder(sofar)
except (OSError, IOError) as e:
logger.log(u'Failed creating %s : %s' % (sofar, ex(e)), logger.ERROR)
return False
@ -477,7 +477,7 @@ def delete_empty_folders(check_empty_dir, keep_dir=None):
# need shutil.rmtree when ignore_items is really implemented
ek.ek(os.rmdir, check_empty_dir)
# do the library update for synoindex
notifiers.synoindex_notifier.deleteFolder(check_empty_dir)
notifiers.NotifierFactory().get('SYNOINDEX').deleteFolder(check_empty_dir)
except OSError as e:
logger.log(u"Unable to delete " + check_empty_dir + ": " + repr(e) + " / " + str(e), logger.WARNING)
break

View file

@ -18,93 +18,141 @@
import emby
import kodi
import xbmc
import plex
import xbmc
import nmj
import nmjv2
import synoindex
import synologynotifier
import pytivo
import trakt
import boxcar2
# import pushalot
import pushbullet
import pushover
import growl
import prowl
from . import libnotify
import pushover
import boxcar2
import nma
import pushalot
import pushbullet
from . import libnotify
import slack
import tweet
from lib import libtrakt
import trakt
import slack
import discordapp
import gitter
import tweet
import emailnotify
# home theater / nas
emby_notifier = emby.EmbyNotifier()
kodi_notifier = kodi.KodiNotifier()
xbmc_notifier = xbmc.XBMCNotifier()
plex_notifier = plex.PLEXNotifier()
nmj_notifier = nmj.NMJNotifier()
nmjv2_notifier = nmjv2.NMJv2Notifier()
synoindex_notifier = synoindex.synoIndexNotifier()
synology_notifier = synologynotifier.synologyNotifier()
pytivo_notifier = pytivo.pyTivoNotifier()
# devices
growl_notifier = growl.GrowlNotifier()
prowl_notifier = prowl.ProwlNotifier()
libnotify_notifier = libnotify.LibnotifyNotifier()
pushover_notifier = pushover.PushoverNotifier()
boxcar2_notifier = boxcar2.Boxcar2Notifier()
nma_notifier = nma.NMA_Notifier()
pushalot_notifier = pushalot.PushalotNotifier()
pushbullet_notifier = pushbullet.PushbulletNotifier()
# social
twitter_notifier = tweet.TwitterNotifier()
trakt_notifier = trakt.TraktNotifier()
slack_notifier = slack.SlackNotifier()
email_notifier = emailnotify.EmailNotifier()
import sickbeard
notifiers = [
libnotify_notifier, # Libnotify notifier goes first because it doesn't involve blocking on network activity.
kodi_notifier,
xbmc_notifier,
plex_notifier,
nmj_notifier,
nmjv2_notifier,
synoindex_notifier,
synology_notifier,
pytivo_notifier,
growl_notifier,
prowl_notifier,
pushover_notifier,
boxcar2_notifier,
nma_notifier,
pushalot_notifier,
pushbullet_notifier,
twitter_notifier,
trakt_notifier,
slack_notifier,
email_notifier,
]
class NotifierFactory(object):
def __init__(self):
self.notifiers = dict(
# home theater / nas
EMBY=emby.EmbyNotifier,
KODI=kodi.KodiNotifier,
PLEX=plex.PLEXNotifier,
# ### XBMC=xbmc.XBMCNotifier,
NMJ=nmj.NMJNotifier,
NMJV2=nmjv2.NMJv2Notifier,
SYNOINDEX=synoindex.SynoIndexNotifier,
SYNOLOGY=synologynotifier.SynologyNotifier,
PYTIVO=pytivo.PyTivoNotifier,
# devices,
BOXCAR2=boxcar2.Boxcar2Notifier,
# PUSHALOT=pushalot.PushalotNotifier,
PUSHBULLET=pushbullet.PushbulletNotifier,
PUSHOVER=pushover.PushoverNotifier,
GROWL=growl.GrowlNotifier,
PROWL=prowl.ProwlNotifier,
NMA=nma.NMANotifier,
LIBNOTIFY=libnotify.LibnotifyNotifier,
# social
TRAKT=trakt.TraktNotifier,
SLACK=slack.SlackNotifier,
DISCORDAPP=discordapp.DiscordappNotifier,
GITTER=gitter.GitterNotifier,
TWITTER=tweet.TwitterNotifier,
EMAIL=emailnotify.EmailNotifier,
)
@property
def enabled(self):
"""
Generator to yield iterable IDs for enabled notifiers
:return: ID String
:rtype: String
"""
for n in filter(lambda v: v.is_enabled(), self.notifiers.values()):
yield n.id()
@property
def enabled_onsnatch(self):
for n in filter(lambda v: v.is_enabled() and v.is_enabled_onsnatch(), self.notifiers.values()):
yield n.id()
@property
def enabled_ondownload(self):
for n in filter(lambda v: v.is_enabled() and v.is_enabled_ondownload(), self.notifiers.values()):
yield n.id()
@property
def enabled_onsubtitledownload(self):
for n in filter(lambda v: v.is_enabled() and v.is_enabled_onsubtitledownload(), self.notifiers.values()):
yield n.id()
@property
def enabled_library(self):
for n in filter(lambda v: v.is_enabled() and v.is_enabled_library(), self.notifiers.values()):
yield n.id()
def get(self, nid):
"""
Get a notifier instance
:param nid: Notified ID
:type nid: String
:return: Notifier instance
:rtype: Notifier
"""
return self.notifiers[nid]()
def get_enabled(self, kind=None):
"""
Generator to yield iterable notifier instance(s) that are either enabled or enabled for requested actions
:param kind: Action
:type kind: String
:return: Notifier instance
:rtype: Notifier
"""
for n in getattr(self, 'enabled' + ('' if None is kind else ('_' + kind))):
yield self.get(n)
def notify_snatch(ep_name):
for n in NotifierFactory().get_enabled('onsnatch'):
n.notify_snatch(ep_name)
def notify_download(ep_name):
for n in notifiers:
for n in NotifierFactory().get_enabled('ondownload'):
n.notify_download(ep_name)
def notify_subtitle_download(ep_name, lang):
for n in notifiers:
for n in NotifierFactory().get_enabled('ondownloadsubtitles'):
n.notify_subtitle_download(ep_name, lang)
def notify_snatch(ep_name):
for n in notifiers:
n.notify_snatch(ep_name)
def notify_git_update(new_version=''):
for n in notifiers:
n.notify_git_update(new_version)
if sickbeard.NOTIFY_ON_UPDATE:
for n in NotifierFactory().get_enabled():
n.notify_git_update(new_version)
def notify_update_library(ep_obj):
for n in NotifierFactory().get_enabled('library'):
n.update_library(show=ep_obj.show, show_name=ep_obj.show.name, ep_obj=ep_obj)

View file

@ -18,115 +18,79 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import time
import urllib
import urllib2
import time
import sickbeard
from sickbeard import logger
from sickbeard.common import notifyStrings, NOTIFY_SNATCH, NOTIFY_DOWNLOAD, NOTIFY_SUBTITLE_DOWNLOAD, NOTIFY_GIT_UPDATE, NOTIFY_GIT_UPDATE_TEXT
from sickbeard.exceptions import ex
API_URL = 'https://new.boxcar.io/api/notifications'
from sickbeard.notifiers.generic import Notifier
class Boxcar2Notifier:
def _sendBoxcar2(self, title, msg, accesstoken, sound):
class Boxcar2Notifier(Notifier):
def __init__(self):
super(Boxcar2Notifier, self).__init__()
self.sg_logo_file = 'apple-touch-icon-60x60.png'
def _notify(self, title, body, access_token=None, sound=None, **kwargs):
"""
Sends a boxcar2 notification to the address provided
msg: The message to send
title: The title of the message
accesstoken: to send to this device
body: The message to send
access_token: To send to this device
sound: Sound profile to use
returns: True if the message succeeded, False otherwise
"""
access_token = self._choose(access_token, sickbeard.BOXCAR2_ACCESSTOKEN)
sound = self._choose(sound, sickbeard.BOXCAR2_SOUND)
# build up the URL and parameters
# more info goes here - https://boxcar.uservoice.com/knowledgebase/articles/306788-how-to-send-your-boxcar-account-a-notification
msg = msg.strip().encode('utf-8')
# more info goes here -
# https://boxcar.uservoice.com/knowledgebase/articles/306788-how-to-send-your-boxcar-account-a-notification
body = body.strip().encode('utf-8')
data = urllib.urlencode({
'user_credentials': accesstoken,
'notification[title]': title + ' - ' + msg,
'notification[long_message]': msg,
'user_credentials': access_token,
'notification[title]': '%s - %s' % (title, body),
'notification[long_message]': body,
'notification[sound]': sound,
'notification[source_name]': 'SickGear',
'notification[icon_url]': 'https://cdn.rawgit.com/SickGear/SickGear/master/gui/slick/images/ico/apple-touch-icon-60x60.png'
'notification[icon_url]': self._sg_logo_url
})
# send the request to boxcar2
result = None
try:
req = urllib2.Request(API_URL)
req = urllib2.Request('https://new.boxcar.io/api/notifications')
handle = urllib2.urlopen(req, data)
handle.close()
except urllib2.URLError as e:
# if we get an error back that doesn't have an error code then who knows what's really happening
if not hasattr(e, 'code'):
logger.log(u'BOXCAR2: Notification failed.' + ex(e), logger.ERROR)
self._log_error(u'Notification failed: %s' % ex(e))
else:
logger.log(u'BOXCAR2: Notification failed. Error code: ' + str(e.code), logger.ERROR)
result = 'Notification failed. Error code: %s' % e.code
self._log_error(result)
if e.code == 404:
logger.log(u'BOXCAR2: Access token is wrong/not associated to a device.', logger.ERROR)
elif e.code == 401:
logger.log(u'BOXCAR2: Access token not recognized.', logger.ERROR)
elif e.code == 400:
logger.log(u'BOXCAR2: Wrong data sent to boxcar.', logger.ERROR)
elif e.code == 503:
logger.log(u'BOXCAR2: Boxcar server to busy to handle the request at this time.', logger.WARNING)
return False
if 503 == e.code:
result = 'Server too busy to handle the request at this time'
self._log_warning(result)
else:
if 404 == e.code:
result = 'Access token is wrong/not associated to a device'
self._log_error(result)
elif 401 == e.code:
result = 'Access token not recognized'
self._log_error(result)
elif 400 == e.code:
result = 'Wrong data sent to Boxcar'
self._log_error(result)
logger.log(u'BOXCAR2: Notification successful.', logger.MESSAGE)
return True
return self._choose((True, 'Failed to send notification: %s' % result)[bool(result)], not bool(result))
def _notifyBoxcar2(self, title, message, accesstoken=None, sound=None, force=False):
"""
Sends a boxcar2 notification based on the provided info or SG config
title: The title of the notification to send
message: The message string to send
accesstoken: to send to this device
force: If True then the notification will be sent even if Boxcar is disabled in the config
"""
# suppress notifications if the notifier is disabled but the notify options are checked
if not sickbeard.USE_BOXCAR2 and not force:
logger.log(u'BOXCAR2: Notifications are not enabled, skipping this notification', logger.DEBUG)
return False
# fill in omitted parameters
if not accesstoken:
accesstoken = sickbeard.BOXCAR2_ACCESSTOKEN
if not sound:
sound = sickbeard.BOXCAR2_SOUND
logger.log(u'BOXCAR2: Sending notification for ' + message, logger.DEBUG)
self._sendBoxcar2(title, message, accesstoken, sound)
return True
def test_notify(self, accesstoken, sound, force=True):
return self._sendBoxcar2('Test', 'This is a test notification from SickGear', accesstoken, sound)
def notify_snatch(self, ep_name):
if sickbeard.BOXCAR2_NOTIFY_ONSNATCH:
self._notifyBoxcar2(notifyStrings[NOTIFY_SNATCH], ep_name)
def notify_download(self, ep_name):
if sickbeard.BOXCAR2_NOTIFY_ONDOWNLOAD:
self._notifyBoxcar2(notifyStrings[NOTIFY_DOWNLOAD], ep_name)
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD:
self._notifyBoxcar2(notifyStrings[NOTIFY_SUBTITLE_DOWNLOAD], ep_name + ': ' + lang)
def notify_git_update(self, new_version = '??'):
if sickbeard.USE_BOXCAR2:
update_text=notifyStrings[NOTIFY_GIT_UPDATE_TEXT]
title=notifyStrings[NOTIFY_GIT_UPDATE]
self._notifyBoxcar2(title, update_text + new_version)
notifier = Boxcar2Notifier

View file

@ -0,0 +1,47 @@
# coding=utf-8
#
# This file is part of SickGear.
#
# Thanks to: mallen86, generica
#
# SickGear 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.
#
# SickGear 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 SickGear. If not, see <http://www.gnu.org/licenses/>.
import sickbeard
from sickbeard.notifiers.generic import Notifier
class DiscordappNotifier(Notifier):
def __init__(self):
super(DiscordappNotifier, self).__init__()
def _notify(self, title, body, as_authed=None, username='', icon_url='', as_tts='', access_token='', **kwargs):
params = [] if not bool(self._choose(not as_authed, sickbeard.DISCORDAPP_AS_AUTHED)) else \
[('username', self._choose(username, sickbeard.DISCORDAPP_USERNAME) or 'SickGear'),
('avatar_url', self._choose(icon_url, sickbeard.DISCORDAPP_ICON_URL) or self._sg_logo_url)]
as_tts = self._choose(as_tts, bool(sickbeard.DISCORDAPP_AS_TTS))
resp = sickbeard.helpers.getURL(
url=self._choose(access_token, sickbeard.DISCORDAPP_ACCESS_TOKEN),
post_json=dict([('content', self._body_only(title, body)), ('tts', as_tts)] + params))
result = '' == resp or self._choose('bad webhook?', None)
if True is not result:
self._log_error('%s failed to send message: %s' % (self.name, result))
return self._choose(('Success, notification sent. (Note: %s clients display icon once in a sequence)'
% self.name, 'Failed to send notification, %s' % result)[True is not result], result)
notifier = DiscordappNotifier

View file

@ -19,118 +19,57 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import re
import sickbeard
import smtplib
from sickbeard import db
from sickbeard import logger
from sickbeard.common import notifyStrings, NOTIFY_SNATCH, NOTIFY_DOWNLOAD, NOTIFY_SUBTITLE_DOWNLOAD
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formatdate
import re
import smtplib
import sickbeard
from sickbeard import db
from sickbeard.notifiers.generic import Notifier, notify_strings
class EmailNotifier:
class EmailNotifier(Notifier):
def __init__(self):
super(EmailNotifier, self).__init__()
self.last_err = None
def test_notify(self, host, port, smtp_from, use_tls, user, pwd, to):
def _sendmail(self, host, port, smtp_from, use_tls, user, pwd, to, msg, smtp_debug=False):
msg = MIMEText('Success. This is a SickGear test message. Typically sent on, %s' %
notifyStrings[NOTIFY_DOWNLOAD])
msg['Subject'] = 'SickGear: Test message'
msg['From'] = smtp_from
msg['To'] = to
msg['Date'] = formatdate(localtime=True)
return self._sendmail(host, port, smtp_from, use_tls, user, pwd, [to], msg, True)
def _send_email(self, title, ep_name, lang='', extra='', force=False):
if not sickbeard.USE_EMAIL and not force:
return
show = ep_name.split(' - ')[0]
to = self._get_recipients(show)
if not any(to):
logger.log(u'No email recipients to notify, skipping', logger.WARNING)
return
logger.log(u'Email recipients to notify: %s' % to, logger.DEBUG)
use_tls = 1 == sickbeard.helpers.tryInt(use_tls)
login = any(user) and any(pwd)
self._log_debug(u'Sendmail HOST: %s; PORT: %s; LOGIN: %s, TLS: %s, USER: %s, FROM: %s, TO: %s' % (
host, port, login, use_tls, user, smtp_from, to))
try:
msg = MIMEMultipart('alternative')
msg.attach(MIMEText(
'<body style="font-family:Helvetica, Arial, sans-serif;">' +
'<h3>SickGear Notification - %s</h3>\n' % title +
'<p>Show: <b>' + show.encode('ascii', 'xmlcharrefreplace') +
'</b></p>\n<p>Episode: <b>' +
unicode(re.search('.+ - (.+?-.+) -.+', ep_name).group(1)).encode('ascii', 'xmlcharrefreplace') +
extra +
'</b></p>\n\n' +
'<footer style="margin-top:2.5em;padding:.7em 0;color:#777;border-top:#BBB solid 1px;">' +
'Powered by SickGear.</footer></body>',
'html'))
except:
try:
msg = MIMEText(ep_name)
except:
msg = MIMEText('Episode %s' % title)
srv = smtplib.SMTP(host, int(port))
if smtp_debug:
srv.set_debuglevel(1)
msg['Subject'] = '%s%s: %s' % (lang, title, ep_name)
msg['From'] = sickbeard.EMAIL_FROM
msg['To'] = ','.join(to)
msg['Date'] = formatdate(localtime=True)
if self._sendmail(sickbeard.EMAIL_HOST, sickbeard.EMAIL_PORT, sickbeard.EMAIL_FROM, sickbeard.EMAIL_TLS,
sickbeard.EMAIL_USER, sickbeard.EMAIL_PASSWORD, to, msg):
logger.log(u'%s notification sent to [%s] for "%s"' % (title, to, ep_name), logger.DEBUG)
else:
logger.log(u'%s notification ERROR: %s' % (title, self.last_err), logger.ERROR)
if use_tls or login:
srv.ehlo()
self._log_debug(u'Sent initial EHLO command')
def notify_snatch(self, ep_name, title=notifyStrings[NOTIFY_SNATCH]):
"""
Send a notification that an episode was snatched
if use_tls:
srv.starttls()
srv.ehlo()
self._log_debug(u'Sent STARTTLS and EHLO command')
:param ep_name: The name of the episode that was snatched
:param title: The title of the notification (optional)
"""
if login:
srv.login(user, pwd)
self._log_debug(u'Sent LOGIN command')
if sickbeard.EMAIL_NOTIFY_ONSNATCH:
title = sickbeard.EMAIL_OLD_SUBJECTS and 'Snatched' or title
self._send_email(title, ep_name)
srv.sendmail(smtp_from, to, msg.as_string())
srv.quit()
def notify_download(self, ep_name, title=notifyStrings[NOTIFY_DOWNLOAD]):
"""
Send a notification that an episode was downloaded
except Exception as e:
self.last_err = '%s' % e
return False
:param ep_name: The name of the episode that was downloaded
:param title: The title of the notification (optional)
"""
if sickbeard.EMAIL_NOTIFY_ONDOWNLOAD:
title = sickbeard.EMAIL_OLD_SUBJECTS and 'Downloaded' or title
self._send_email(title, ep_name)
def notify_subtitle_download(self, ep_name, lang, title=notifyStrings[NOTIFY_SUBTITLE_DOWNLOAD]):
"""
Send a notification that a subtitle was downloaded
:param ep_name: The name of the episode that was downloaded
:param lang: Subtitle language
:param title: The title of the notification (optional)
"""
if sickbeard.EMAIL_NOTIFY_ONSUBTITLEDOWNLOAD:
title = sickbeard.EMAIL_OLD_SUBJECTS and 'Subtitle Downloaded' or title
self._send_email(title, ep_name, '%s ' % lang, '</b></p>\n<p>Language: <b>%s' % lang)
def notify_git_update(self, new_version='??'):
pass
return True
@staticmethod
def _get_recipients(show_name=None):
@ -154,39 +93,91 @@ class EmailNotifier:
return list(set(email_list))
def _sendmail(self, host, port, smtp_from, use_tls, user, pwd, to, msg, smtp_debug=False):
def _notify(self, title, body, lang='', extra='', **kwargs):
use_tls = 1 == sickbeard.helpers.tryInt(use_tls)
login = any(user) and any(pwd)
logger.log(u'Sendmail HOST: %s; PORT: %s; LOGIN: %s, TLS: %s, USER: %s, FROM: %s, TO: %s' % (
host, port, login, use_tls, user, smtp_from, to), logger.DEBUG)
show = body.split(' - ')[0]
to = self._get_recipients(show)
if not any(to):
self._log_warning(u'No email recipients to notify, skipping')
return
self._log_debug(u'Email recipients to notify: %s' % to)
try:
srv = smtplib.SMTP(host, int(port))
if smtp_debug:
srv.set_debuglevel(1)
msg = MIMEMultipart('alternative')
msg.attach(MIMEText(
'<body style="font-family:Helvetica, Arial, sans-serif;">' +
'<h3>SickGear Notification - %s</h3>\n' % title +
'<p>Show: <b>' + show.encode('ascii', 'xmlcharrefreplace') +
'</b></p>\n<p>Episode: <b>' +
unicode(re.search('.+ - (.+?-.+) -.+', body).group(1)).encode('ascii', 'xmlcharrefreplace') +
extra +
'</b></p>\n\n' +
'<footer style="margin-top:2.5em;padding:.7em 0;color:#777;border-top:#BBB solid 1px;">' +
'Powered by SickGear.</footer></body>',
'html'))
except (StandardError, Exception):
try:
msg = MIMEText(body)
except (StandardError, Exception):
msg = MIMEText('Episode %s' % title)
if use_tls or login:
srv.ehlo()
logger.log(u'Sent initial EHLO command', logger.DEBUG)
msg['Subject'] = '%s%s: %s' % (lang, title, body)
msg['From'] = sickbeard.EMAIL_FROM
msg['To'] = ','.join(to)
msg['Date'] = formatdate(localtime=True)
if self._sendmail(sickbeard.EMAIL_HOST, sickbeard.EMAIL_PORT, sickbeard.EMAIL_FROM, sickbeard.EMAIL_TLS,
sickbeard.EMAIL_USER, sickbeard.EMAIL_PASSWORD, to, msg):
self._log_debug(u'%s notification sent to [%s] for "%s"' % (title, to, body))
else:
self._log_error(u'%s notification ERROR: %s' % (title, self.last_err))
if use_tls:
srv.starttls()
srv.ehlo()
logger.log(u'Sent STARTTLS and EHLO command', logger.DEBUG)
def test_notify(self, host, port, smtp_from, use_tls, user, pwd, to):
self._testing = True
if login:
srv.login(user, pwd)
logger.log(u'Sent LOGIN command', logger.DEBUG)
msg = MIMEText('Success. This is a SickGear test message. Typically sent on, %s' % notify_strings['download'])
msg['Subject'] = 'SickGear: Test message'
msg['From'] = smtp_from
msg['To'] = to
msg['Date'] = formatdate(localtime=True)
srv.sendmail(smtp_from, to, msg.as_string())
srv.quit()
r = self._sendmail(host, port, smtp_from, use_tls, user, pwd, [to], msg, True)
return self._choose(('Success, notification sent.',
'Failed to send notification: %s' % self.last_err)[not r], r)
except Exception as e:
self.last_err = '%s' % e
return False
def notify_snatch(self, ep_name, title=None):
"""
Send a notification that an episode was snatched
return True
:param ep_name: The name of the episode that was snatched
:param title: The title of the notification (optional)
"""
title = sickbeard.EMAIL_OLD_SUBJECTS and 'Snatched' or title or notify_strings['snatch']
self._notify(title, ep_name)
def notify_download(self, ep_name, title=None):
"""
Send a notification that an episode was downloaded
:param ep_name: The name of the episode that was downloaded
:param title: The title of the notification (optional)
"""
title = sickbeard.EMAIL_OLD_SUBJECTS and 'Downloaded' or title or notify_strings['download']
self._notify(title, ep_name)
def notify_subtitle_download(self, ep_name, lang, title=None):
"""
Send a notification that a subtitle was downloaded
:param ep_name: The name of the episode that was downloaded
:param lang: Subtitle language
:param title: The title of the notification (optional)
"""
title = sickbeard.EMAIL_OLD_SUBJECTS and 'Subtitle Downloaded' or title or notify_strings['subtitle_download']
self._notify(title, ep_name, '%s ' % lang, '</b></p>\n<p>Language: <b>%s' % lang)
notifier = EmailNotifier

View file

@ -15,68 +15,31 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import sickbeard
from sickbeard import logger
from socket import socket, AF_INET, SOCK_DGRAM, SOL_SOCKET, SO_REUSEADDR, SO_BROADCAST, SHUT_RDWR
import sickbeard
from sickbeard.notifiers.generic import Notifier
from lib import simplejson as json
class EmbyNotifier:
class EmbyNotifier(Notifier):
def __init__(self):
self.sg_logo_url = 'https://raw.githubusercontent.com/SickGear/SickGear/master/gui/slick/images/ico/' + \
'apple-touch-icon-precomposed.png'
super(EmbyNotifier, self).__init__()
self.response = None
self.test_mode = False
def _notify_emby(self, msg, hosts=None, apikeys=None):
""" Internal wrapper for the test_notify function
def update_library(self, show=None, **kwargs):
""" Update library function
Args:
msg: Message body of the notice to send
:param show: TVShow object
Returns:
2-Tuple True if msg successfully sent otherwise False, Failure message string or None
Returns: None if no processing done, True if processing succeeded with no issues else False if any issues found
"""
if not sickbeard.USE_EMBY and not self.test_mode:
self._log(u'Notification not enabled, skipping this notification', logger.DEBUG)
return False, None
hosts, keys, message = self._check_config(hosts, apikeys)
if not hosts:
return False, message
total_success = True
messages = []
args = dict(post_json={'Name': 'SickGear', 'Description': msg, 'ImageUrl': self.sg_logo_url})
for i, cur_host in enumerate(hosts):
self.response = None
response = sickbeard.helpers.getURL(
'http://%s/emby/Notifications/Admin' % cur_host,
headers={'Content-type': 'application/json', 'X-MediaBrowser-Token': keys[i]},
timeout=10, hooks=dict(response=self._cb_response), **args)
if not response or self.response:
if self.response and 401 == self.response.get('status_code'):
total_success = False
messages += ['Fail: Cannot authenticate API key with %s' % cur_host]
self._log(u'Failed to authenticate with %s' % cur_host)
continue
elif not response and not self.response or not self.response.get('ok'):
total_success = False
messages += ['Fail: No supported Emby server found at %s' % cur_host]
self._log(u'Warning, could not connect with server at ' + cur_host)
continue
messages += ['OK: %s' % cur_host]
return total_success, '<br />\n'.join(messages)
def _update_library(self, show=None):
hosts, keys, message = self._check_config()
if not hosts:
self._log(u'Issue with hosts or api keys, check your settings')
self._log_warning(u'Issue with hosts or api keys, check your settings')
return False
from sickbeard.indexers.indexer_config import INDEXER_TVDB
@ -94,30 +57,28 @@ class EmbyNotifier:
timeout=20, hooks=dict(response=self._cb_response), **args)
# Emby will initiate a LibraryMonitor path refresh one minute after this success
if self.response and 204 == self.response.get('status_code') and self.response.get('ok'):
self._log(u'Success: update %s sent to host %s in a library updated call' % (mode_to_log, cur_host),
logger.MESSAGE)
self._log(u'Success: update %s sent to host %s in a library updated call' % (mode_to_log, cur_host))
continue
elif self.response and 401 == self.response.get('status_code'):
self._log(u'Failed to authenticate with %s' % cur_host)
self._log_warning(u'Failed to authenticate with %s' % cur_host)
elif self.response and 404 == self.response.get('status_code'):
self._log(u'Warning, Library update responded 404 not found at %s' % cur_host, logger.DEBUG)
self._log_debug(u'Warning, Library update responded 404 not found at %s' % cur_host)
elif not response and not self.response or not self.response.get('ok'):
self._log(u'Warning, could not connect with server at %s' % cur_host)
self._log_warning(u'Warning, could not connect with server at %s' % cur_host)
else:
self._log(u'Warning, unknown response %sfrom %s, can most likely be ignored'
% (self.response and '%s ' % self.response.get('status_code') or '', cur_host), logger.DEBUG)
self._log_debug(u'Warning, unknown response %sfrom %s, can most likely be ignored'
% (self.response and '%s ' % self.response.get('status_code') or '', cur_host))
total_success = False
return total_success
# noinspection PyUnusedLocal
def _cb_response(self, r, *args, **kwargs):
self.response = dict(status_code=r.status_code, ok=r.ok)
return r
@staticmethod
def _discover_server():
def _discover_server(self):
cs = socket(AF_INET, SOCK_DGRAM)
mb_listen_port = 7359
@ -132,14 +93,14 @@ class EmbyNotifier:
'Not all data sent through the socket'
message, host = cs.recvfrom(1024)
if message:
logger.log('%s found at %s: udp query response (%s)' % (server, host[0], message))
self._log('%s found at %s: udp query response (%s)' % (server, host[0], message))
result = ('{"Address":' not in message and message.split('|')[1] or
json.loads(message).get('Address', ''))
if result:
break
except AssertionError:
sock_issue = True
except Exception:
except (StandardError, Exception):
pass
if not sock_issue:
cs.shutdown(SHUT_RDWR)
@ -149,7 +110,7 @@ class EmbyNotifier:
from sickbeard.helpers import starify
hosts, keys = hosts or sickbeard.EMBY_HOST, apikeys or sickbeard.EMBY_APIKEY
hosts, keys = self._choose(hosts, sickbeard.EMBY_HOST), self._choose(apikeys, sickbeard.EMBY_APIKEY)
hosts = [x.strip() for x in hosts.split(',') if x.strip()]
keys = [x.strip() for x in keys.split(',') if x.strip()]
@ -165,15 +126,50 @@ class EmbyNotifier:
if len(hosts) != len(apikeys):
message = ('Not enough Api keys for hosts', 'More Api keys than hosts')[len(apikeys) > len(hosts)]
self._log(u'%s, check your settings' % message)
self._log_warning(u'%s, check your settings' % message)
return False, False, message
return hosts, apikeys, 'OK'
@staticmethod
def _log(msg, log_level=logger.WARNING):
def _notify(self, title, body, hosts, apikeys, **kwargs):
""" Internal wrapper for the test_notify function
logger.log(u'Emby: %s' % msg, log_level)
Args:
title: The title of the message
body: Message body of the notice to send
Returns:
2-Tuple True if body successfully sent otherwise False, Failure message string or None
"""
hosts, keys, message = self._check_config(hosts, apikeys)
if not hosts:
return False, message
success = True
message = []
args = dict(post_json={'Name': 'SickGear', 'Description': body, 'ImageUrl': self._sg_logo_url})
for i, cur_host in enumerate(hosts):
self.response = None
response = sickbeard.helpers.getURL(
'http://%s/emby/Notifications/Admin' % cur_host,
headers={'Content-type': 'application/json', 'X-MediaBrowser-Token': keys[i]},
timeout=10, hooks=dict(response=self._cb_response), **args)
if not response or self.response:
if self.response and 401 == self.response.get('status_code'):
success = False
message += ['Fail: Cannot authenticate API key with %s' % cur_host]
self._log_warning(u'Failed to authenticate with %s' % cur_host)
continue
elif not response and not self.response or not self.response.get('ok'):
success = False
message += ['Fail: No supported Emby server found at %s' % cur_host]
self._log_warning(u'Warning, could not connect with server at ' + cur_host)
continue
message += ['OK: %s' % cur_host]
return self._choose(('Success, all hosts tested', '<br />\n'.join(message))[not success], success)
##############################################################################
# Public functions
@ -182,23 +178,5 @@ class EmbyNotifier:
def discover_server(self):
return self._discover_server()
def test_notify(self, host, apikey):
self.test_mode = True
result = self._notify_emby('Testing SickGear Emby notifier', host, apikey)
self.test_mode = False
return result
def update_library(self, show=None, force=False):
""" Wrapper for the update library functions
:param show: TVShow object
:param force: True force update process
Returns: None if no processing done, True if processing succeeded with no issues else False if any issues found
"""
if sickbeard.USE_EMBY and (sickbeard.EMBY_UPDATE_LIBRARY or force):
return self._update_library(show)
notifier = EmbyNotifier

View file

@ -0,0 +1,141 @@
# coding=utf-8
#
# Author: Nic Wolfe <nic@wolfeden.ca>
# URL: http://code.google.com/p/sickbeard/
#
# This file is part of SickGear.
#
# SickGear 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.
#
# SickGear 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 SickGear. If not, see <http://www.gnu.org/licenses/>.
import sickbeard
from sickbeard import logger
notify_strings = dict(
snatch='Started download',
download='Download finished',
subtitle_download='Subtitle download finished',
git_updated='SickGear updated',
git_updated_text='SickGear updated to commit#: ',
test_title='SickGear notification test',
test_body=u'Success testing %s settings from SickGear ʕ•ᴥ•ʔ',
)
class BaseNotifier(object):
def __init__(self):
self.sg_logo_file = 'apple-touch-icon-precomposed.png'
self._testing = False
@property
def _sg_logo_url(self):
return 'https://raw.githubusercontent.com/SickGear/SickGear/master/gui/slick/images/ico/' + self.sg_logo_file
def _log(self, msg, level=logger.MESSAGE):
logger.log(u'%s: %s' % (self.name, msg), level)
def _log_debug(self, msg):
self._log(msg, logger.DEBUG)
def _log_error(self, msg):
self._log(msg, logger.ERROR)
def _log_warning(self, msg):
self._log(msg, logger.WARNING)
@classmethod
def id(cls):
return cls.__name__.replace('Notifier', '').upper()
@property
def name(self):
return self.__class__.__name__.replace('Notifier', '')
@classmethod
def is_enabled_onsnatch(cls):
return cls.is_enabled('NOTIFY_ONSNATCH')
@classmethod
def is_enabled_ondownload(cls):
return cls.is_enabled('NOTIFY_ONDOWNLOAD')
@classmethod
def is_enabled_onsubtitledownload(cls):
return cls.is_enabled('NOTIFY_ONSUBTITLEDOWNLOAD')
@classmethod
def is_enabled_library(cls):
return cls.is_enabled('UPDATE_LIBRARY')
@classmethod
def is_enabled(cls, action=None):
return getattr(sickbeard, action and '%s_%s' % (cls.id(), action) or 'USE_%s' % cls.id(), False)
def notify_snatch(self, *args, **kwargs):
pass
def notify_download(self, *args, **kwargs):
pass
def notify_subtitle_download(self, *args, **kwargs):
pass
def notify_git_update(self, *args, **kwargs):
pass
def update_library(self, **kwargs):
"""
note: nmj_notifier fires its library update when the notify_download is issued (inside notifiers)
"""
pass
def _notify(self, *args, **kwargs):
pass
def _choose(self, current=True, saved=True):
if self._testing:
return current
return saved
@staticmethod
def _body_only(title, body):
# don't use title with updates or testing, as only one str is used
return body if 'SickGear' in title else '%s: %s' % (title, body.replace('#: ', '# '))
class Notifier(BaseNotifier):
def test_notify(self, *args, **kwargs):
self._testing = True
r = self._pre_notify('test_title', notify_strings['test_body'] % (self.name + ' notifier'), *args, **kwargs)
return (r, (('Success, notification sent.', 'Failed to send notification.')[not r]))[r in (True, False)]
def notify_snatch(self, ep_name, **kwargs):
self._pre_notify('snatch', ep_name, **kwargs)
def notify_download(self, ep_name, **kwargs):
self._pre_notify('download', ep_name, **kwargs)
def notify_subtitle_download(self, ep_name, lang, **kwargs):
self._pre_notify('subtitle_download', '%s : %s' % (ep_name, lang), **kwargs)
def notify_git_update(self, new_version='??', **kwargs):
self._pre_notify('git_updated', notify_strings['git_updated_text'] + new_version, **kwargs)
def _pre_notify(self, notify_string, message, *args, **kwargs):
self._log_debug(u'Sending notification "%s"' % (self._body_only(notify_strings[notify_string], message)))
try:
return self._notify(notify_strings[notify_string], message, *args, **kwargs)
except (StandardError, Exception):
return False

View file

@ -0,0 +1,80 @@
# coding=utf-8
#
# This file is part of SickGear.
#
# Thanks to: mallen86, generica
#
# SickGear 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.
#
# SickGear 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 SickGear. If not, see <http://www.gnu.org/licenses/>.
import sickbeard
from sickbeard.notifiers.generic import Notifier
class GitterNotifier(Notifier):
def __init__(self):
super(GitterNotifier, self).__init__()
def _notify(self, title, body, room_name='', access_token='', **kwargs):
api_url = 'https://api.gitter.im/v1/'
params = [('headers', dict(
Authorization='Bearer %s' % self._choose(access_token, sickbeard.GITTER_ACCESS_TOKEN))), ('json', True)]
is_locked = False
# get user of token
# noinspection PyTypeChecker
resp = sickbeard.helpers.getURL(**dict([('url', '%suser' % api_url)] + params))
user_id = resp and 1 == len(resp) and resp[0].get('id') or None
if None is user_id:
result = self._failed('bad oath access token?')
else:
# get a room
# noinspection PyTypeChecker
resp = sickbeard.helpers.getURL(**dict(
[('url', '%srooms' % api_url),
('post_json', dict(uri=self._choose(room_name, sickbeard.GITTER_ROOM)))] + params))
room_id = resp and resp.get('id') or None
if None is room_id:
result = self._failed('room locked or not found')
else:
is_locked = 'private' == resp.get('security', '').lower()
# join room
# noinspection PyTypeChecker
if not sickbeard.helpers.getURL(**dict(
[('url', '%suser/%s/rooms' % (api_url, user_id)),
('post_json', dict(id=room_id))] + params)):
result = self._failed('failed to join room')
else:
# send text
# noinspection PyTypeChecker
resp = sickbeard.helpers.getURL(**dict(
[('url', '%srooms/%s/chatMessages' % (api_url, room_id)),
('post_json', dict(text=self._body_only(title, body)))] + params))
if None is (resp and resp.get('id') or None):
result = self._failed('failed to send text', append=False)
else:
result = True
return self._choose(('Error sending notification, %s' % result,
'Successful test notice sent%s. (Note: %s clients display icon once in a sequence).' %
(('', ' to locked room')[is_locked], self.name))[True is result], result)
def _failed(self, result, append=True):
self._log_error('%s failed to send message%s' % (self.name, append and ', %s' % result or ''))
return self._choose(result, None)
notifier = GitterNotifier

View file

@ -18,45 +18,28 @@
from __future__ import print_function
import socket
import urllib
import sickbeard
from sickbeard import logger, common
from sickbeard.exceptions import ex
from sickbeard.notifiers.generic import Notifier, notify_strings
from lib.growl import gntp
class GrowlNotifier:
def test_notify(self, host, password):
self._sendRegistration(host, password, 'Test')
return self._sendGrowl('Test Growl', 'Testing Growl settings from SickGear', 'Test', host, password,
force=True)
class GrowlNotifier(Notifier):
def notify_snatch(self, ep_name):
if sickbeard.GROWL_NOTIFY_ONSNATCH:
self._sendGrowl(common.notifyStrings[common.NOTIFY_SNATCH], ep_name)
def __init__(self):
super(GrowlNotifier, self).__init__()
def notify_download(self, ep_name):
if sickbeard.GROWL_NOTIFY_ONDOWNLOAD:
self._sendGrowl(common.notifyStrings[common.NOTIFY_DOWNLOAD], ep_name)
self.sg_logo_file = 'apple-touch-icon-72x72.png'
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.GROWL_NOTIFY_ONSUBTITLEDOWNLOAD:
self._sendGrowl(common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD], ep_name + ': ' + lang)
def notify_git_update(self, new_version = '??'):
if sickbeard.USE_GROWL:
update_text=common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title=common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._sendGrowl(title, update_text + new_version)
def _send_growl_msg(self, options, message=None):
def _send_growl(self, options, message=None):
#Send Notification
# Send Notification
notice = gntp.GNTPNotice()
#Required
# Required
notice.add_header('Application-Name', options['app'])
notice.add_header('Notification-Name', options['name'])
notice.add_header('Notification-Title', options['title'])
@ -64,7 +47,7 @@ class GrowlNotifier:
if options['password']:
notice.set_password(options['password'])
#Optional
# Optional
if options['sticky']:
notice.add_header('Notification-Sticky', options['sticky'])
if options['priority']:
@ -77,11 +60,15 @@ class GrowlNotifier:
notice.add_header('Notification-Text', message)
response = self._send(options['host'], options['port'], notice.encode(), options['debug'])
if isinstance(response, gntp.GNTPOK): return True
if isinstance(response, gntp.GNTPOK):
return True
return False
def _send(self, host, port, data, debug=False):
if debug: print('<Sending>\n', data, '\n</Sending>')
@staticmethod
def _send(host, port, data, debug=False):
if debug:
print('<Sending>\n', data, '\n</Sending>')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
@ -89,107 +76,71 @@ class GrowlNotifier:
response = gntp.parse_gntp(s.recv(1024))
s.close()
if debug: print('<Recieved>\n', response, '\n</Recieved>')
if debug:
print('<Recieved>\n', response, '\n</Recieved>')
return response
def _sendGrowl(self, title='SickGear Notification', message=None, name=None, host=None, password=None,
force=False):
if not sickbeard.USE_GROWL and not force:
return False
def _send_registration(self, host=None, password=None):
if name == None:
name = title
host_parts = self._choose(host, sickbeard.GROWL_HOST).split(':')
port = 23053 if (2 != len(host_parts) or '' == host_parts[1]) else int(host_parts[1])
password = self._choose(password, sickbeard.GROWL_PASSWORD)
if host == None:
hostParts = sickbeard.GROWL_HOST.split(':')
else:
hostParts = host.split(':')
opts = dict(app='SickGear', host=host_parts[0], port=port, password=password, debug=False)
if len(hostParts) != 2 or hostParts[1] == '':
port = 23053
else:
port = int(hostParts[1])
growlHosts = [(hostParts[0], port)]
opts = {}
opts['name'] = name
opts['title'] = title
opts['app'] = 'SickGear'
opts['sticky'] = None
opts['priority'] = None
opts['debug'] = False
if password == None:
opts['password'] = sickbeard.GROWL_PASSWORD
else:
opts['password'] = password
opts['icon'] = True
for pc in growlHosts:
opts['host'] = pc[0]
opts['port'] = pc[1]
logger.log(u'GROWL: Sending message "' + message + '" to ' + opts['host'] + ':' + str(opts['port']), logger.DEBUG)
try:
if self._send_growl(opts, message):
return True
else:
if self._sendRegistration(host, password, 'Sickbeard'):
return self._send_growl(opts, message)
else:
return False
except Exception as e:
logger.log(u'GROWL: Unable to send growl to ' + opts['host'] + ':' + str(opts['port']) + ' - ' + ex(e), logger.WARNING)
return False
def _sendRegistration(self, host=None, password=None, name='SickGear Notification'):
opts = {}
if host == None:
hostParts = sickbeard.GROWL_HOST.split(':')
else:
hostParts = host.split(':')
if len(hostParts) != 2 or hostParts[1] == '':
port = 23053
else:
port = int(hostParts[1])
opts['host'] = hostParts[0]
opts['port'] = port
if password == None:
opts['password'] = sickbeard.GROWL_PASSWORD
else:
opts['password'] = password
opts['app'] = 'SickGear'
opts['debug'] = False
#Send Registration
# Send Registration
register = gntp.GNTPRegister()
register.add_header('Application-Name', opts['app'])
register.add_header('Application-Icon',
'https://raw.githubusercontent.com/SickGear/SickGear/master/gui/slick/images/ico/apple-touch-icon-72x72.png')
register.add_header('Application-Icon', self._sg_logo_url)
register.add_notification('Test', True)
register.add_notification(common.notifyStrings[common.NOTIFY_SNATCH], True)
register.add_notification(common.notifyStrings[common.NOTIFY_DOWNLOAD], True)
register.add_notification(common.notifyStrings[common.NOTIFY_GIT_UPDATE], True)
register.add_notification(notify_strings['snatch'], True)
register.add_notification(notify_strings['download'], True)
register.add_notification(notify_strings['git_updated'], True)
if opts['password']:
register.set_password(opts['password'])
try:
return self._send(opts['host'], opts['port'], register.encode(), opts['debug'])
except Exception as e:
logger.log(u'GROWL: Unable to send growl to ' + opts['host'] + ':' + str(opts['port']) + ' - ' + ex(e), logger.WARNING)
self._log_warning(u'Unable to send growl to %s:%s - %s' % (opts['host'], opts['port'], ex(e)))
return False
def _notify(self, title, body, name=None, host=None, password=None, **kwargs):
name = name or title or 'SickGear Notification'
host_parts = self._choose(host, sickbeard.GROWL_HOST).split(':')
port = (int(host_parts[1]), 23053)[len(host_parts) != 2 or '' == host_parts[1]]
growl_hosts = [(host_parts[0], port)]
password = self._choose(password, sickbeard.GROWL_PASSWORD)
opts = dict(title=title, name=name, app='SickGear', sticky=None, priority=None,
password=password, icon=True, debug=False)
for pc in growl_hosts:
opts['host'] = pc[0]
opts['port'] = pc[1]
try:
if self._send_growl_msg(opts, body):
return True
if self._send_registration(host, password):
return self._send_growl_msg(opts, body)
except Exception as e:
self._log_warning(u'Unable to send growl to %s:%s - %s' % (opts['host'], opts['port'], ex(e)))
return False
def test_notify(self, host, password):
self._testing = True
self._send_registration(host, password)
return ('Success, registered and tested', 'Failed registration and testing')[
True is not super(GrowlNotifier, self).test_notify(name='Test', host=host, password=password)] + \
(urllib.unquote_plus(host) + ' with password: ' + password, '')[password in (None, '')]
notifier = GrowlNotifier

View file

@ -17,56 +17,29 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import time
import urllib
import sickbeard
import sickbeard.helpers
from sickbeard.exceptions import ex
from sickbeard import logger, common
try:
# noinspection PyPep8Naming
import xml.etree.cElementTree as etree
except ImportError:
# noinspection PyPep8Naming
import xml.etree.ElementTree as etree
try:
import json
except ImportError:
from lib import simplejson as json
import time
import urllib
import xml.etree.cElementTree as XmlEtree
import sickbeard
import sickbeard.helpers
from sickbeard import logger
from sickbeard.exceptions import ex
from sickbeard.notifiers.generic import Notifier
class KodiNotifier:
class KodiNotifier(Notifier):
def __init__(self):
self.sg_logo_url = 'https://raw.githubusercontent.com/SickGear/SickGear/master/gui/slick/images/ico/' + \
'apple-touch-icon-precomposed.png'
super(KodiNotifier, self).__init__()
self.username, self.password = (None, None)
self.response = None
self.prefix = ''
self.test_mode = False
@staticmethod
def _log(msg, log_level=logger.WARNING):
logger.log(u'Kodi: %s' % msg, log_level)
def _maybe_log(self, msg, log_level=logger.WARNING):
if msg and (sickbeard.KODI_ALWAYS_ON or self.test_mode):
self._log(msg + (not sickbeard.KODI_ALWAYS_ON and self.test_mode and
' (Test mode ignores "Always On")' or ''), log_level)
def _maybe_log_failed_detection(self, host, msg='connect to'):
self._maybe_log(u'Failed to %s %s, check device(s) and config.' % (msg, host), logger.ERROR)
# noinspection PyUnusedLocal
def cb_response(self, r, *args, **kwargs):
self.response = dict(status_code=r.status_code)
return r
def _get_kodi_version(self, host):
""" Return Kodi JSON-RPC API version (odd # = dev, even # = stable)
@ -88,7 +61,7 @@ class KodiNotifier:
"""
timeout = 10
response = self._send_to_kodi_json(host, dict(method='JSONRPC.Version'), timeout)
response = self._send_json(host, dict(method='JSONRPC.Version'), timeout)
if self.response and 401 == self.response.get('status_code'):
return False
@ -98,77 +71,19 @@ class KodiNotifier:
# fallback to legacy HTTPAPI method
test_command = {'command': 'Help'}
if self._send_to_kodi(host, test_command, timeout):
if self._send(host, test_command, timeout):
# return fake version number to use the legacy method
return 1
if self.response and 404 == self.response.get('status_code'):
self.prefix = 'xbmc'
if self._send_to_kodi(host, test_command, timeout):
if self._send(host, test_command, timeout):
# return fake version number to use the legacy method
return 1
return False
def _notify_kodi(self, msg, title='SickGear', kodi_hosts=None):
""" Internal wrapper for the notify_snatch and notify_download functions
Call either the JSON-RPC over HTTP or the legacy HTTP API methods depending on the Kodi API version.
Args:
msg: Message body of the notice to send
title: Title of the notice to send
Return:
A list of results in the format of host:ip:result, where result will either be 'OK' or False.
"""
# fill in omitted parameters
if not kodi_hosts:
kodi_hosts = sickbeard.KODI_HOST
if not sickbeard.USE_KODI and not self.test_mode:
self._log(u'Notification not enabled, skipping this notification', logger.DEBUG)
return False, None
total_success = True
message = []
for host in [x.strip() for x in kodi_hosts.split(',')]:
cur_host = urllib.unquote_plus(host)
self._log(u'Sending notification to "%s" - %s' % (cur_host, message), logger.DEBUG)
api_version = self._get_kodi_version(cur_host)
if self.response and 401 == self.response.get('status_code'):
total_success = False
message += ['Fail: Cannot authenticate with %s' % cur_host]
self._log(u'Failed to authenticate with %s' % cur_host, logger.DEBUG)
elif not api_version:
total_success = False
message += ['Fail: No supported Kodi found at %s' % cur_host]
self._maybe_log_failed_detection(cur_host, 'connect and detect version for')
else:
if 4 >= api_version:
self._log(u'Detected %sversion <= 11, using HTTP API'
% self.prefix and ' ' + self.prefix.capitalize(), logger.DEBUG)
__method_send = self._send_to_kodi
command = dict(command='ExecBuiltIn',
parameter='Notification(%s,%s)' % (title, msg))
else:
self._log(u'Detected version >= 12, using JSON API', logger.DEBUG)
__method_send = self._send_to_kodi_json
command = dict(method='GUI.ShowNotification',
params={'title': '%s' % title,
'message': '%s' % msg,
'image': '%s' % self.sg_logo_url})
response_notify = __method_send(cur_host, command, 10)
if response_notify:
message += ['%s: %s' % ((response_notify, 'OK')['OK' in response_notify], cur_host)]
return total_success, '<br />\n'.join(message)
def _update_library(self, show_name=None):
def update_library(self, show_name=None, **kwargs):
""" Wrapper for the update library functions
Call either the JSON-RPC over HTTP or the legacy HTTP API methods depending on the Kodi API version.
@ -184,7 +99,7 @@ class KodiNotifier:
Returns: True if processing succeeded with no issues else False if any issues found
"""
if not sickbeard.KODI_HOST:
self._log(u'No Kodi hosts specified, check your settings')
self._log_warning(u'No Kodi hosts specified, check your settings')
return False
# either update each host, or only attempt to update until one successful result
@ -196,15 +111,15 @@ class KodiNotifier:
for cur_host in [x.strip() for x in sickbeard.KODI_HOST.split(',')]:
response = self._send_to_kodi_json(cur_host, dict(method='Profiles.GetCurrentProfile'))
response = self._send_json(cur_host, dict(method='Profiles.GetCurrentProfile'))
if self.response and 401 == self.response.get('status_code'):
self._log(u'Failed to authenticate with %s' % cur_host, logger.DEBUG)
self._log_debug(u'Failed to authenticate with %s' % cur_host)
continue
if not response:
self._maybe_log_failed_detection(cur_host)
continue
if self._send_update_library(cur_host, show_name):
if self._send_library_update(cur_host, show_name):
only_first.update(dict(profile=response.get('label') or 'Master', host=cur_host))
self._log('Success: profile;' +
u'"%(profile)s" at%(first)s host;%(host)s updated%(show)s%(first_note)s' % only_first)
@ -218,7 +133,7 @@ class KodiNotifier:
# needed for the 'update kodi' submenu command as it only cares of the final result vs the individual ones
return 0 == result
def _send_update_library(self, host, show_name=None):
def _send_library_update(self, host, show_name=None):
""" Internal wrapper for the update library function
Call either the JSON-RPC over HTTP or the legacy HTTP API methods depending on the Kodi API version.
@ -229,9 +144,6 @@ class KodiNotifier:
Return:
True if the update was successful else False
"""
self._log(u'Sending request to update library for host: "%s"' % host, logger.DEBUG)
api_version = self._get_kodi_version(host)
if api_version:
# try to update just the show, if it fails, do full update if enabled
@ -241,18 +153,17 @@ class KodiNotifier:
failed_msg = 'Single show update failed,'
if sickbeard.KODI_UPDATE_FULL:
self._log(u'%s falling back to full update' % failed_msg, logger.DEBUG)
self._log_debug(u'%s falling back to full update' % failed_msg)
return __method_update(host)
self._log(u'%s consider enabling "Perform full library update" in config/notifications' % failed_msg,
logger.DEBUG)
self._log_debug(u'%s consider enabling "Perform full library update" in config/notifications' % failed_msg)
return False
##############################################################################
# Legacy HTTP API (pre Kodi 12) methods
##############################################################################
def _send_to_kodi(self, host, command, timeout=30):
def _send(self, host, command, timeout=30):
""" Handle communication to Kodi servers via HTTP API
Args:
@ -263,11 +174,11 @@ class KodiNotifier:
"""
if not host:
self._log(u'No host specified, aborting update', logger.WARNING)
self._log_warning(u'No host specified, aborting update')
return False
args = {}
if not sickbeard.KODI_ALWAYS_ON and not self.test_mode:
if not sickbeard.KODI_ALWAYS_ON and not self._testing:
args['mute_connect_err'] = True
if self.password or sickbeard.KODI_PASSWORD:
@ -292,51 +203,54 @@ class KodiNotifier:
"""
if not host:
self._log(u'No host specified, aborting update', logger.WARNING)
self._log_warning(u'No host specified, aborting update')
return False
self._log(u'Updating library via HTTP method for host: %s' % host, logger.DEBUG)
self._log_debug(u'Updating library via HTTP method for host: %s' % host)
# if we're doing per-show
if show_name:
self._log(u'Updating library via HTTP method for show %s' % show_name, logger.DEBUG)
self._log_debug(u'Updating library via HTTP method for show %s' % show_name)
path_sql = 'SELECT path.strPath FROM path, tvshow, tvshowlinkpath WHERE ' \
'tvshow.c00 = "%s"' % show_name \
+ ' AND tvshowlinkpath.idShow = tvshow.idShow AND tvshowlinkpath.idPath = path.idPath'
# noinspection SqlResolve
path_sql = 'SELECT path.strPath' \
' FROM path, tvshow, tvshowlinkpath' \
' WHERE tvshow.c00 = "%s"' % show_name \
+ ' AND tvshowlinkpath.idShow = tvshow.idShow' \
' AND tvshowlinkpath.idPath = path.idPath'
# set xml response format, if this fails then don't bother with the rest
if not self._send_to_kodi(
host, {'command': 'SetResponseFormat(webheader;false;webfooter;false;header;<xml>;footer;</xml>;' +
if not self._send(
host, {'command': 'SetResponseFormat(webheader;false;webfooter;false;header;<xml>;footer;</xml>;'
'opentag;<tag>;closetag;</tag>;closefinaltag;false)'}):
return False
# sql used to grab path(s)
response = self._send_to_kodi(host, {'command': 'QueryVideoDatabase(%s)' % path_sql})
response = self._send(host, {'command': 'QueryVideoDatabase(%s)' % path_sql})
if not response:
self._log(u'Invalid response for %s on %s' % (show_name, host), logger.DEBUG)
self._log_debug(u'Invalid response for %s on %s' % (show_name, host))
return False
try:
et = etree.fromstring(urllib.quote(response, ':\\/<>'))
et = XmlEtree.fromstring(urllib.quote(response, ':\\/<>'))
except SyntaxError as e:
self._log(u'Unable to parse XML in response: %s' % ex(e), logger.ERROR)
self._log_error(u'Unable to parse XML in response: %s' % ex(e))
return False
paths = et.findall('.//field')
if not paths:
self._log(u'No valid path found for %s on %s' % (show_name, host), logger.DEBUG)
self._log_debug(u'No valid path found for %s on %s' % (show_name, host))
return False
for path in paths:
# we do not need it double-encoded, gawd this is dumb
un_enc_path = urllib.unquote(path.text).decode(sickbeard.SYS_ENCODING)
self._log(u'Updating %s on %s at %s' % (show_name, host, un_enc_path), logger.DEBUG)
self._log_debug(u'Updating %s on %s at %s' % (show_name, host, un_enc_path))
if not self._send_to_kodi(
host, {'command': 'ExecBuiltIn', 'parameter': 'Kodi.updatelibrary(video, %s)' % un_enc_path}):
self._log(u'Update of show directory failed for %s on %s at %s'
% (show_name, host, un_enc_path), logger.ERROR)
if not self._send(
host, dict(command='ExecBuiltIn', parameter='Kodi.updatelibrary(video, %s)' % un_enc_path)):
self._log_error(u'Update of show directory failed for %s on %s at %s'
% (show_name, host, un_enc_path))
return False
# sleep for a few seconds just to be sure kodi has a chance to finish each directory
@ -344,10 +258,10 @@ class KodiNotifier:
time.sleep(5)
# do a full update if requested
else:
self._log(u'Full library update on host: %s' % host, logger.DEBUG)
self._log_debug(u'Full library update on host: %s' % host)
if not self._send_to_kodi(host, {'command': 'ExecBuiltIn', 'parameter': 'Kodi.updatelibrary(video)'}):
self._log(u'Failed full library update on: %s' % host, logger.ERROR)
if not self._send(host, dict(command='ExecBuiltIn', parameter='Kodi.updatelibrary(video)')):
self._log_error(u'Failed full library update on: %s' % host)
return False
return True
@ -356,7 +270,7 @@ class KodiNotifier:
# JSON-RPC API (Kodi 12+) methods
##############################################################################
def _send_to_kodi_json(self, host, command, timeout=30):
def _send_json(self, host, command, timeout=30):
""" Handle communication to Kodi installations via JSONRPC
Args:
@ -368,7 +282,7 @@ class KodiNotifier:
result = {}
if not host:
self._log(u'No host specified, aborting update', logger.WARNING)
self._log_warning(u'No host specified, aborting update')
return result
if isinstance(command, dict):
@ -378,7 +292,7 @@ class KodiNotifier:
else:
args = dict(data=command)
if not sickbeard.KODI_ALWAYS_ON and not self.test_mode:
if not sickbeard.KODI_ALWAYS_ON and not self._testing:
args['mute_connect_err'] = True
if self.password or sickbeard.KODI_PASSWORD:
@ -391,8 +305,8 @@ class KodiNotifier:
if not response.get('error'):
return 'OK' == response.get('result') and {'OK': True} or response.get('result')
self._log(u'API error; %s from %s in response to command: %s'
% (json.dumps(response['error']), host, json.dumps(command)), logger.ERROR)
self._log_error(u'API error; %s from %s in response to command: %s'
% (json.dumps(response['error']), host, json.dumps(command)))
return result
def _update_json(self, host=None, show_name=None):
@ -408,12 +322,12 @@ class KodiNotifier:
"""
if not host:
self._log(u'No host specified, aborting update', logger.WARNING)
self._log_warning(u'No host specified, aborting update')
return False
# if we're doing per-show
if show_name:
self._log(u'JSON library update. Host: %s Show: %s' % (host, show_name), logger.DEBUG)
self._log_debug(u'JSON library update. Host: %s Show: %s' % (host, show_name))
# try fetching tvshowid using show_name with a fallback to getting show list
show_name = urllib.unquote_plus(show_name)
@ -424,17 +338,18 @@ class KodiNotifier:
shows = None
for command in commands:
response = self._send_to_kodi_json(host, command)
response = self._send_json(host, command)
shows = response.get('tvshows')
if shows:
break
if not shows:
self._log(u'No items in GetTVShows response', logger.DEBUG)
self._log_debug(u'No items in GetTVShows response')
return False
tvshowid = -1
path = ''
# noinspection PyTypeChecker
for show in shows:
if show_name == show.get('title') or show_name == show.get('label'):
tvshowid = show.get('tvshowid', -1)
@ -444,82 +359,104 @@ class KodiNotifier:
# we didn't find the show (exact match), thus revert to just doing a full update if enabled
if -1 == tvshowid:
self._log(u'Doesn\'t have "%s" in it\'s known shows, full library update required' % show_name,
logger.DEBUG)
self._log_debug(u'Doesn\'t have "%s" in it\'s known shows, full library update required' % show_name)
return False
# lookup tv-show path if we don't already know it
if not len(path):
command = dict(method='VideoLibrary.GetTVShowDetails',
params={'tvshowid': tvshowid, 'properties': ['file']})
response = self._send_to_kodi_json(host, command)
response = self._send_json(host, command)
path = 'tvshowdetails' in response and response['tvshowdetails'].get('file', '') or ''
if not len(path):
self._log(u'No valid path found for %s with ID: %s on %s' % (show_name, tvshowid, host), logger.WARNING)
self._log_warning(u'No valid path found for %s with ID: %s on %s' % (show_name, tvshowid, host))
return False
self._log(u'Updating %s on %s at %s' % (show_name, host, path), logger.DEBUG)
self._log_debug(u'Updating %s on %s at %s' % (show_name, host, path))
command = dict(method='VideoLibrary.Scan', params={'directory': '%s' % json.dumps(path)[1:-1]})
response_scan = self._send_to_kodi_json(host, command)
response_scan = self._send_json(host, command)
if not response_scan.get('OK'):
self._log(u'Update of show directory failed for %s on %s at %s response: %s' %
(show_name, host, path, response_scan), logger.ERROR)
self._log_error(u'Update of show directory failed for %s on %s at %s response: %s' %
(show_name, host, path, response_scan))
return False
# do a full update if requested
else:
self._log(u'Full library update on host: %s' % host, logger.DEBUG)
response_scan = self._send_to_kodi_json(host, dict(method='VideoLibrary.Scan'))
self._log_debug(u'Full library update on host: %s' % host)
response_scan = self._send_json(host, dict(method='VideoLibrary.Scan'))
if not response_scan.get('OK'):
self._log(u'Failed full library update on: %s response: %s' % (host, response_scan), logger.ERROR)
self._log_error(u'Failed full library update on: %s response: %s' % (host, response_scan))
return False
return True
##############################################################################
# Public functions which will call the JSON or Legacy HTTP API methods
##############################################################################
# noinspection PyUnusedLocal
def cb_response(self, r, *args, **kwargs):
self.response = dict(status_code=r.status_code)
return r
def notify_snatch(self, ep_name):
def _maybe_log(self, msg, log_level=logger.WARNING):
if sickbeard.KODI_NOTIFY_ONSNATCH:
self._notify_kodi(ep_name, common.notifyStrings[common.NOTIFY_SNATCH])
if msg and (sickbeard.KODI_ALWAYS_ON or self._testing):
self._log(msg + (not sickbeard.KODI_ALWAYS_ON and self._testing and
' (Test mode ignores "Always On")' or ''), log_level)
def notify_download(self, ep_name):
def _maybe_log_failed_detection(self, host, msg='connect to'):
if sickbeard.KODI_NOTIFY_ONDOWNLOAD:
self._notify_kodi(ep_name, common.notifyStrings[common.NOTIFY_DOWNLOAD])
self._maybe_log(u'Failed to %s %s, check device(s) and config' % (msg, host), logger.ERROR)
def notify_subtitle_download(self, ep_name, lang):
def _notify(self, title, body, hosts, username, password, **kwargs):
""" Internal wrapper for the notify_snatch and notify_download functions
if sickbeard.KODI_NOTIFY_ONSUBTITLEDOWNLOAD:
self._notify_kodi('%s: %s' % (ep_name, lang), common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD])
Call either the JSON-RPC over HTTP or the legacy HTTP API methods depending on the Kodi API version.
def notify_git_update(self, new_version='??'):
Args:
title: Title of the notice to send
body: Message body of the notice to send
if sickbeard.USE_KODI:
update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title = common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._notify_kodi('%s %s' % (update_text, new_version), title)
def test_notify(self, host, username, password):
self.test_mode, self.username, self.password = True, username, password
result = self._notify_kodi('Testing SickGear Kodi notifier', 'Test Notification', kodi_hosts=host)
self.test_mode = False
return result
def update_library(self, showName=None, force=False):
""" Wrapper for the update library functions
:param showName: Name of a TV show
:param force: True force update process
Returns: None if no processing done, True if processing succeeded with no issues else False if any issues found
Return:
A list of results in the format of host:ip:result, where result will either be 'OK' or False.
"""
if sickbeard.USE_KODI and (sickbeard.KODI_UPDATE_LIBRARY or force):
return self._update_library(showName)
self.username, self.password = username, password
title = title or 'SickGear'
hosts = self._choose(hosts, sickbeard.KODI_HOST)
success = True
message = []
for host in [x.strip() for x in hosts.split(',')]:
cur_host = urllib.unquote_plus(host)
api_version = self._get_kodi_version(cur_host)
if self.response and 401 == self.response.get('status_code'):
success = False
message += ['Fail: Cannot authenticate with %s' % cur_host]
self._log_debug(u'Failed to authenticate with %s' % cur_host)
elif not api_version:
success = False
message += ['Fail: No supported Kodi found at %s' % cur_host]
self._maybe_log_failed_detection(cur_host, 'connect and detect version for')
else:
if 4 >= api_version:
self._log_debug(u'Detected %sversion <= 11, using HTTP API'
% self.prefix and ' ' + self.prefix.capitalize())
__method_send = self._send
command = dict(command='ExecBuiltIn',
parameter='Notification(%s,%s)' % (title, body))
else:
self._log_debug(u'Detected version >= 12, using JSON API')
__method_send = self._send_json
command = dict(method='GUI.ShowNotification', params=dict(
[('title', title), ('message', body), ('image', self._sg_logo_url)]
+ ([], [('displaytime', 8000)])[self._testing]))
response_notify = __method_send(cur_host, command, 10)
if response_notify:
message += ['%s: %s' % ((response_notify, 'OK')['OK' in response_notify], cur_host)]
return self._choose(('Success, all hosts tested', '<br />\n'.join(message))[not success], success)
notifier = KodiNotifier

View file

@ -20,23 +20,24 @@ import os
import cgi
import sickbeard
from sickbeard import logger, common
from sickbeard.notifiers.generic import Notifier
def diagnose():
'''
"""
Check the environment for reasons libnotify isn't working. Return a
user-readable message indicating possible issues.
'''
"""
try:
import pynotify #@UnusedImport
# noinspection PyPackageRequirements
import pynotify
except ImportError:
return (u"<p>Error: pynotify isn't installed. On Ubuntu/Debian, install the "
u"<a href=\"apt:python-notify\">python-notify</a> package.")
return ('Error: pynotify isn\'t installed. On Ubuntu/Debian, install the '
'<a href=\'apt:python-notify\'>python-notify</a> package.')
if 'DISPLAY' not in os.environ and 'DBUS_SESSION_BUS_ADDRESS' not in os.environ:
return (u"<p>Error: Environment variables DISPLAY and DBUS_SESSION_BUS_ADDRESS "
u"aren't set. libnotify will only work when you run SickGear "
u"from a desktop login.")
return ('Error: Environment variables DISPLAY and DBUS_SESSION_BUS_ADDRESS '
'aren\'t set. libnotify will only work when you run SickGear '
'from a desktop login.')
try:
import dbus
except ImportError:
@ -45,82 +46,71 @@ def diagnose():
try:
bus = dbus.SessionBus()
except dbus.DBusException as e:
return (u"<p>Error: unable to connect to D-Bus session bus: <code>%s</code>."
u"<p>Are you running SickGear in a desktop session?") % (cgi.escape(e),)
return (u'Error: unable to connect to D-Bus session bus: <code>%s</code>. '
u'Are you running SickGear in a desktop session?') % (cgi.escape(e),)
try:
bus.get_object('org.freedesktop.Notifications',
'/org/freedesktop/Notifications')
except dbus.DBusException as e:
return (u"<p>Error: there doesn't seem to be a notification daemon available: <code>%s</code> "
u"<p>Try installing notification-daemon or notify-osd.") % (cgi.escape(e),)
return u"<p>Error: Unable to send notification."
return (u'Error: there doesn\'t seem to be a notification daemon available: <code>%s</code> '
u'Try installing notification-daemon or notify-osd.') % (cgi.escape(e),)
return 'Error: Unable to send notification.'
class LibnotifyNotifier:
class LibnotifyNotifier(Notifier):
def __init__(self):
super(LibnotifyNotifier, self).__init__()
self.pynotify = None
self.gobject = None
def init_pynotify(self):
if self.pynotify is not None:
return True
try:
# noinspection PyPackageRequirements
import pynotify
except ImportError:
logger.log(u"Unable to import pynotify. libnotify notifications won't work.", logger.ERROR)
self._log_error(u'Unable to import pynotify. libnotify notifications won\'t work')
return False
try:
# noinspection PyPackageRequirements
from gi.repository import GObject
except ImportError:
logger.log(u"Unable to import GObject from gi.repository. We can't catch a GError in display.", logger.ERROR)
self._log_error(u'Unable to import GObject from gi.repository. Cannot catch a GError in display')
return False
if not pynotify.init('SickGear'):
logger.log(u"Initialization of pynotify failed. libnotify notifications won't work.", logger.ERROR)
self._log_error(u'Initialization of pynotify failed. libnotify notifications won\'t work')
return False
self.pynotify = pynotify
self.gobject = GObject
return True
def notify_snatch(self, ep_name):
if sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH:
self._notify(common.notifyStrings[common.NOTIFY_SNATCH], ep_name)
def _notify(self, title, body, **kwargs):
def notify_download(self, ep_name):
if sickbeard.LIBNOTIFY_NOTIFY_ONDOWNLOAD:
self._notify(common.notifyStrings[common.NOTIFY_DOWNLOAD], ep_name)
result = False
if self.init_pynotify():
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.LIBNOTIFY_NOTIFY_ONSUBTITLEDOWNLOAD:
self._notify(common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD], ep_name + ": " + lang)
# Can't make this a global constant because PROG_DIR isn't available
# when the module is imported.
icon_path = os.path.join(sickbeard.PROG_DIR, 'data/images/sickbeard_touch_icon.png')
icon_uri = 'file://' + os.path.abspath(icon_path)
def notify_git_update(self, new_version = "??"):
if sickbeard.USE_LIBNOTIFY:
update_text=common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title=common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._notify(title, update_text + new_version)
# If the session bus can't be acquired here a bunch of warning messages
# will be printed but the call to show() will still return True.
# pynotify doesn't seem too keen on error handling.
n = self.pynotify.Notification(title, body, icon_uri)
try:
result = n.show()
except self.gobject.GError:
pass
def test_notify(self):
return self._notify('Test notification', "This is a test notification from SickGear", force=True)
def _notify(self, title, message, force=False):
if not sickbeard.USE_LIBNOTIFY and not force:
return False
if not self.init_pynotify():
return False
# Can't make this a global constant because PROG_DIR isn't available
# when the module is imported.
icon_path = os.path.join(sickbeard.PROG_DIR, "data/images/sickbeard_touch_icon.png")
icon_uri = 'file://' + os.path.abspath(icon_path)
# If the session bus can't be acquired here a bunch of warning messages
# will be printed but the call to show() will still return True.
# pynotify doesn't seem too keen on error handling.
n = self.pynotify.Notification(title, message, icon_uri)
try:
return n.show()
except self.gobject.GError:
return False
return self._choose((True if result else diagnose()), result)
notifier = LibnotifyNotifier

View file

@ -1,47 +1,15 @@
import sickbeard
from sickbeard.notifiers.generic import Notifier
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 SickGear',
force=True)
class NMANotifier(Notifier):
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(self, title, body, nma_api=None, nma_priority=None, **kwargs):
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 notify_subtitle_download(self, ep_name, lang):
if sickbeard.NMA_NOTIFY_ONSUBTITLEDOWNLOAD:
self._sendNMA(nma_api=None, nma_priority=None, event=common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD],
message=ep_name + ': ' + lang)
def notify_git_update(self, new_version = '??'):
if sickbeard.USE_NMA:
update_text=common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title=common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._sendNMA(nma_api=None, nma_priority=None, event=title, message=update_text + new_version)
def _sendNMA(self, nma_api=None, nma_priority=None, event=None, message=None, force=False):
title = 'SickGear'
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
nma_api = self._choose(nma_api, sickbeard.NMA_API)
nma_priority = self._choose(nma_priority, sickbeard.NMA_PRIORITY)
batch = False
@ -49,17 +17,22 @@ class NMA_Notifier:
keys = nma_api.split(',')
p.addkey(keys)
if len(keys) > 1: batch = True
if 1 < len(keys):
batch = True
logger.log('NMA: Sending notice with details: event="%s", message="%s", priority=%s, batch=%s' % (event, message, nma_priority, batch), logger.DEBUG)
response = p.push(title, event, message, priority=nma_priority, batch_mode=batch)
self._log_debug('Sending notice with priority=%s, batch=%s' % (nma_priority, batch))
response = p.push('SickGear', title, body, 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:
logger.log(u'NMA: Notification sent to NotifyMyAndroid', logger.MESSAGE)
return True
result = False
try:
if u'200' != response[nma_api][u'code']:
self._log_error('Notification failed')
else:
result = True
except (StandardError, Exception):
pass
return result
notifier = NMA_Notifier
notifier = NMANotifier

View file

@ -16,188 +16,159 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import urllib, urllib2
import sickbeard
import telnetlib
import re
import telnetlib
import urllib
import urllib2
import xml.etree.cElementTree as XmlEtree
from sickbeard import logger
import sickbeard
from sickbeard.exceptions import ex
try:
import xml.etree.cElementTree as etree
except ImportError:
import xml.etree.ElementTree as etree
from sickbeard.notifiers.generic import BaseNotifier
class NMJNotifier:
class NMJNotifier(BaseNotifier):
def notify_settings(self, host):
"""
Retrieves the settings from a NMJ/Popcorn hour
host: The hostname/IP of the Popcorn Hour server
Returns: True if the settings were retrieved successfully, False otherwise
"""
# establish a terminal session to the PC
terminal = False
result, terminal = False, None
try:
terminal = telnetlib.Telnet(host)
except Exception:
logger.log(u'Warning: unable to get a telnet session to %s' % (host), logger.WARNING)
return False
except (StandardError, Exception):
self._log_warning(u'Unable to get a telnet session to %s' % host)
# tell the terminal to output the necessary info to the screen so we can search it later
logger.log(u'Connected to %s via telnet' % (host), logger.DEBUG)
terminal.read_until('sh-3.00# ')
terminal.write('cat /tmp/source\n')
terminal.write('cat /tmp/netshare\n')
terminal.write('exit\n')
tnoutput = terminal.read_all()
if result:
# tell the terminal to output the necessary info to the screen so we can search it later
self._log_debug(u'Connected to %s via telnet' % host)
terminal.read_until('sh-3.00# ')
terminal.write('cat /tmp/source\n')
terminal.write('cat /tmp/netshare\n')
terminal.write('exit\n')
tnoutput = terminal.read_all()
database = ''
device = ''
match = re.search(r'(.+\.db)\r\n?(.+)(?=sh-3.00# cat /tmp/netshare)', tnoutput)
# if we found the database in the terminal output then save that database to the config
if match:
database = match.group(1)
device = match.group(2)
logger.log(u'Found NMJ database %s on device %s' % (database, device), logger.DEBUG)
sickbeard.NMJ_DATABASE = database
else:
logger.log(u'Could not get current NMJ database on %s, NMJ is probably not running!' % (host), logger.WARNING)
return False
# if the device is a remote host then try to parse the mounting URL and save it to the config
if device.startswith('NETWORK_SHARE/'):
match = re.search('.*(?=\r\n?%s)' % (re.escape(device[14:])), tnoutput)
if match:
mount = match.group().replace('127.0.0.1', host)
logger.log(u'Found mounting url on the Popcorn Hour in configuration: %s' % (mount), logger.DEBUG)
sickbeard.NMJ_MOUNT = mount
match = re.search(r'(.+\.db)\r\n?(.+)(?=sh-3.00# cat /tmp/netshare)', tnoutput)
# if we found the database in the terminal output then save that database to the config
if not match:
self._log_warning(u'Could not get current NMJ database on %s, NMJ is probably not running!' % host)
else:
logger.log(u'Detected a network share on the Popcorn Hour, but could not get the mounting url',
logger.WARNING)
return False
database = match.group(1)
device = match.group(2)
self._log_debug(u'Found NMJ database %s on device %s' % (database, device))
sickbeard.NMJ_DATABASE = database
# if the device is a remote host then try to parse the mounting URL and save it to the config
if device.startswith('NETWORK_SHARE/'):
match = re.search('.*(?=\r\n?%s)' % (re.escape(device[14:])), tnoutput)
return True
if not match:
self._log_warning('Detected a network share on the Popcorn Hour, '
'but could not get the mounting url')
else:
mount = match.group().replace('127.0.0.1', host)
self._log_debug(u'Found mounting url on the Popcorn Hour in configuration: %s' % mount)
sickbeard.NMJ_MOUNT = mount
result = True
def notify_snatch(self, ep_name):
return False
#Not implemented: Start the scanner when snatched does not make any sense
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}
return '{"message": "Failed! Make sure your Popcorn is on and NMJ is running. ' \
'(see Error Log -> Debug for detailed info)", "database": "", "mount": ""}'
def notify_download(self, ep_name):
if sickbeard.USE_NMJ:
self._notifyNMJ()
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.USE_NMJ:
self._notifyNMJ()
def notify_git_update(self, new_version):
return False
# Not implemented, no reason to start scanner.
def test_notify(self, host, database, mount):
return self._sendNMJ(host, database, mount)
def _sendNMJ(self, host, database, mount=None):
def _send(self, host=None, database=None, mount=None):
"""
Sends a NMJ update command to the specified machine
host: The hostname/IP to send the request to (no port)
database: The database to send the requst to
mount: The mount URL to use (optional)
Returns: True if the request succeeded, False otherwise
"""
host = self._choose(host, sickbeard.NMJ_HOST)
database = self._choose(database, sickbeard.NMJ_DATABASE)
mount = self._choose(mount, sickbeard.NMJ_MOUNT)
self._log_debug(u'Sending scan command for NMJ ')
# if a mount URL is provided then attempt to open a handle to that URL
if mount:
try:
req = urllib2.Request(mount)
logger.log(u'Try to mount network drive via url: %s' % (mount), logger.DEBUG)
handle = urllib2.urlopen(req)
self._log_debug(u'Try to mount network drive via url: %s' % mount)
urllib2.urlopen(req)
except IOError as e:
if hasattr(e, 'reason'):
logger.log(u'NMJ: Could not contact Popcorn Hour on host %s: %s' % (host, e.reason), logger.WARNING)
self._log_warning(u'Could not contact Popcorn Hour on host %s: %s' % (host, e.reason))
elif hasattr(e, 'code'):
logger.log(u'NMJ: Problem with Popcorn Hour on host %s: %s' % (host, e.code), logger.WARNING)
self._log_warning(u'Problem with Popcorn Hour on host %s: %s' % (host, e.code))
return False
except Exception as e:
logger.log(u'NMJ: Unknown exception: ' + ex(e), logger.ERROR)
self._log_error(u'Unknown exception: ' + ex(e))
return False
# build up the request URL and parameters
UPDATE_URL = 'http://%(host)s:8008/metadata_database?%(params)s'
params = {
'arg0': 'scanner_start',
'arg1': database,
'arg2': 'background',
'arg3': ''
}
params = dict(arg0='scanner_start', arg1=database, arg2='background', arg3='')
params = urllib.urlencode(params)
updateUrl = UPDATE_URL % {'host': host, 'params': params}
update_url = 'http://%(host)s:8008/metadata_database?%(params)s' % {'host': host, 'params': params}
# send the request to the server
try:
req = urllib2.Request(updateUrl)
logger.log(u'Sending NMJ scan update command via url: %s' % (updateUrl), logger.DEBUG)
req = urllib2.Request(update_url)
self._log_debug(u'Sending scan update command via url: %s' % update_url)
handle = urllib2.urlopen(req)
response = handle.read()
except IOError as e:
if hasattr(e, 'reason'):
logger.log(u'NMJ: Could not contact Popcorn Hour on host %s: %s' % (host, e.reason), logger.WARNING)
self._log_warning(u'Could not contact Popcorn Hour on host %s: %s' % (host, e.reason))
elif hasattr(e, 'code'):
logger.log(u'NMJ: Problem with Popcorn Hour on host %s: %s' % (host, e.code), logger.WARNING)
self._log_warning(u'Problem with Popcorn Hour on host %s: %s' % (host, e.code))
return False
except Exception as e:
logger.log(u'NMJ: Unknown exception: ' + ex(e), logger.ERROR)
self._log_error(u'Unknown exception: ' + ex(e))
return False
# try to parse the resulting XML
try:
et = etree.fromstring(response)
et = XmlEtree.fromstring(response)
result = et.findtext('returnValue')
except SyntaxError as e:
logger.log(u'Unable to parse XML returned from the Popcorn Hour: %s' % (e), logger.ERROR)
self._log_error(u'Unable to parse XML returned from the Popcorn Hour: %s' % e)
return False
# if the result was a number then consider that an error
if int(result) > 0:
logger.log(u'Popcorn Hour returned an errorcode: %s' % (result), logger.ERROR)
return False
else:
logger.log(u'NMJ started background scan', logger.MESSAGE)
return True
def _notifyNMJ(self, host=None, database=None, mount=None, force=False):
"""
Sends a NMJ update command based on the SB config settings
host: The host to send the command to (optional, defaults to the host in the config)
database: The database to use (optional, defaults to the database in the config)
mount: The mount URL (optional, defaults to the mount URL in the config)
force: If True then the notification will be sent even if NMJ is disabled in the config
"""
if not sickbeard.USE_NMJ and not force:
logger.log('Notification for NMJ scan update not enabled, skipping this notification', logger.DEBUG)
if 0 < int(result):
self._log_error(u'Popcorn Hour returned an errorcode: %s' % result)
return False
# fill in omitted parameters
if not host:
host = sickbeard.NMJ_HOST
if not database:
database = sickbeard.NMJ_DATABASE
if not mount:
mount = sickbeard.NMJ_MOUNT
self._log(u'NMJ started background scan')
return True
logger.log(u'Sending scan command for NMJ ', logger.DEBUG)
def _notify(self, host=None, database=None, mount=None, **kwargs):
return self._sendNMJ(host, database, mount)
result = self._send(host, database, mount)
return self._choose((('Success, started %s', 'Failed to start %s')[not result] % 'the scan update'), result)
def test_notify(self, host, database, mount):
self._testing = True
return self._notify(host, database, mount)
# notify_snatch() Not implemented: Start the scanner when snatched does not make sense
# notify_git_update() Not implemented, no reason to start scanner
def notify_download(self):
self._notify()
def notify_subtitle_download(self):
self._notify()
notifier = NMJNotifier

View file

@ -17,122 +17,129 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import urllib, urllib2, xml.dom.minidom
from xml.dom.minidom import parseString
import sickbeard
import telnetlib
import re
import telnetlib
import time
import urllib
import urllib2
import xml.dom.minidom
from xml.dom.minidom import parseString
import xml.etree.cElementTree as XmlEtree
from sickbeard import logger
try:
import xml.etree.cElementTree as etree
except ImportError:
import xml.etree.ElementTree as etree
import sickbeard
from sickbeard.notifiers.generic import BaseNotifier
class NMJv2Notifier:
def notify_snatch(self, ep_name):
return False
#Not implemented: Start the scanner when snatched does not make any sense
class NMJv2Notifier(BaseNotifier):
def notify_download(self, ep_name):
self._notifyNMJ()
def notify_subtitle_download(self, ep_name, lang):
self._notifyNMJ()
def notify_git_update(self, new_version):
return False
# Not implemented, no reason to start scanner.
def test_notify(self, host):
return self._sendNMJ(host)
def notify_settings(self, host, dbloc, instance):
def notify_settings(self, host, db_loc, instance):
"""
Retrieves the NMJv2 database location from Popcorn hour
host: The hostname/IP of the Popcorn Hour server
dbloc: 'local' for PCH internal harddrive. 'network' for PCH network shares
instance: Allows for selection of different DB in case of multiple databases
Returns: True if the settings were retrieved successfully, False otherwise
"""
result = False
try:
url_loc = 'http://' + host + ':8008/file_operation?arg0=list_user_storage_file&arg1=&arg2=' + instance + '&arg3=20&arg4=true&arg5=true&arg6=true&arg7=all&arg8=name_asc&arg9=false&arg10=false'
req = urllib2.Request(url_loc)
handle1 = urllib2.urlopen(req)
response1 = handle1.read()
xml = parseString(response1)
base_url = 'http://%s:8008/' % host
req = urllib2.Request('%s%s%s' % (base_url, 'file_operation?', urllib.urlencode(
dict(arg0='list_user_storage_file', arg1='', arg2=instance, arg3=20, arg4='true', arg5='true',
arg6='true', arg7='all', arg8='name_asc', arg9='false', arg10='false'))))
handle = urllib2.urlopen(req)
response = handle.read()
xml_data = parseString(response)
time.sleep(300.0 / 1000.0)
for node in xml.getElementsByTagName('path'):
xmlTag = node.toxml();
xmlData = xmlTag.replace('<path>', '').replace('</path>', '').replace('[=]', '')
url_db = 'http://' + host + ':8008/metadata_database?arg0=check_database&arg1=' + xmlData
reqdb = urllib2.Request(url_db)
for node in xml_data.getElementsByTagName('path'):
xml_tag = node.toxml()
reqdb = urllib2.Request('%s%s%s' % (base_url, 'metadata_database?', urllib.urlencode(
dict(arg0='check_database',
arg1=xml_tag.replace('<path>', '').replace('</path>', '').replace('[=]', '')))))
handledb = urllib2.urlopen(reqdb)
responsedb = handledb.read()
xmldb = parseString(responsedb)
returnvalue = xmldb.getElementsByTagName('returnValue')[0].toxml().replace('<returnValue>', '').replace(
'</returnValue>', '')
if returnvalue == '0':
DB_path = xmldb.getElementsByTagName('database_path')[0].toxml().replace('<database_path>',
'').replace(
'</database_path>', '').replace('[=]', '')
if dbloc == 'local' and DB_path.find('localhost') > -1:
xml_db = parseString(responsedb)
if '0' == xml_db.getElementsByTagName('returnValue')[0].toxml().replace(
'<returnValue>', '').replace('</returnValue>', ''):
db_path = xml_db.getElementsByTagName('database_path')[0].toxml().replace(
'<database_path>', '').replace('</database_path>', '').replace('[=]', '')
if 'local' == db_loc and db_path.find('localhost') > -1:
sickbeard.NMJv2_HOST = host
sickbeard.NMJv2_DATABASE = DB_path
return True
if dbloc == 'network' and DB_path.find('://') > -1:
sickbeard.NMJv2_DATABASE = db_path
result = True
if 'network' == db_loc and db_path.find('://') > -1:
sickbeard.NMJv2_HOST = host
sickbeard.NMJv2_DATABASE = DB_path
return True
sickbeard.NMJv2_DATABASE = db_path
result = True
except IOError as e:
logger.log(u"Warning: Couldn't contact popcorn hour on host %s: %s" % (host, e), logger.WARNING)
return False
return False
self._log_warning(u'Couldn\'t contact popcorn hour on host %s: %s' % (host, e))
def _sendNMJ(self, host):
if result:
return '{"message": "Success, NMJ Database found at: %(host)s", "database": "%(database)s"}' % {
"host": host, "database": sickbeard.NMJv2_DATABASE}
return '{"message": "Failed to find NMJ Database at location: %(dbloc)s. ' \
'Is the right location selected and PCH running? ", "database": ""}' % {"dbloc": db_loc}
def _send(self, host=None):
"""
Sends a NMJ update command to the specified machine
host: The hostname/IP to send the request to (no port)
database: The database to send the requst to
mount: The mount URL to use (optional)
Returns: True if the request succeeded, False otherwise
"""
#if a host is provided then attempt to open a handle to that URL
host = self._choose(host, sickbeard.NMJv2_HOST)
self._log_debug(u'Sending scan command for NMJ ')
# if a host is provided then attempt to open a handle to that URL
try:
url_scandir = 'http://' + host + ':8008/metadata_database?arg0=update_scandir&arg1=' + sickbeard.NMJv2_DATABASE + '&arg2=&arg3=update_all'
logger.log(u'NMJ scan update command sent to host: %s' % (host), logger.DEBUG)
url_updatedb = 'http://' + host + ':8008/metadata_database?arg0=scanner_start&arg1=' + sickbeard.NMJv2_DATABASE + '&arg2=background&arg3='
logger.log(u'Try to mount network drive via url: %s' % (host), logger.DEBUG)
base_url = 'http://%s:8008/' % host
url_scandir = '%s%s%s' % (base_url, 'metadata_database?', urllib.urlencode(
dict(arg0='update_scandir', arg1=sickbeard.NMJv2_DATABASE, arg2='', arg3='update_all')))
self._log_debug(u'Scan update command sent to host: %s' % host)
url_updatedb = '%s%s%s' % (base_url, 'metadata_database?', urllib.urlencode(
dict(arg0='scanner_start', arg1=sickbeard.NMJv2_DATABASE, arg2='background', arg3='')))
self._log_debug(u'Try to mount network drive via url: %s' % host)
prereq = urllib2.Request(url_scandir)
req = urllib2.Request(url_updatedb)
handle1 = urllib2.urlopen(prereq)
response1 = handle1.read()
time.sleep(300.0 / 1000.0)
handle2 = urllib2.urlopen(req)
response2 = handle2.read()
except IOError as e:
logger.log(u"Warning: Couldn't contact popcorn hour on host %s: %s" % (host, e), logger.WARNING)
self._log_warning(u'Couldn\'t contact popcorn hour on host %s: %s' % (host, e))
return False
try:
et = etree.fromstring(response1)
et = XmlEtree.fromstring(response1)
result1 = et.findtext('returnValue')
except SyntaxError as e:
logger.log(u'Unable to parse XML returned from the Popcorn Hour: update_scandir, %s' % (e), logger.ERROR)
self._log_error(u'Unable to parse XML returned from the Popcorn Hour: update_scandir, %s' % e)
return False
try:
et = etree.fromstring(response2)
et = XmlEtree.fromstring(response2)
result2 = et.findtext('returnValue')
except SyntaxError as e:
logger.log(u'Unable to parse XML returned from the Popcorn Hour: scanner_start, %s' % (e), logger.ERROR)
self._log_error(u'Unable to parse XML returned from the Popcorn Hour: scanner_start, %s' % e)
return False
# if the result was a number then consider that an error
@ -144,39 +151,38 @@ class NMJv2Notifier:
'Database read error',
'Open fifo pipe failed',
'Read only file system']
if int(result1) > 0:
if 0 < int(result1):
index = error_codes.index(result1)
logger.log(u'Popcorn Hour returned an error: %s' % (error_messages[index]), logger.ERROR)
return False
else:
if int(result2) > 0:
index = error_codes.index(result2)
logger.log(u'Popcorn Hour returned an error: %s' % (error_messages[index]), logger.ERROR)
return False
else:
logger.log(u'NMJv2 started background scan', logger.MESSAGE)
return True
def _notifyNMJ(self, host=None, force=False):
"""
Sends a NMJ update command based on the SB config settings
host: The host to send the command to (optional, defaults to the host in the config)
database: The database to use (optional, defaults to the database in the config)
mount: The mount URL (optional, defaults to the mount URL in the config)
force: If True then the notification will be sent even if NMJ is disabled in the config
"""
if not sickbeard.USE_NMJv2 and not force:
logger.log('Notification for NMJ scan update not enabled, skipping this notification', logger.DEBUG)
self._log_error(u'Popcorn Hour returned an error: %s' % (error_messages[index]))
return False
# fill in omitted parameters
if not host:
host = sickbeard.NMJv2_HOST
elif 0 < int(result2):
index = error_codes.index(result2)
self._log_error(u'Popcorn Hour returned an error: %s' % (error_messages[index]))
return False
logger.log(u'Sending scan command for NMJ ', logger.DEBUG)
self._log(u'NMJv2 started background scan')
return True
return self._sendNMJ(host)
def _notify(self, host=None, **kwargs):
result = self._send(host)
return self._choose((('Success, started %s', 'Failed to start %s')[not result] % 'the scan update at "%s"'
% host), result)
def test_notify(self, host):
self._testing = True
return self._notify(host)
# notify_snatch() Not implemented: Start the scanner when snatched does not make sense
# notify_git_update() Not implemented, no reason to start scanner
def notify_download(self):
self._notify()
def notify_subtitle_download(self):
self._notify()
notifier = NMJv2Notifier

View file

@ -20,28 +20,18 @@ import urllib
import urllib2
import base64
import re
import xml.etree.cElementTree as XmlEtree
import sickbeard
from sickbeard import common, logger
from sickbeard.exceptions import ex
from sickbeard.encodingKludge import fixStupidEncodings
try:
import xml.etree.cElementTree as etree
except ImportError:
import elementtree.ElementTree as etree
from sickbeard.exceptions import ex
from sickbeard.notifiers.generic import Notifier
class PLEXNotifier:
class PLEXNotifier(Notifier):
def __init__(self):
self.name = 'PLEX'
def log(self, msg, level=logger.MESSAGE):
logger.log(u'%s: %s' % (self.name, msg), level)
super(PLEXNotifier, self).__init__()
def _send_to_plex(self, command, host, username=None, password=None):
"""Handles communication to Plex hosts via HTTP API
@ -53,18 +43,11 @@ class PLEXNotifier:
password: Plex API password
Returns:
Returns 'OK' for successful commands or False if there was an error
Returns True for successful commands or False if there was an error
"""
# fill in omitted parameters
if not username:
username = sickbeard.PLEX_USERNAME
if not password:
password = sickbeard.PLEX_PASSWORD
if not host:
self.log(u'No host specified, check your settings', logger.ERROR)
self._log_error(u'No host specified, check your settings')
return False
for key in command:
@ -72,102 +55,30 @@ class PLEXNotifier:
command[key] = command[key].encode('utf-8')
enc_command = urllib.urlencode(command)
self.log(u'Encoded API command: ' + enc_command, logger.DEBUG)
self._log_debug(u'Encoded API command: ' + enc_command)
url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, enc_command)
try:
req = urllib2.Request(url)
# if we have a password, use authentication
if password:
base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
authheader = 'Basic %s' % base64string
req.add_header('Authorization', authheader)
self.log(u'Contacting (with auth header) via url: ' + url, logger.DEBUG)
self._log_debug(u'Contacting (with auth header) via url: ' + url)
else:
self.log(u'Contacting via url: ' + url, logger.DEBUG)
self._log_debug(u'Contacting via url: ' + url)
response = urllib2.urlopen(req)
result = response.read().decode(sickbeard.SYS_ENCODING)
response.close()
self.log(u'HTTP response: ' + result.replace('\n', ''), logger.DEBUG)
# could return result response = re.compile('<html><li>(.+\w)</html>').findall(result)
return 'OK'
self._log_debug(u'HTTP response: ' + result.replace('\n', ''))
return True
except (urllib2.URLError, IOError) as e:
self.log(u'Couldn\'t contact Plex at ' + fixStupidEncodings(url) + ' ' + ex(e), logger.WARNING)
self._log_warning(u'Couldn\'t contact Plex at ' + fixStupidEncodings(url) + ' ' + ex(e))
return False
def _notify_pmc(self, message, title='SickGear', host=None, username=None, password=None, force=False):
"""Internal wrapper for the notify_snatch and notify_download functions
Args:
message: Message body of the notice to send
title: Title of the notice to send
host: Plex Media Client(s) host:port
username: Plex username
password: Plex password
force: Used for the Test method to override config safety checks
Returns:
Returns a list results in the format of host:ip:result
The result will either be 'OK' or False, this is used to be parsed by the calling function.
"""
# suppress notifications if the notifier is disabled but the notify options are checked
if not sickbeard.USE_PLEX and not force:
return False
# fill in omitted parameters
if not host:
host = sickbeard.PLEX_HOST
if not username:
username = sickbeard.PLEX_USERNAME
if not password:
password = sickbeard.PLEX_PASSWORD
result = ''
for curHost in [x.strip() for x in host.split(',')]:
self.log(u'Sending notification to \'%s\' - %s' % (curHost, message))
command = {'command': 'ExecBuiltIn',
'parameter': 'Notification(%s,%s)' % (title.encode('utf-8'), message.encode('utf-8'))}
notify_result = self._send_to_plex(command, curHost, username, password)
if notify_result:
result += '%s:%s' % (curHost, str(notify_result))
return result
##############################################################################
# Public functions
##############################################################################
def notify_snatch(self, ep_name):
if sickbeard.PLEX_NOTIFY_ONSNATCH:
self._notify_pmc(ep_name, common.notifyStrings[common.NOTIFY_SNATCH])
def notify_download(self, ep_name):
if sickbeard.PLEX_NOTIFY_ONDOWNLOAD:
self._notify_pmc(ep_name, common.notifyStrings[common.NOTIFY_DOWNLOAD])
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.PLEX_NOTIFY_ONSUBTITLEDOWNLOAD:
self._notify_pmc(ep_name + ': ' + lang, common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD])
def notify_git_update(self, new_version='??'):
if sickbeard.USE_PLEX and sickbeard.PLEX_HOST:
update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title = common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._notify_pmc(update_text + new_version, title)
def test_notify(self, host, username, password, server=False):
if server:
return self.update_library(host=host, username=username, password=password, force=False, test=True)
return self._notify_pmc(
'This is a test notification from SickGear', 'Test', host, username, password, force=True)
@staticmethod
def _get_host_list(host='', enable_secure=False):
"""
@ -184,7 +95,54 @@ class PLEXNotifier:
return host_list
def update_library(self, ep_obj=None, host=None, username=None, password=None, force=True, test=False):
def _notify(self, title, body, host=None, username=None, password=None, **kwargs):
"""Internal wrapper for the notify_snatch and notify_download functions
Args:
title: Title of the notice to send
body: Message body of the notice to send
host: Plex Media Client(s) host:port
username: Plex username
password: Plex password
Returns:
Returns a test result string for ui output while testing, otherwise True if all tests are a success
"""
host = self._choose(host, sickbeard.PLEX_HOST)
username = self._choose(username, sickbeard.PLEX_USERNAME)
password = self._choose(password, sickbeard.PLEX_PASSWORD)
command = {'command': 'ExecBuiltIn',
'parameter': 'Notification(%s,%s)' % (title.encode('utf-8'), body.encode('utf-8'))}
results = []
for cur_host in [x.strip() for x in host.split(',')]:
cur_host = urllib.unquote_plus(cur_host)
self._log(u'Sending notification to \'%s\'' % cur_host)
result = self._send_to_plex(command, cur_host, username, password)
results += [self._choose(('%s Plex client ... %s' % (('Successful test notice sent to',
'Failed test for')[not result], cur_host)), result)]
return self._choose('<br>\n'.join(results), all(results))
##############################################################################
# Public functions
##############################################################################
def notify_git_update(self, new_version='??', **kwargs):
# ensure PMS is setup, this is not for when clients are
if sickbeard.PLEX_HOST:
super(PLEXNotifier, self).notify_git_update(new_version, **kwargs)
def test_update_library(self, host=None, username=None, password=None):
self._testing = True
result = self.update_library(host=urllib.unquote_plus(host), username=username, password=password)
if '<br>' == result:
result += 'Fail: No valid host set to connect with'
return (('Test result for', 'Successful test of')['Fail' not in result]
+ ' Plex server(s) ... %s<br>\n' % result)
def update_library(self, ep_obj=None, host=None, username=None, password=None, **kwargs):
"""Handles updating the Plex Media Server host via HTTP API
Plex Media Server currently only supports updating the whole video library and not a specific path.
@ -193,126 +151,120 @@ class PLEXNotifier:
Returns None for no issue, else a string of host with connection issues
"""
host = self._choose(host, sickbeard.PLEX_SERVER_HOST)
if not host:
msg = u'No Plex Media Server host specified, check your settings'
self._log_debug(msg)
return '%sFail: %s' % (('', '<br>')[self._testing], msg)
if sickbeard.USE_PLEX and sickbeard.PLEX_UPDATE_LIBRARY or test:
username = self._choose(username, sickbeard.PLEX_USERNAME)
password = self._choose(password, sickbeard.PLEX_PASSWORD)
if not test:
if not sickbeard.PLEX_SERVER_HOST:
msg = u'No Plex Media Server host specified, check your settings'
self.log(msg, logger.DEBUG)
return '%sFail: %s' % (('', '<br />')[test], msg)
# if username and password were provided, fetch the auth token from plex.tv
token_arg = None
if username and password:
if not host:
host = sickbeard.PLEX_SERVER_HOST
if not username:
username = sickbeard.PLEX_USERNAME
if not password:
password = sickbeard.PLEX_PASSWORD
self._log_debug(u'Fetching plex.tv credentials for user: ' + username)
req = urllib2.Request('https://plex.tv/users/sign_in.xml', data='')
authheader = 'Basic %s' % base64.encodestring('%s:%s' % (username, password))[:-1]
req.add_header('Authorization', authheader)
req.add_header('X-Plex-Device-Name', 'SickGear')
req.add_header('X-Plex-Product', 'SickGear Notifier')
req.add_header('X-Plex-Client-Identifier', '5f48c063eaf379a565ff56c9bb2b401e')
req.add_header('X-Plex-Version', '1.0')
token_arg = False
# if username and password were provided, fetch the auth token from plex.tv
token_arg = None
if username and password:
try:
response = urllib2.urlopen(req)
auth_tree = XmlEtree.parse(response)
token = auth_tree.findall('.//authentication-token')[0].text
token_arg = '?X-Plex-Token=' + token
self.log(u'fetching plex.tv credentials for user: ' + username, logger.DEBUG)
req = urllib2.Request('https://plex.tv/users/sign_in.xml', data='')
authheader = 'Basic %s' % base64.encodestring('%s:%s' % (username, password))[:-1]
req.add_header('Authorization', authheader)
req.add_header('X-Plex-Device-Name', 'SickGear')
req.add_header('X-Plex-Product', 'SickGear Notifier')
req.add_header('X-Plex-Client-Identifier', '5f48c063eaf379a565ff56c9bb2b401e')
req.add_header('X-Plex-Version', '1.0')
token_arg = False
except urllib2.URLError as e:
self._log(u'Error fetching credentials from plex.tv for user %s: %s' % (username, ex(e)))
try:
response = urllib2.urlopen(req)
auth_tree = etree.parse(response)
token = auth_tree.findall('.//authentication-token')[0].text
token_arg = '?X-Plex-Token=' + token
except (ValueError, IndexError) as e:
self._log(u'Error parsing plex.tv response: ' + ex(e))
except urllib2.URLError as e:
self.log(u'Error fetching credentials from plex.tv for user %s: %s' % (username, ex(e)))
file_location = '' if None is ep_obj else ep_obj.location
host_validate = self._get_host_list(host, all([token_arg]))
hosts_all = {}
hosts_match = {}
hosts_failed = []
for cur_host in host_validate:
response = sickbeard.helpers.getURL(
'%s/library/sections%s' % (cur_host, token_arg or ''), timeout=10,
mute_connect_err=True, mute_read_timeout=True, mute_connect_timeout=True)
if response:
response = sickbeard.helpers.parse_xml(response)
if not response:
hosts_failed.append(cur_host)
continue
except (ValueError, IndexError) as e:
self.log(u'Error parsing plex.tv response: ' + ex(e))
sections = response.findall('.//Directory')
if not sections:
self._log(u'Plex Media Server not running on: ' + cur_host)
hosts_failed.append(cur_host)
continue
file_location = '' if None is ep_obj else ep_obj.location
host_validate = self._get_host_list(host, all([token_arg]))
hosts_all = {}
hosts_match = {}
hosts_failed = []
for cur_host in host_validate:
response = sickbeard.helpers.getURL(
'%s/library/sections%s' % (cur_host, token_arg or ''), timeout=10,
mute_connect_err=True, mute_read_timeout=True, mute_connect_timeout=True)
if response:
response = sickbeard.helpers.parse_xml(response)
if not response:
hosts_failed.append(cur_host)
for section in filter(lambda x: 'show' == x.attrib['type'], sections):
if str(section.attrib['key']) in hosts_all:
continue
keyed_host = [(str(section.attrib['key']), cur_host)]
hosts_all.update(keyed_host)
if not file_location:
continue
sections = response.findall('.//Directory')
if not sections:
self.log(u'Plex Media Server not running on: ' + cur_host)
hosts_failed.append(cur_host)
continue
for section_location in section.findall('.//Location'):
section_path = re.sub(r'[/\\]+', '/', section_location.attrib['path'].lower())
section_path = re.sub(r'^(.{,2})[/\\]', '', section_path)
location_path = re.sub(r'[/\\]+', '/', file_location.lower())
location_path = re.sub(r'^(.{,2})[/\\]', '', location_path)
for section in filter(lambda x: 'show' == x.attrib['type'], sections):
if str(section.attrib['key']) in hosts_all:
continue
keyed_host = [(str(section.attrib['key']), cur_host)]
hosts_all.update(keyed_host)
if not file_location:
continue
if section_path in location_path:
hosts_match.update(keyed_host)
break
for section_location in section.findall('.//Location'):
section_path = re.sub(r'[/\\]+', '/', section_location.attrib['path'].lower())
section_path = re.sub(r'^(.{,2})[/\\]', '', section_path)
location_path = re.sub(r'[/\\]+', '/', file_location.lower())
location_path = re.sub(r'^(.{,2})[/\\]', '', location_path)
if section_path in location_path:
hosts_match.update(keyed_host)
break
if not test:
hosts_try = (hosts_all.copy(), hosts_match.copy())[any(hosts_match)]
host_list = []
for section_key, cur_host in hosts_try.items():
refresh_result = None
if force:
refresh_result = sickbeard.helpers.getURL(
'%s/library/sections/%s/refresh%s' % (cur_host, section_key, token_arg or ''))
if (force and '' == refresh_result) or not force:
host_list.append(cur_host)
else:
hosts_failed.append(cur_host)
self.log(u'Error updating library section for Plex Media Server: %s' % cur_host, logger.ERROR)
if len(hosts_failed) == len(host_validate):
self.log(u'No successful Plex host updated')
return 'Fail no successful Plex host updated: %s' % ', '.join(host for host in hosts_failed)
if not self._testing:
hosts_try = (hosts_all.copy(), hosts_match.copy())[any(hosts_match)]
host_list = []
for section_key, cur_host in hosts_try.items():
refresh_result = None
if not self._testing:
refresh_result = sickbeard.helpers.getURL(
'%s/library/sections/%s/refresh%s' % (cur_host, section_key, token_arg or ''))
if (not self._testing and '' == refresh_result) or self._testing:
host_list.append(cur_host)
else:
hosts = ', '.join(set(host_list))
if len(hosts_match):
self.log(u'Hosts updating where TV section paths match the downloaded show: %s' % hosts)
else:
self.log(u'Updating all hosts with TV sections: %s' % hosts)
return ''
hosts_failed.append(cur_host)
self._log_error(u'Error updating library section for Plex Media Server: %s' % cur_host)
if len(hosts_failed) == len(host_validate):
self._log(u'No successful Plex host updated')
return 'Fail no successful Plex host updated: %s' % ', '.join(host for host in hosts_failed)
else:
hosts = ', '.join(set(host_list))
if len(hosts_match):
self._log(u'Hosts updating where TV section paths match the downloaded show: %s' % hosts)
else:
self._log(u'Updating all hosts with TV sections: %s' % hosts)
return ''
hosts = [
host.replace('http://', '') for host in filter(lambda x: x.startswith('http:'), hosts_all.values())]
secured = [
host.replace('https://', '') for host in filter(lambda x: x.startswith('https:'), hosts_all.values())]
failed = ', '.join([
host.replace('http://', '') for host in filter(lambda x: x.startswith('http:'), hosts_failed)])
failed_secured = ', '.join(filter(
lambda x: x not in hosts,
[host.replace('https://', '') for host in filter(lambda x: x.startswith('https:'), hosts_failed)]))
return '<br>' + '<br>'.join(result for result in [
('', 'Fail: username/password when fetching credentials from plex.tv')[False is token_arg],
('', 'OK (secure connect): %s' % ', '.join(secured))[any(secured)],
('', 'OK%s: %s' % ((' (legacy connect)', '')[None is token_arg], ', '.join(hosts)))[any(hosts)],
('', 'Fail (secure connect): %s' % failed_secured)[any(failed_secured)],
('', 'Fail%s: %s' % ((' (legacy connect)', '')[None is token_arg], failed))[bool(failed)]] if result)
hosts = [
host.replace('http://', '') for host in filter(lambda x: x.startswith('http:'), hosts_all.values())]
secured = [
host.replace('https://', '') for host in filter(lambda x: x.startswith('https:'), hosts_all.values())]
failed = [
host.replace('http://', '') for host in filter(lambda x: x.startswith('http:'), hosts_failed)]
failed_secured = ', '.join(filter(
lambda x: x not in hosts,
[host.replace('https://', '') for host in filter(lambda x: x.startswith('https:'), hosts_failed)]))
return '<br />' + '<br />'.join(result for result in [
('', 'Fail: username/password when fetching credentials from plex.tv')[False is token_arg],
('', 'OK (secure connect): %s' % ', '.join(secured))[any(secured)],
('', 'OK%s: %s' % ((' (legacy connect)', '')[None is token_arg], ', '.join(hosts)))[any(hosts)],
('', 'Fail (secure connect): %s' % failed_secured)[any(failed_secured)],
('', 'Fail%s: %s' % ((' (legacy connect)', '')[None is token_arg], failed))[any(failed)]] if result)
notifier = PLEXNotifier

View file

@ -16,94 +16,50 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
from lib.six import moves
import socket
from ssl import SSLError
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.notifiers.generic import Notifier
from sickbeard import logger, common
from lib.six import moves
class ProwlNotifier:
def test_notify(self, prowl_api, prowl_priority):
return self._sendProwl(prowl_api, prowl_priority, event='Test',
message='Testing Prowl settings from SickGear', force=True)
class ProwlNotifier(Notifier):
def notify_snatch(self, ep_name):
if sickbeard.PROWL_NOTIFY_ONSNATCH:
self._sendProwl(prowl_api=None, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_SNATCH],
message=ep_name)
def _notify(self, title, body, prowl_api=None, prowl_priority=None, **kwargs):
def notify_download(self, ep_name):
if sickbeard.PROWL_NOTIFY_ONDOWNLOAD:
self._sendProwl(prowl_api=None, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD],
message=ep_name)
prowl_api = self._choose(prowl_api, sickbeard.PROWL_API)
prowl_priority = self._choose(prowl_priority, sickbeard.PROWL_PRIORITY)
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.PROWL_NOTIFY_ONSUBTITLEDOWNLOAD:
self._sendProwl(prowl_api=None, prowl_priority=None,
event=common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD], message=ep_name + ': ' + lang)
def notify_git_update(self, new_version = '??'):
if sickbeard.USE_PROWL:
update_text=common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title=common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._sendProwl(prowl_api=None, prowl_priority=None,
event=title, message=update_text + new_version)
def _sendProwl(self, prowl_api=None, prowl_priority=None, event=None, message=None, force=False):
if not sickbeard.USE_PROWL and not force:
return False
if prowl_api == None:
prowl_api = sickbeard.PROWL_API
if prowl_priority == None:
prowl_priority = sickbeard.PROWL_PRIORITY
title = 'SickGear'
logger.log('PROWL: Sending notice with details: event="%s", message="%s", priority=%s, api=%s' % (event, message, prowl_priority, prowl_api), logger.DEBUG)
self._log_debug('Sending notice with details: title="%s", message="%s", priority=%s, api=%s' % (
title, body, prowl_priority, prowl_api))
http_handler = moves.http_client.HTTPSConnection('api.prowlapp.com')
data = {'apikey': prowl_api,
'application': title,
'event': event,
'description': message.encode('utf-8'),
'priority': prowl_priority}
data = dict(apikey=prowl_api, application='SickGear', event=title,
description=body.encode('utf-8'), priority=prowl_priority)
try:
http_handler.request('POST',
'/publicapi/add',
headers={'Content-type': 'application/x-www-form-urlencoded'},
body=urlencode(data))
http_handler.request('POST', '/publicapi/add',
headers={'Content-type': 'application/x-www-form-urlencoded'}, body=urlencode(data))
except (SSLError, moves.http_client.HTTPException, socket.error):
logger.log(u'Prowl notification failed.', logger.ERROR)
return False
response = http_handler.getresponse()
request_status = response.status
if request_status == 200:
logger.log(u'Prowl notifications sent.', logger.MESSAGE)
return True
elif request_status == 401:
logger.log(u'Prowl authentication failed: %s' % response.reason, logger.ERROR)
return False
result = 'Connection failed'
self._log_error(result)
else:
logger.log(u'Prowl notification failed.', logger.ERROR)
return False
response = http_handler.getresponse()
result = None
if 200 != response.status:
if 401 == response.status:
result = u'Authentication, %s (bad API key?)' % response.reason
else:
result = 'Http response code "%s"' % response.status
self._log_error(result)
return self._choose((True, 'Failed to send notification: %s' % result)[bool(result)], not bool(result))
notifier = ProwlNotifier

View file

@ -17,83 +17,47 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
from lib.six import moves
import socket
from urllib import urlencode
from ssl import SSLError
from urllib import urlencode
import sickbeard
from sickbeard import logger, common
from sickbeard.notifiers.generic import Notifier
from lib.six import moves
class PushalotNotifier:
def test_notify(self, pushalot_authorizationtoken):
return self._sendPushalot(pushalot_authorizationtoken, event='Test',
message='Testing Pushalot settings from SickGear', force=True)
class PushalotNotifier(Notifier):
def notify_snatch(self, ep_name):
if sickbeard.PUSHALOT_NOTIFY_ONSNATCH:
self._sendPushalot(pushalot_authorizationtoken=None, event=common.notifyStrings[common.NOTIFY_SNATCH],
message=ep_name)
def _notify(self, title, body, pushalot_auth_token=None, **kwargs):
def notify_download(self, ep_name):
if sickbeard.PUSHALOT_NOTIFY_ONDOWNLOAD:
self._sendPushalot(pushalot_authorizationtoken=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD],
message=ep_name)
pushalot_auth_token = self._choose(pushalot_auth_token, sickbeard.PUSHALOT_AUTHORIZATIONTOKEN)
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD:
self._sendPushalot(pushalot_authorizationtoken=None,
event=common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD],
message=ep_name + ': ' + lang)
def notify_git_update(self, new_version = '??'):
if sickbeard.USE_PUSHALOT:
update_text=common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title=common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._sendPushalot(pushalot_authorizationtoken=None,
event=title,
message=update_text + new_version)
def _sendPushalot(self, pushalot_authorizationtoken=None, event=None, message=None, force=False):
if not sickbeard.USE_PUSHALOT and not force:
return False
if pushalot_authorizationtoken == None:
pushalot_authorizationtoken = sickbeard.PUSHALOT_AUTHORIZATIONTOKEN
logger.log(u'Pushalot event: ' + event, logger.DEBUG)
logger.log(u'Pushalot message: ' + message, logger.DEBUG)
logger.log(u'Pushalot api: ' + pushalot_authorizationtoken, logger.DEBUG)
self._log_debug(u'Title: %s, Message: %s, API: %s' % (title, body, pushalot_auth_token))
http_handler = moves.http_client.HTTPSConnection('pushalot.com')
data = {'AuthorizationToken': pushalot_authorizationtoken,
'Title': event.encode('utf-8'),
'Body': message.encode('utf-8')}
try:
http_handler.request('POST',
'/api/sendmessage',
headers={'Content-type': 'application/x-www-form-urlencoded'},
body=urlencode(data))
http_handler.request('POST', '/api/sendmessage',
body=urlencode(dict(Title=title.encode('utf-8'), Body=body.encode('utf-8'),
AuthorizationToken=pushalot_auth_token)),
headers={'Content-type': 'application/x-www-form-urlencoded'})
except (SSLError, moves.http_client.HTTPException, socket.error):
logger.log(u'Pushalot notification failed.', logger.ERROR)
return False
response = http_handler.getresponse()
request_status = response.status
if request_status == 200:
logger.log(u'Pushalot notifications sent.', logger.DEBUG)
return True
elif request_status == 410:
logger.log(u'Pushalot authentication failed: %s' % response.reason, logger.ERROR)
return False
result = 'Connection failed'
self._log_error(result)
else:
logger.log(u'Pushalot notification failed.', logger.ERROR)
return False
response = http_handler.getresponse()
result = None
if 200 != response.status:
if 410 == response.status:
result = u'Authentication, %s (bad API key?)' % response.reason
else:
result = 'Http response code "%s"' % response.status
self._log_error(result)
return self._choose((True, 'Failed to send notification: %s' % result)[bool(result)], not bool(result))
notifier = PushalotNotifier

View file

@ -18,102 +18,63 @@
import base64
import simplejson as json
import sickbeard
from sickbeard import logger
from sickbeard.common import notifyStrings, NOTIFY_SNATCH, NOTIFY_DOWNLOAD, NOTIFY_SUBTITLE_DOWNLOAD, NOTIFY_GIT_UPDATE, NOTIFY_GIT_UPDATE_TEXT
import sickbeard
from sickbeard.notifiers.generic import Notifier
import requests
PUSHAPI_ENDPOINT = 'https://api.pushbullet.com/v2/pushes'
DEVICEAPI_ENDPOINT = 'https://api.pushbullet.com/v2/devices'
class PushbulletNotifier:
class PushbulletNotifier(Notifier):
def get_devices(self, accessToken=None):
@staticmethod
def get_devices(access_token=None):
# fill in omitted parameters
if not accessToken:
accessToken = sickbeard.PUSHBULLET_ACCESS_TOKEN
if not access_token:
access_token = sickbeard.PUSHBULLET_ACCESS_TOKEN
# get devices from pushbullet
try:
base64string = base64.encodestring('%s:%s' % (accessToken, ''))[:-1]
headers = {'Authorization': 'Basic %s' % base64string}
base64string = base64.encodestring('%s:%s' % (access_token, ''))[:-1]
headers = dict(Authorization='Basic %s' % base64string)
return requests.get(DEVICEAPI_ENDPOINT, headers=headers).text
except Exception as e:
return json.dumps({'error': {'message': 'Error failed to connect: %s' % e}})
except (StandardError, Exception):
return json.dumps(dict(error=dict(message='Error failed to connect')))
def _sendPushbullet(self, title, body, accessToken, device_iden):
# build up the URL and parameters
payload = {
'type': 'note',
'title': title,
'body': body.strip().encode('utf-8'),
'device_iden': device_iden
}
# send the request to pushbullet
try:
base64string = base64.encodestring('%s:%s' % (accessToken, ''))[:-1]
headers = {'Authorization': 'Basic %s' % base64string, 'Content-Type': 'application/json'}
result = requests.post(PUSHAPI_ENDPOINT, headers=headers, data=json.dumps(payload))
result.raise_for_status()
except Exception as e:
try:
e = result.json()['error']['message']
except:
pass
logger.log(u'PUSHBULLET: %s' % e, logger.WARNING)
return 'Error sending Pushbullet notification: %s' % e
logger.log(u'PUSHBULLET: Pushbullet notification succeeded', logger.MESSAGE)
return 'Pushbullet notification succeeded'
def _notifyPushbullet(self, title, body, accessToken=None, device_iden=None, force=False):
def _notify(self, title, body, access_token=None, device_iden=None, **kwargs):
"""
Sends a pushbullet notification based on the provided info or SG config
title: The title of the notification to send
body: The body string to send
accessToken: The access token to grant access
access_token: The access token to grant access
device_iden: The iden of a specific target, if none provided send to all devices
force: If True then the notification will be sent even if Pushbullet is disabled in the config
"""
access_token = self._choose(access_token, sickbeard.PUSHBULLET_ACCESS_TOKEN)
device_iden = self._choose(device_iden, sickbeard.PUSHBULLET_DEVICE_IDEN)
# suppress notifications if the notifier is disabled but the notify options are checked
if not sickbeard.USE_PUSHBULLET and not force:
return False
# send the request to Pushbullet
result = None
try:
base64string = base64.encodestring('%s:%s' % (access_token, ''))[:-1]
headers = {'Authorization': 'Basic %s' % base64string, 'Content-Type': 'application/json'}
resp = requests.post(PUSHAPI_ENDPOINT, headers=headers,
data=json.dumps(dict(
type='note', title=title, body=body.strip().encode('utf-8'),
device_iden=device_iden)))
resp.raise_for_status()
except (StandardError, Exception):
try:
# noinspection PyUnboundLocalVariable
result = resp.json()['error']['message']
except (StandardError, Exception):
result = 'no response'
self._log_warning(u'%s' % result)
# fill in omitted parameters
if not accessToken:
accessToken = sickbeard.PUSHBULLET_ACCESS_TOKEN
if not device_iden:
device_iden = sickbeard.PUSHBULLET_DEVICE_IDEN
return self._choose((True, 'Failed to send notification: %s' % result)[bool(result)], not bool(result))
logger.log(u'PUSHBULLET: Sending notice with details: "%s - %s", device_iden: %s' % (title, body, device_iden), logger.DEBUG)
return self._sendPushbullet(title, body, accessToken, device_iden)
def notify_snatch(self, ep_name):
if sickbeard.PUSHBULLET_NOTIFY_ONSNATCH:
self._notifyPushbullet(notifyStrings[NOTIFY_SNATCH], ep_name)
def notify_download(self, ep_name):
if sickbeard.PUSHBULLET_NOTIFY_ONDOWNLOAD:
self._notifyPushbullet(notifyStrings[NOTIFY_DOWNLOAD], ep_name)
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD:
self._notifyPushbullet(notifyStrings[NOTIFY_SUBTITLE_DOWNLOAD], ep_name + ': ' + lang)
def notify_git_update(self, new_version = '??'):
if sickbeard.USE_PUSHBULLET:
update_text=notifyStrings[NOTIFY_GIT_UPDATE_TEXT]
title=notifyStrings[NOTIFY_GIT_UPDATE]
self._notifyPushbullet(title, update_text + new_version)
def test_notify(self, accessToken, device_iden):
return self._notifyPushbullet('Test', 'This is a test notification from SickGear', accessToken, device_iden, force=True)
notifier = PushbulletNotifier

View file

@ -18,171 +18,114 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import base64
import socket
import time
import urllib
import urllib2
import time
import socket
import base64
import sickbeard
from sickbeard import logger
from sickbeard.common import notifyStrings, NOTIFY_SNATCH, NOTIFY_DOWNLOAD, NOTIFY_SUBTITLE_DOWNLOAD, NOTIFY_GIT_UPDATE, NOTIFY_GIT_UPDATE_TEXT
from sickbeard.exceptions import ex
from sickbeard.notifiers.generic import Notifier
API_URL = 'https://api.pushover.net/1/messages.json'
DEVICE_URL = 'https://api.pushover.net/1/users/validate.json'
class PushoverNotifier:
class PushoverNotifier(Notifier):
def get_devices(self, userKey=None, apiKey=None):
# fill in omitted parameters
if not userKey:
userKey = sickbeard.PUSHOVER_USERKEY
if not apiKey:
apiKey = sickbeard.PUSHOVER_APIKEY
def get_devices(self, user_key=None, api_key=None):
data = urllib.urlencode({
'token': apiKey,
'user': userKey
})
user_key = self._choose(user_key, sickbeard.PUSHOVER_USERKEY)
api_key = self._choose(api_key, sickbeard.PUSHOVER_APIKEY)
data = urllib.urlencode(dict(token=api_key, user=user_key))
# get devices from pushover
result = False
try:
req = urllib2.Request(DEVICE_URL)
handle = urllib2.urlopen(req, data)
if handle:
result = handle.read()
handle.close()
return result
except urllib2.URLError:
return None
except socket.timeout:
return None
except (urllib2.URLError, socket.timeout):
pass
def _sendPushover(self, title, msg, userKey, apiKey, priority, device, sound):
return ('{}', result)[bool(result)]
def _notify(self, title, body, user_key=None, api_key=None, priority=None, device=None, sound=None, **kwargs):
"""
Sends a pushover notification to the address provided
msg: The message to send (unicode)
title: The title of the message
userKey: The pushover user id to send the message to (or to subscribe with)
msg: The message to send (unicode)
user_key: The pushover user id to send the message to (or to subscribe with)
returns: True if the message succeeded, False otherwise
"""
# fill in omitted parameters
if not userKey:
userKey = sickbeard.PUSHOVER_USERKEY
if not apiKey:
apiKey = sickbeard.PUSHOVER_APIKEY
user_key = self._choose(user_key, sickbeard.PUSHOVER_USERKEY)
api_key = self._choose(api_key, sickbeard.PUSHOVER_APIKEY)
priority = self._choose(priority, sickbeard.PUSHOVER_PRIORITY)
device = self._choose(device, sickbeard.PUSHOVER_DEVICE)
sound = self._choose(sound, sickbeard.PUSHOVER_SOUND)
# build up the URL and parameters
msg = msg.strip()
data = urllib.urlencode({
'token': apiKey,
'title': title,
'user': userKey,
'message': msg.encode('utf-8'),
'priority': priority,
'device': device,
'sound': sound,
'timestamp': int(time.time())
})
params = dict(title=title, message=body.strip().encode('utf-8'), user=user_key, timestamp=int(time.time()))
if api_key:
params.update(token=api_key)
if priority:
params.update(priority=priority)
if not device:
params.update(device=device)
if not sound:
params.update(sound=sound)
# send the request to pushover
result = None
try:
req = urllib2.Request(API_URL)
handle = urllib2.urlopen(req, data)
handle = urllib2.urlopen(req, urllib.urlencode(params))
handle.close()
except urllib2.URLError as e:
# HTTP status 404 if the provided email address isn't a Pushover user.
if e.code == 404:
logger.log(u'PUSHOVER: Username is wrong/not a Pushover email. Pushover will send an email to it', logger.WARNING)
return False
if 404 == e.code:
result = 'Username is wrong/not a Pushover email. Pushover will send an email to it'
self._log_warning(result)
# For HTTP status code 401's, it is because you are passing in either an invalid token, or the user has not added your service.
elif e.code == 401:
# For HTTP status code 401's, it is because you are passing in either an invalid token,
# or the user has not added your service.
elif 401 == e.code:
# HTTP status 401 if the user doesn't have the service added
subscribeNote = self._sendPushover(title, msg, userKey)
if subscribeNote:
logger.log(u'PUSHOVER: Subscription sent', logger.DEBUG)
return True
subscribe_note = self._send_pushover(title, body, user_key)
if subscribe_note:
self._log_debug('Subscription sent')
# return True
else:
logger.log(u'PUSHOVER: Subscription could not be sent', logger.ERROR)
return False
result = 'Subscription could not be sent'
self._log_error(result)
else:
# If you receive an HTTP status code of 400, it is because you failed to send the proper parameters
if 400 == e.code:
result = 'Wrong data sent to Pushover'
# If you receive an HTTP status code of 400, it is because you failed to send the proper parameters
elif e.code == 400:
logger.log(u'PUSHOVER: Wrong data sent to Pushover', logger.ERROR)
return False
# If you receive a HTTP status code of 429,
# it is because the message limit has been reached (free limit is 7,500)
elif 429 == e.code:
result = 'API message limit reached - try a different API key'
# If you receive a HTTP status code of 429, it is because the message limit has been reached (free limit is 7,500)
elif e.code == 429:
logger.log(u'PUSHOVER: API message limit reached - try a different API key', logger.ERROR)
return False
# If you receive a HTTP status code of 500, service is unavailable
elif 500 == e.code:
result = 'Unable to connect to API, service unavailable'
# If you receive a HTTP status code of 500, service is unavailable
elif e.code == 500:
logger.log(u'PUSHOVER: Unable to connect to API, service unavailable', logger.ERROR)
return False
else:
result = 'Http response code "%s"' % response.status
logger.log(u'PUSHOVER: Notification successful.', logger.MESSAGE)
return True
self._log_error(result)
def _notifyPushover(self, title, message, userKey=None, apiKey=None, priority=None, device=None, sound=None, force=False):
"""
Sends a pushover notification based on the provided info or SG config
return self._choose((True, 'Failed to send notification: %s' % result)[bool(result)], not bool(result))
title: The title of the notification to send
message: The message string to send
userKey: The userKey to send the notification to
force: Enforce sending, for instance for testing
"""
# suppress notifications if the notifier is disabled but the notify options are checked
if not sickbeard.USE_PUSHOVER and not force:
logger.log(u'PUSHOVER: Notifications not enabled, skipping this notification', logger.DEBUG)
return False
# fill in omitted parameters
if not userKey:
userKey = sickbeard.PUSHOVER_USERKEY
if not apiKey:
apiKey = sickbeard.PUSHOVER_APIKEY
if not priority:
priority = sickbeard.PUSHOVER_PRIORITY
if not device:
device = sickbeard.PUSHOVER_DEVICE
if not sound:
sound = sickbeard.PUSHOVER_SOUND
logger.log(u'PUSHOVER: Sending notice with details: %s - %s, priority: %s, device: %s, sound: %s' % (title, message, priority, device, sound), logger.DEBUG)
return self._sendPushover(title, message, userKey, apiKey, priority, device, sound)
def test_notify(self, userKey, apiKey, priority, device, sound):
return self._notifyPushover('Test', 'This is a test notification from SickGear', userKey, apiKey, priority, device, sound, force=True)
def notify_snatch(self, ep_name):
if sickbeard.PUSHOVER_NOTIFY_ONSNATCH:
self._notifyPushover(notifyStrings[NOTIFY_SNATCH], ep_name)
def notify_download(self, ep_name):
if sickbeard.PUSHOVER_NOTIFY_ONDOWNLOAD:
self._notifyPushover(notifyStrings[NOTIFY_DOWNLOAD], ep_name)
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.PUSHOVER_NOTIFY_ONSUBTITLEDOWNLOAD:
self._notifyPushover(notifyStrings[NOTIFY_SUBTITLE_DOWNLOAD], ep_name + ': ' + lang)
def notify_git_update(self, new_version = '??'):
if sickbeard.USE_PUSHOVER:
update_text=notifyStrings[NOTIFY_GIT_UPDATE_TEXT]
title=notifyStrings[NOTIFY_GIT_UPDATE]
self._notifyPushover(title, update_text + new_version)
notifier = PushoverNotifier

View file

@ -17,93 +17,78 @@
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import os
import sickbeard
from urllib import urlencode
from urllib2 import Request, urlopen, HTTPError
from sickbeard import logger
from sickbeard.exceptions import ex
import sickbeard
# noinspection PyPep8Naming
from sickbeard import encodingKludge as ek
from sickbeard.exceptions import ex
from sickbeard.notifiers.generic import BaseNotifier
class pyTivoNotifier:
def notify_snatch(self, ep_name):
pass
class PyTivoNotifier(BaseNotifier):
def notify_download(self, ep_name):
pass
def notify_subtitle_download(self, ep_name, lang):
pass
def notify_git_update(self, new_version):
pass
def update_library(self, ep_obj):
# Values from config
if not sickbeard.USE_PYTIVO:
return False
def update_library(self, ep_obj=None, **kwargs):
host = sickbeard.PYTIVO_HOST
shareName = sickbeard.PYTIVO_SHARE_NAME
share_name = 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
# 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
show_path = ep_obj.show.location
show_name = ep_obj.show.name
root_show_and_season = ek.ek(os.path.dirname, ep_obj.location)
abs_path = 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(':', '')
show_name = show_name.replace(':', '')
root = showPath.replace(showName, '')
showAndSeason = rootShowAndSeason.replace(root, '')
root = show_path.replace(show_name, '')
show_and_season = root_show_and_season.replace(root, '')
container = shareName + '/' + showAndSeason
file = '/' + absPath.replace(root, '')
container = share_name + '/' + show_and_season
file_path = '/' + abs_path.replace(root, '')
# Finally create the url and make request
requestUrl = 'http://' + host + '/TiVoConnect?' + urlencode(
{'Command': 'Push', 'Container': container, 'File': file, 'tsn': tsn})
request_url = 'http://%s/TiVoConnect?%s' % (host, urlencode(
dict(Command='Push', Container=container, File=file_path, tsn=tsn)))
logger.log(u'pyTivo notification: Requesting ' + requestUrl, logger.DEBUG)
self._log_debug(u'Requesting ' + request_url)
request = Request(requestUrl)
request = Request(request_url)
try:
response = urlopen(request) #@UnusedVariable
urlopen(request)
except HTTPError as e:
if hasattr(e, 'reason'):
logger.log(u'pyTivo notification: Error, failed to reach a server - ' + e.reason, logger.ERROR)
self._log_error(u'Error, failed to reach a server - ' + e.reason)
return False
elif hasattr(e, 'code'):
logger.log(u"pyTivo notification: Error, the server couldn't fulfill the request - " + e.code, logger.ERROR)
self._log_error(u'Error, the server couldn\'t fulfill the request - ' + e.code)
return False
except Exception as e:
logger.log(u'PYTIVO: Unknown exception: ' + ex(e), logger.ERROR)
self._log_error(u'Unknown exception: ' + ex(e))
return False
else:
logger.log(u'pyTivo notification: Successfully requested transfer of file')
return True
self._log(u'Successfully requested transfer of file')
return True
notifier = pyTivoNotifier
notifier = PyTivoNotifier

View file

@ -18,49 +18,34 @@
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import sickbeard
from sickbeard import common, logger
from sickbeard.notifiers.generic import Notifier
class SlackNotifier:
class SlackNotifier(Notifier):
def __init__(self):
self.sg_logo_url = 'https://raw.githubusercontent.com/SickGear/SickGear/master' + \
'/gui/slick/images/ico/apple-touch-icon-precomposed.png'
super(SlackNotifier, self).__init__()
def _notify(self, msg, channel='', as_user=False, bot_name='', icon_url='', access_token='', force=False):
custom = (force and not as_user) or (not (force or sickbeard.SLACK_AS_USER))
resp = (sickbeard.USE_SLACK or force) and sickbeard.helpers.getURL(
def _notify(self, title, body, channel='', as_authed=None, bot_name='', icon_url='', access_token='', **kwargs):
custom = not self._choose(as_authed, sickbeard.SLACK_AS_AUTHED)
resp = sickbeard.helpers.getURL(
url='https://slack.com/api/chat.postMessage',
post_data=dict(
[('text', msg), ('token', (access_token, sickbeard.SLACK_ACCESS_TOKEN)[not access_token]),
('channel', (channel, sickbeard.SLACK_CHANNEL)[not channel]), ('as_user', not custom)] +
([], [('username', (bot_name, sickbeard.SLACK_BOT_NAME or 'SickGear')[not bot_name]),
('icon_url', (icon_url, sickbeard.SLACK_ICON_URL or self.sg_logo_url)[not icon_url])])[custom]),
[('text', self._body_only(title, body)),
('channel', self._choose(channel, sickbeard.SLACK_CHANNEL)), ('as_authed', not custom),
('token', self._choose(access_token, sickbeard.SLACK_ACCESS_TOKEN))]
+ ([], [('username', self._choose(bot_name, sickbeard.SLACK_BOT_NAME) or 'SickGear'),
('icon_url', self._choose(icon_url, sickbeard.SLACK_ICON_URL) or self._sg_logo_url)])[custom]),
json=True)
result = resp and resp.get('ok', resp.get('error')) or not (sickbeard.USE_SLACK or force)
result = resp and resp.get('ok') or 'response: "%s"' % (resp.get('error') or self._choose(
'bad oath access token?', None))
if True is not result:
logger.log(u'Slack failed sending message, response: "%s"' % result, logger.ERROR)
return result
self._log_error('Failed to send message, %s' % result)
def _notify_str(self, pre_text, post_text):
return self._notify('%s: %s' % (common.notifyStrings[pre_text].strip('#: '), post_text))
def test_notify(self, channel, as_user, bot_name, icon_url, access_token):
return self._notify('This is a test notification from SickGear',
channel, as_user, bot_name, icon_url, access_token, force=True)
def notify_snatch(self, ep_name):
return sickbeard.SLACK_NOTIFY_ONSNATCH and self._notify_str(common.NOTIFY_SNATCH, ep_name)
def notify_download(self, ep_name):
return sickbeard.SLACK_NOTIFY_ONDOWNLOAD and self._notify_str(common.NOTIFY_DOWNLOAD, ep_name)
def notify_subtitle_download(self, ep_name, lang):
return sickbeard.SLACK_NOTIFY_ONSUBTITLEDOWNLOAD and \
self._notify_str(common.NOTIFY_SUBTITLE_DOWNLOAD, '%s: %s' % (ep_name, lang))
def notify_git_update(self, new_version='??'):
return self._notify_str(common.NOTIFY_GIT_UPDATE_TEXT, new_version)
return self._choose(('Successful test notice sent. (Note: %s clients display icon once in a sequence)'
% self.name, 'Error sending notification, %s' % result)[True is not result], result)
notifier = SlackNotifier

View file

@ -16,75 +16,66 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import os
import subprocess
import sickbeard
from sickbeard import logger
# noinspection PyPep8Naming
from sickbeard import encodingKludge as ek
from sickbeard.exceptions import ex
from sickbeard.notifiers.generic import BaseNotifier
class synoIndexNotifier:
def notify_snatch(self, ep_name):
pass
def notify_download(self, ep_name):
pass
def notify_subtitle_download(self, ep_name, lang):
pass
def notify_git_update(self, new_version):
pass
# noinspection PyPep8Naming
class SynoIndexNotifier(BaseNotifier):
def moveFolder(self, old_path, new_path):
self.moveObject(old_path, new_path)
self._move_object(old_path, new_path)
def moveFile(self, old_file, new_file):
self.moveObject(old_file, new_file)
self._move_object(old_file, new_file)
def moveObject(self, old_path, new_path):
if sickbeard.USE_SYNOINDEX:
def _move_object(self, old_path, new_path):
if self.is_enabled():
synoindex_cmd = ['/usr/syno/bin/synoindex', '-N', ek.ek(os.path.abspath, new_path),
ek.ek(os.path.abspath, old_path)]
logger.log(u'Executing command ' + str(synoindex_cmd), logger.DEBUG)
logger.log(u'Absolute path to command: ' + ek.ek(os.path.abspath, synoindex_cmd[0]), logger.DEBUG)
self._log_debug(u'Executing command ' + str(synoindex_cmd))
self._log_debug(u'Absolute path to command: ' + ek.ek(os.path.abspath, synoindex_cmd[0]))
try:
p = subprocess.Popen(synoindex_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
cwd=sickbeard.PROG_DIR)
out, err = p.communicate() #@UnusedVariable
logger.log(u'Script result: ' + str(out), logger.DEBUG)
out, err = p.communicate()
self._log_debug(u'Script result: ' + str(out))
except OSError as e:
logger.log(u'Unable to run synoindex: ' + ex(e), logger.ERROR)
self._log_error(u'Unable to run synoindex: ' + ex(e))
def deleteFolder(self, cur_path):
self.makeObject('-D', cur_path)
self._make_object('-D', cur_path)
def addFolder(self, cur_path):
self.makeObject('-A', cur_path)
self._make_object('-A', cur_path)
def deleteFile(self, cur_file):
self.makeObject('-d', cur_file)
self._make_object('-d', cur_file)
def addFile(self, cur_file):
self.makeObject('-a', cur_file)
self._make_object('-a', cur_file)
def makeObject(self, cmd_arg, cur_path):
if sickbeard.USE_SYNOINDEX:
def _make_object(self, cmd_arg, cur_path):
if self.is_enabled():
synoindex_cmd = ['/usr/syno/bin/synoindex', cmd_arg, ek.ek(os.path.abspath, cur_path)]
logger.log(u'Executing command ' + str(synoindex_cmd), logger.DEBUG)
logger.log(u'Absolute path to command: ' + ek.ek(os.path.abspath, synoindex_cmd[0]), logger.DEBUG)
self._log_debug(u'Executing command ' + str(synoindex_cmd))
self._log_debug(u'Absolute path to command: ' + ek.ek(os.path.abspath, synoindex_cmd[0]))
try:
p = subprocess.Popen(synoindex_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
cwd=sickbeard.PROG_DIR)
out, err = p.communicate() #@UnusedVariable
logger.log(u'Script result: ' + str(out), logger.DEBUG)
out, err = p.communicate()
self._log_debug(u'Script result: ' + str(out))
except OSError as e:
logger.log(u'Unable to run synoindex: ' + ex(e), logger.ERROR)
self._log_error(u'Unable to run synoindex: ' + ex(e))
def update_library(self, ep_obj=None, **kwargs):
self.addFile(ep_obj.location)
notifier = synoIndexNotifier
notifier = SynoIndexNotifier

View file

@ -15,49 +15,30 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import os
import subprocess
import sickbeard
from sickbeard import logger
# noinspection PyPep8Naming
from sickbeard import encodingKludge as ek
from sickbeard.exceptions import ex
from sickbeard import common
from sickbeard.notifiers.generic import Notifier
class synologyNotifier:
def notify_snatch(self, ep_name):
if sickbeard.SYNOLOGYNOTIFIER_NOTIFY_ONSNATCH:
self._send_synologyNotifier(ep_name, common.notifyStrings[common.NOTIFY_SNATCH])
class SynologyNotifier(Notifier):
def notify_download(self, ep_name):
if sickbeard.SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD:
self._send_synologyNotifier(ep_name, common.notifyStrings[common.NOTIFY_DOWNLOAD])
def _notify(self, title, body, **kwargs):
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD:
self._send_synologyNotifier(ep_name + ': ' + lang, common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD])
def notify_git_update(self, new_version = '??'):
if sickbeard.USE_SYNOLOGYNOTIFIER:
update_text=common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title=common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._send_synologyNotifier(update_text + new_version, title)
def _send_synologyNotifier(self, message, title):
synodsmnotify_cmd = ['/usr/syno/bin/synodsmnotify', '@administrators', title, message]
logger.log(u'Executing command ' + str(synodsmnotify_cmd))
logger.log(u'Absolute path to command: ' + ek.ek(os.path.abspath, synodsmnotify_cmd[0]), logger.DEBUG)
synodsmnotify_cmd = ['/usr/syno/bin/synodsmnotify', '@administrators', title, body]
self._log(u'Executing command ' + str(synodsmnotify_cmd))
self._log_debug(u'Absolute path to command: ' + ek.ek(os.path.abspath, synodsmnotify_cmd[0]))
try:
p = subprocess.Popen(synodsmnotify_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
cwd=sickbeard.PROG_DIR)
out, err = p.communicate() #@UnusedVariable
logger.log(u'Script result: ' + str(out), logger.DEBUG)
out, err = p.communicate()
self._log_debug(u'Script result: ' + str(out))
except OSError as e:
logger.log(u'Unable to run synodsmnotify: ' + ex(e))
self._log(u'Unable to run synodsmnotify: ' + ex(e))
notifier = synologyNotifier
notifier = SynologyNotifier

View file

@ -16,52 +16,36 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import sickbeard
from sickbeard import logger
from lib.libtrakt import TraktAPI, exceptions
import os
import sickbeard
from sickbeard.notifiers.generic import BaseNotifier
class TraktNotifier:
def __init__(self):
pass
from lib.libtrakt import TraktAPI, exceptions
class TraktNotifier(BaseNotifier):
"""
A "notifier" for trakt.tv which keeps track of what has and hasn't been added to your library.
"""
def notify_snatch(self, ep_name):
pass
def update_library(self, ep_obj=None, **kwargs):
def notify_download(self, ep_name):
pass
self._update_collection(ep_obj)
def notify_subtitle_download(self, ep_name, lang):
pass
def notify_git_update(self, new_version):
pass
@staticmethod
def update_collection(ep_obj):
def _update_collection(self, ep_obj):
"""
Sends a request to trakt indicating that the given episode is part of our collection.
:param ep_obj: The TVEpisode object to add to trakt
"""
if sickbeard.USE_TRAKT and sickbeard.TRAKT_ACCOUNTS:
if sickbeard.TRAKT_ACCOUNTS:
# URL parameters
data = {
'shows': [
{
'title': ep_obj.show.name,
'year': ep_obj.show.startyear,
'ids': {},
}
]
}
data = dict(shows=[
dict(title=ep_obj.show.name, year=ep_obj.show.startyear, ids={})
])
from sickbeard.indexers.indexer_config import INDEXER_TVDB, INDEXER_TVRAGE, INDEXER_IMDB, INDEXER_TMDB, \
INDEXER_TRAKT
@ -75,12 +59,12 @@ class TraktNotifier:
indexer, indexerid = supported_indexer[ep_obj.show.indexer], ep_obj.show.indexerid
else:
for i in indexer_priorities:
if ep_obj.show.ids.get(i, {'id': 0}).get('id', 0) > 0:
if 0 < ep_obj.show.ids.get(i, {'id': 0}).get('id', 0):
indexer, indexerid = supported_indexer[i], ep_obj.show.ids[i]['id']
break
if indexer is None or indexerid is None:
logger.log('Missing trakt supported id, could not add to collection.', logger.WARNING)
if None is indexer or None is indexerid:
self._log_warning('Missing trakt supported id, could not add to collection')
return
data['shows'][0]['ids'][indexer] = indexerid
@ -101,13 +85,17 @@ class TraktNotifier:
warn, msg = False, ''
try:
resp = TraktAPI().trakt_request('sync/collection', data, send_oauth=tid)
if 'added' in resp and 'episodes' in resp['added'] and 0 < sickbeard.helpers.tryInt(resp['added']['episodes']):
if 'added' in resp and 'episodes' in resp['added'] \
and 0 < sickbeard.helpers.tryInt(resp['added']['episodes']):
msg = 'Added episode to'
elif 'updated' in resp and 'episodes' in resp['updated'] and 0 < sickbeard.helpers.tryInt(resp['updated']['episodes']):
elif 'updated' in resp and 'episodes' in resp['updated'] \
and 0 < sickbeard.helpers.tryInt(resp['updated']['episodes']):
msg = 'Updated episode in'
elif 'existing' in resp and 'episodes' in resp['existing'] and 0 < sickbeard.helpers.tryInt(resp['existing']['episodes']):
elif 'existing' in resp and 'episodes' in resp['existing'] \
and 0 < sickbeard.helpers.tryInt(resp['existing']['episodes']):
msg = 'Episode is already in'
elif 'not_found' in resp and 'episodes' in resp['not_found'] and 0 < sickbeard.helpers.tryInt(resp['not_found']['episodes']):
elif 'not_found' in resp and 'episodes' in resp['not_found'] \
and 0 < sickbeard.helpers.tryInt(resp['not_found']['episodes']):
msg = 'Episode not found on Trakt, not adding to'
else:
warn, msg = True, 'Could not add episode to'
@ -115,14 +103,9 @@ class TraktNotifier:
warn, msg = True, 'Error adding episode to'
msg = 'Trakt: %s your %s collection' % (msg, sickbeard.TRAKT_ACCOUNTS[tid].name)
if not warn:
logger.log(msg)
self._log(msg)
else:
logger.log(msg, logger.WARNING)
@staticmethod
def _use_me():
return sickbeard.USE_TRAKT
self._log_warning(msg)
notifier = TraktNotifier

View file

@ -16,22 +16,18 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
from urlparse import parse_qsl
import sickbeard
from sickbeard import logger, common
from sickbeard.exceptions import ex
# parse_qsl moved to urlparse module in v2.6
try:
from urlparse import parse_qsl #@UnusedImport
except:
from cgi import parse_qsl #@Reimport
from sickbeard.notifiers.generic import Notifier
import lib.oauth2 as oauth
import lib.pythontwitter as twitter
class TwitterNotifier:
class TwitterNotifier(Notifier):
consumer_key = 'vHHtcB6WzpWDG6KYlBMr8g'
consumer_secret = 'zMqq5CB3f8cWKiRO2KzWPTlBanYmV0VYxSXZ0Pxds0E'
@ -40,39 +36,19 @@ class TwitterNotifier:
AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize'
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
def notify_snatch(self, ep_name):
if sickbeard.TWITTER_NOTIFY_ONSNATCH:
self._notifyTwitter(common.notifyStrings[common.NOTIFY_SNATCH] + ': ' + ep_name)
def get_authorization(self):
def notify_download(self, ep_name):
if sickbeard.TWITTER_NOTIFY_ONDOWNLOAD:
self._notifyTwitter(common.notifyStrings[common.NOTIFY_DOWNLOAD] + ': ' + ep_name)
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.TWITTER_NOTIFY_ONSUBTITLEDOWNLOAD:
self._notifyTwitter(common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD] + ' ' + ep_name + ': ' + lang)
def notify_git_update(self, new_version = '??'):
if sickbeard.USE_TWITTER:
update_text=common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title=common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._notifyTwitter(title + ' - ' + update_text + new_version)
def test_notify(self):
return self._notifyTwitter('This is a test notification from SickGear', force=True)
def _get_authorization(self):
signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable
# noinspection PyUnusedLocal
signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1()
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
oauth_client = oauth.Client(oauth_consumer)
logger.log('Requesting temp token from Twitter', logger.DEBUG)
self._log_debug('Requesting temp token from Twitter')
resp, content = oauth_client.request(self.REQUEST_TOKEN_URL, 'GET')
if resp['status'] != '200':
logger.log('Invalid response from Twitter requesting temp token: %s' % resp['status'], logger.ERROR)
if '200' != resp['status']:
self._log_error('Invalid response from Twitter requesting temp token: %s' % resp['status'])
else:
request_token = dict(parse_qsl(content))
@ -81,67 +57,62 @@ class TwitterNotifier:
return self.AUTHORIZATION_URL + '?oauth_token=' + request_token['oauth_token']
def _get_credentials(self, key):
request_token = {}
request_token['oauth_token'] = sickbeard.TWITTER_USERNAME
request_token['oauth_token_secret'] = sickbeard.TWITTER_PASSWORD
request_token['oauth_callback_confirmed'] = 'true'
def get_credentials(self, key):
request_token = dict(oauth_token=sickbeard.TWITTER_USERNAME, oauth_token_secret=sickbeard.TWITTER_PASSWORD,
oauth_callback_confirmed='true')
token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret'])
token.set_verifier(key)
logger.log('Generating and signing request for an access token using key ' + key, logger.DEBUG)
self._log_debug('Generating and signing request for an access token using key ' + key)
signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable
# noinspection PyUnusedLocal
signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1()
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
logger.log('oauth_consumer: ' + str(oauth_consumer), logger.DEBUG)
self._log_debug('oauth_consumer: ' + str(oauth_consumer))
oauth_client = oauth.Client(oauth_consumer, token)
logger.log('oauth_client: ' + str(oauth_client), logger.DEBUG)
self._log_debug('oauth_client: ' + str(oauth_client))
resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, method='POST', body='oauth_verifier=%s' % key)
logger.log('resp, content: ' + str(resp) + ',' + str(content), logger.DEBUG)
self._log_debug('resp, content: ' + str(resp) + ',' + str(content))
access_token = dict(parse_qsl(content))
logger.log('access_token: ' + str(access_token), logger.DEBUG)
self._log_debug('access_token: ' + str(access_token))
logger.log('resp[status] = ' + str(resp['status']), logger.DEBUG)
if resp['status'] != '200':
logger.log('The request for a token with did not succeed: ' + str(resp['status']), logger.ERROR)
return False
self._log_debug('resp[status] = ' + str(resp['status']))
if '200' != resp['status']:
self._log_error('The request for a token with did not succeed: ' + str(resp['status']))
result = False
else:
logger.log('Your Twitter Access Token key: %s' % access_token['oauth_token'], logger.DEBUG)
logger.log('Access Token secret: %s' % access_token['oauth_token_secret'], logger.DEBUG)
self._log_debug('Your Twitter Access Token key: %s' % access_token['oauth_token'])
self._log_debug('Access Token secret: %s' % access_token['oauth_token_secret'])
sickbeard.TWITTER_USERNAME = access_token['oauth_token']
sickbeard.TWITTER_PASSWORD = access_token['oauth_token_secret']
return True
result = True
message = ('Key verification successful', 'Unable to verify key')[not result]
logger.log(u'%s result: %s' % (self.name, message))
return self._choose(message, result)
def _send_tweet(self, message=None):
def _notify(self, title, body, **kwargs):
# don't use title with updates or testing, as only one str is used
body = '::'.join(([], [sickbeard.TWITTER_PREFIX])[bool(sickbeard.TWITTER_PREFIX)]
+ [body.replace('#: ', ': ') if 'SickGear' in title else body])
username = self.consumer_key
password = self.consumer_secret
access_token_key = sickbeard.TWITTER_USERNAME
access_token_secret = sickbeard.TWITTER_PASSWORD
logger.log(u'Sending tweet: ' + message, logger.DEBUG)
api = twitter.Api(username, password, access_token_key, access_token_secret)
try:
api.PostUpdate(message.encode('utf8'))
api.PostUpdate(body.encode('utf8'))
except Exception as e:
logger.log(u'Error Sending Tweet: ' + ex(e), logger.ERROR)
self._log_error(u'Error sending Tweet: ' + ex(e))
return False
return True
def _notifyTwitter(self, message='', force=False):
prefix = sickbeard.TWITTER_PREFIX
if not sickbeard.USE_TWITTER and not force:
return False
return self._send_tweet(prefix + ': ' + message)
notifier = TwitterNotifier

View file

@ -16,32 +16,30 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import urllib
import urllib2
import socket
import base64
import time
import sickbeard
from sickbeard import logger
from sickbeard import common
from sickbeard.exceptions import ex
from sickbeard.encodingKludge import fixStupidEncodings
try:
import xml.etree.cElementTree as etree
except ImportError:
import xml.etree.ElementTree as etree
try:
import json
except ImportError:
from lib import simplejson as json
import socket
import time
import urllib
import urllib2
import xml.etree.cElementTree as XmlEtree
import sickbeard
from sickbeard.exceptions import ex
from sickbeard.encodingKludge import fixStupidEncodings
from sickbeard.notifiers.generic import Notifier
class XBMCNotifier:
sb_logo_url = 'https://raw.githubusercontent.com/SickGear/SickGear/master/gui/slick/images/ico/apple-touch-icon-72x72.png'
class XBMCNotifier(Notifier):
def __init__(self):
super(XBMCNotifier, self).__init__()
self.sg_logo_file = 'apple-touch-icon-72x72.png'
def _get_xbmc_version(self, host, username, password):
"""Returns XBMC JSON-RPC API version (odd # = dev, even # = stable)
@ -70,12 +68,12 @@ class XBMCNotifier:
"""
# since we need to maintain python 2.5 compatability we can not pass a timeout delay to urllib2 directly (python 2.6+)
# override socket timeout to reduce delay for this call alone
# since we need to maintain python 2.5 compatability we can not pass a timeout delay
# to urllib2 directly (python 2.6+) override socket timeout to reduce delay for this call alone
socket.setdefaulttimeout(10)
checkCommand = '{"jsonrpc":"2.0","method":"JSONRPC.Version","id":1}'
result = self._send_to_xbmc_json(checkCommand, host, username, password)
check_command = '{"jsonrpc":"2.0","method":"JSONRPC.Version","id":1}'
result = self._send_to_xbmc_json(check_command, host, username, password)
# revert back to default socket timeout
socket.setdefaulttimeout(sickbeard.SOCKET_TIMEOUT)
@ -84,113 +82,49 @@ class XBMCNotifier:
return result['result']['version']
else:
# fallback to legacy HTTPAPI method
testCommand = {'command': 'Help'}
request = self._send_to_xbmc(testCommand, host, username, password)
test_command = {'command': 'Help'}
request = self._send_to_xbmc(test_command, host, username, password)
if request:
# return a fake version number, so it uses the legacy method
return 1
else:
return False
def _notify_xbmc(self, message, title='SickGear', host=None, username=None, password=None, force=False):
"""Internal wrapper for the notify_snatch and notify_download functions
Detects JSON-RPC version then branches the logic for either the JSON-RPC or legacy HTTP API methods.
Args:
message: Message body of the notice to send
title: Title of the notice to send
host: XBMC webserver host:port
username: XBMC webserver username
password: XBMC webserver password
force: Used for the Test method to override config saftey checks
Returns:
Returns a list results in the format of host:ip:result
The result will either be 'OK' or False, this is used to be parsed by the calling function.
"""
# fill in omitted parameters
if not host:
host = sickbeard.XBMC_HOST
if not username:
username = sickbeard.XBMC_USERNAME
if not password:
password = sickbeard.XBMC_PASSWORD
# suppress notifications if the notifier is disabled but the notify options are checked
if not sickbeard.USE_XBMC and not force:
logger.log('Notification for XBMC not enabled, skipping this notification', logger.DEBUG)
return False
result = ''
for curHost in [x.strip() for x in host.split(',')]:
logger.log(u'Sending XBMC notification to "' + curHost + '" - ' + message, logger.MESSAGE)
xbmcapi = self._get_xbmc_version(curHost, username, password)
if xbmcapi:
if (xbmcapi <= 4):
logger.log(u'Detected XBMC version <= 11, using XBMC HTTP API', logger.DEBUG)
command = {'command': 'ExecBuiltIn',
'parameter': 'Notification(' + title.encode('utf-8') + ',' + message.encode(
'utf-8') + ')'}
notifyResult = self._send_to_xbmc(command, curHost, username, password)
if notifyResult:
result += curHost + ':' + str(notifyResult)
else:
logger.log(u'Detected XBMC version >= 12, using XBMC JSON API', logger.DEBUG)
command = '{"jsonrpc":"2.0","method":"GUI.ShowNotification","params":{"title":"%s","message":"%s", "image": "%s"},"id":1}' % (
title.encode('utf-8'), message.encode('utf-8'), self.sb_logo_url)
notifyResult = self._send_to_xbmc_json(command, curHost, username, password)
if notifyResult.get('result'):
result += curHost + ':' + notifyResult['result'].decode(sickbeard.SYS_ENCODING)
else:
if sickbeard.XBMC_ALWAYS_ON or force:
logger.log(
u'Failed to detect XBMC version for "' + curHost + '", check configuration and try again.',
logger.ERROR)
result += curHost + ':False'
return result
def _send_update_library(self, host, showName=None):
def _send_update_library(self, host, show_name=None):
"""Internal wrapper for the update library function to branch the logic for JSON-RPC or legacy HTTP API
Checks the XBMC API version to branch the logic to call either the legacy HTTP API or the newer JSON-RPC over HTTP methods.
Checks the XBMC API version to branch the logic
to call either the legacy HTTP API or the newer JSON-RPC over HTTP methods.
Args:
host: XBMC webserver host:port
showName: Name of a TV show to specifically target the library update for
show_name: Name of a TV show to specifically target the library update for
Returns:
Returns True or False, if the update was successful
"""
logger.log(u'Sending request to update library for XBMC host: "' + host + '"', logger.MESSAGE)
self._log(u'Sending request to update library for host: "%s"' % host)
xbmcapi = self._get_xbmc_version(host, sickbeard.XBMC_USERNAME, sickbeard.XBMC_PASSWORD)
if xbmcapi:
if (xbmcapi <= 4):
if 4 >= xbmcapi:
# try to update for just the show, if it fails, do full update if enabled
if not self._update_library(host, showName) and sickbeard.XBMC_UPDATE_FULL:
logger.log(u'Single show update failed, falling back to full update', logger.WARNING)
return self._update_library(host)
if not self._update_library_http(host, show_name) and sickbeard.XBMC_UPDATE_FULL:
self._log_warning(u'Single show update failed, falling back to full update')
return self._update_library_http(host)
else:
return True
else:
# try to update for just the show, if it fails, do full update if enabled
if not self._update_library_json(host, showName) and sickbeard.XBMC_UPDATE_FULL:
logger.log(u'Single show update failed, falling back to full update', logger.WARNING)
if not self._update_library_json(host, show_name) and sickbeard.XBMC_UPDATE_FULL:
self._log_warning(u'Single show update failed, falling back to full update')
return self._update_library_json(host)
else:
return True
else:
logger.log(u'Failed to detect XBMC version for "' + host + '", check configuration and try again.',
logger.DEBUG)
return False
self._log_debug(u'Failed to detect version for "%s", check configuration and try again' % host)
return False
# #############################################################################
@ -210,23 +144,19 @@ class XBMCNotifier:
Returns response.result for successful commands or False if there was an error
"""
# fill in omitted parameters
if not username:
username = sickbeard.XBMC_USERNAME
if not password:
password = sickbeard.XBMC_PASSWORD
if not host:
logger.log(u'No XBMC host passed, aborting update', logger.DEBUG)
self._log_debug(u'No host passed, aborting update')
return False
username = self._choose(username, sickbeard.XBMC_USERNAME)
password = self._choose(password, sickbeard.XBMC_PASSWORD)
for key in command:
if type(command[key]) == unicode:
command[key] = command[key].encode('utf-8')
enc_command = urllib.urlencode(command)
logger.log(u'XBMC encoded API command: ' + enc_command, logger.DEBUG)
self._log_debug(u'Encoded API command: ' + enc_command)
url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, enc_command)
try:
@ -236,23 +166,22 @@ class XBMCNotifier:
base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
authheader = 'Basic %s' % base64string
req.add_header('Authorization', authheader)
logger.log(u'Contacting XBMC (with auth header) via url: ' + fixStupidEncodings(url), logger.DEBUG)
self._log_debug(u'Contacting (with auth header) via url: ' + fixStupidEncodings(url))
else:
logger.log(u'Contacting XBMC via url: ' + fixStupidEncodings(url), logger.DEBUG)
self._log_debug(u'Contacting via url: ' + fixStupidEncodings(url))
response = urllib2.urlopen(req)
result = response.read().decode(sickbeard.SYS_ENCODING)
response.close()
logger.log(u'XBMC HTTP response: ' + result.replace('\n', ''), logger.DEBUG)
self._log_debug(u'HTTP response: ' + result.replace('\n', ''))
return result
except (urllib2.URLError, IOError) as e:
logger.log(u"Warning: Couldn't contact XBMC HTTP at " + fixStupidEncodings(url) + ' ' + ex(e),
logger.WARNING)
self._log_warning(u'Couldn\'t contact HTTP at %s %s' % (fixStupidEncodings(url), ex(e)))
return False
def _update_library(self, host=None, showName=None):
def _update_library_http(self, host=None, show_name=None):
"""Handles updating XBMC host via HTTP API
Attempts to update the XBMC video library for a specific tv show if passed,
@ -260,7 +189,7 @@ class XBMCNotifier:
Args:
host: XBMC webserver host:port
showName: Name of a TV show to specifically target the library update for
show_name: Name of a TV show to specifically target the library update for
Returns:
Returns True or False
@ -268,73 +197,76 @@ class XBMCNotifier:
"""
if not host:
logger.log(u'No XBMC host passed, aborting update', logger.DEBUG)
self._log_debug(u'No host passed, aborting update')
return False
logger.log(u'Updating XMBC library via HTTP method for host: ' + host, logger.DEBUG)
self._log_debug(u'Updating XMBC library via HTTP method for host: ' + host)
# if we're doing per-show
if showName:
logger.log(u'Updating library in XBMC via HTTP method for show ' + showName, logger.DEBUG)
if show_name:
self._log_debug(u'Updating library via HTTP method for show ' + show_name)
pathSql = 'select path.strPath from path, tvshow, tvshowlinkpath where ' \
'tvshow.c00 = "%s" and tvshowlinkpath.idShow = tvshow.idShow ' \
'and tvshowlinkpath.idPath = path.idPath' % (showName)
# noinspection SqlResolve
path_sql = 'select path.strPath' \
' from path, tvshow, tvshowlinkpath' \
' where tvshow.c00 = "%s"' \
' and tvshowlinkpath.idShow = tvshow.idShow' \
' and tvshowlinkpath.idPath = path.idPath' % show_name
# use this to get xml back for the path lookups
xmlCommand = {
'command': 'SetResponseFormat(webheader;false;webfooter;false;header;<xml>;footer;</xml>;opentag;<tag>;closetag;</tag>;closefinaltag;false)'}
xml_command = dict(command='SetResponseFormat(webheader;false;webfooter;false;header;<xml>;footer;</xml>;'
'opentag;<tag>;closetag;</tag>;closefinaltag;false)')
# sql used to grab path(s)
sqlCommand = {'command': 'QueryVideoDatabase(%s)' % (pathSql)}
sql_command = dict(command='QueryVideoDatabase(%s)' % path_sql)
# set output back to default
resetCommand = {'command': 'SetResponseFormat()'}
reset_command = dict(command='SetResponseFormat()')
# set xml response format, if this fails then don't bother with the rest
request = self._send_to_xbmc(xmlCommand, host)
request = self._send_to_xbmc(xml_command, host)
if not request:
return False
sqlXML = self._send_to_xbmc(sqlCommand, host)
request = self._send_to_xbmc(resetCommand, host)
sql_xml = self._send_to_xbmc(sql_command, host)
self._send_to_xbmc(reset_command, host)
if not sqlXML:
logger.log(u'Invalid response for ' + showName + ' on ' + host, logger.DEBUG)
if not sql_xml:
self._log_debug(u'Invalid response for ' + show_name + ' on ' + host)
return False
encSqlXML = urllib.quote(sqlXML, ':\\/<>')
enc_sql_xml = urllib.quote(sql_xml, ':\\/<>')
try:
et = etree.fromstring(encSqlXML)
et = XmlEtree.fromstring(enc_sql_xml)
except SyntaxError as e:
logger.log(u'Unable to parse XML returned from XBMC: ' + ex(e), logger.ERROR)
self._log_error(u'Unable to parse XML response: ' + ex(e))
return False
paths = et.findall('.//field')
if not paths:
logger.log(u'No valid paths found for ' + showName + ' on ' + host, logger.DEBUG)
self._log_debug(u'No valid paths found for ' + show_name + ' on ' + host)
return False
for path in paths:
# we do not need it double-encoded, gawd this is dumb
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._send_to_xbmc(updateCommand, host)
un_enc_path = urllib.unquote(path.text).decode(sickbeard.SYS_ENCODING)
self._log_debug(u'Updating ' + show_name + ' on ' + host + ' at ' + un_enc_path)
update_command = dict(command='ExecBuiltIn', parameter='XBMC.updatelibrary(video, %s)' % un_enc_path)
request = self._send_to_xbmc(update_command, host)
if not request:
logger.log(u'Update of show directory failed on ' + showName + ' on ' + host + ' at ' + unEncPath,
logger.ERROR)
self._log_error(u'Update of show directory failed on ' + show_name
+ ' on ' + host + ' at ' + un_enc_path)
return False
# sleep for a few seconds just to be sure xbmc has a chance to finish each directory
if len(paths) > 1:
time.sleep(5)
# do a full update if requested
else:
logger.log(u'Doing Full Library XBMC update on host: ' + host, logger.MESSAGE)
updateCommand = {'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'}
request = self._send_to_xbmc(updateCommand, host)
self._log(u'Doing full library update on host: ' + host)
update_command = {'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'}
request = self._send_to_xbmc(update_command, host)
if not request:
logger.log(u'XBMC Full Library update failed on: ' + host, logger.ERROR)
self._log_error(u'Full Library update failed on: ' + host)
return False
return True
@ -356,21 +288,17 @@ class XBMCNotifier:
Returns response.result for successful commands or False if there was an error
"""
# fill in omitted parameters
if not username:
username = sickbeard.XBMC_USERNAME
if not password:
password = sickbeard.XBMC_PASSWORD
if not host:
logger.log(u'No XBMC host passed, aborting update', logger.DEBUG)
self._log_debug(u'No host passed, aborting update')
return False
command = command.encode('utf-8')
logger.log(u'XBMC JSON command: ' + command, logger.DEBUG)
username = self._choose(username, sickbeard.XBMC_USERNAME)
password = self._choose(password, sickbeard.XBMC_PASSWORD)
url = 'http://%s/jsonrpc' % (host)
command = command.encode('utf-8')
self._log_debug(u'JSON command: ' + command)
url = 'http://%s/jsonrpc' % host
try:
req = urllib2.Request(url, command)
req.add_header('Content-type', 'application/json')
@ -379,33 +307,31 @@ class XBMCNotifier:
base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
authheader = 'Basic %s' % base64string
req.add_header('Authorization', authheader)
logger.log(u'Contacting XBMC (with auth header) via url: ' + fixStupidEncodings(url), logger.DEBUG)
self._log_debug(u'Contacting (with auth header) via url: ' + fixStupidEncodings(url))
else:
logger.log(u'Contacting XBMC via url: ' + fixStupidEncodings(url), logger.DEBUG)
self._log_debug(u'Contacting via url: ' + fixStupidEncodings(url))
try:
response = urllib2.urlopen(req)
except urllib2.URLError as e:
logger.log(u'Error while trying to retrieve XBMC API version for ' + host + ': ' + ex(e),
logger.WARNING)
self._log_warning(u'Error while trying to retrieve API version for "%s": %s' % (host, ex(e)))
return False
# parse the json result
try:
result = json.load(response)
response.close()
logger.log(u'XBMC JSON response: ' + str(result), logger.DEBUG)
self._log_debug(u'JSON response: ' + str(result))
return result # need to return response for parsing
except ValueError as e:
logger.log(u'Unable to decode JSON: ' + response, logger.WARNING)
except ValueError:
self._log_warning(u'Unable to decode JSON: ' + response)
return False
except IOError as e:
logger.log(u"Warning: Couldn't contact XBMC JSON API at " + fixStupidEncodings(url) + ' ' + ex(e),
logger.WARNING)
self._log_warning(u'Couldn\'t contact JSON API at ' + fixStupidEncodings(url) + ' ' + ex(e))
return False
def _update_library_json(self, host=None, showName=None):
def _update_library_json(self, host=None, show_name=None):
"""Handles updating XBMC host via HTTP JSON-RPC
Attempts to update the XBMC video library for a specific tv show if passed,
@ -413,7 +339,7 @@ class XBMCNotifier:
Args:
host: XBMC webserver host:port
showName: Name of a TV show to specifically target the library update for
show_name: Name of a TV show to specifically target the library update for
Returns:
Returns True or False
@ -421,28 +347,28 @@ class XBMCNotifier:
"""
if not host:
logger.log(u'No XBMC host passed, aborting update', logger.DEBUG)
self._log_debug(u'No host passed, aborting update')
return False
logger.log(u'Updating XMBC library via JSON method for host: ' + host, logger.MESSAGE)
self._log(u'Updating XMBC library via JSON method for host: ' + host)
# if we're doing per-show
if showName:
if show_name:
tvshowid = -1
logger.log(u'Updating library in XBMC via JSON method for show ' + showName, logger.DEBUG)
self._log_debug(u'Updating library via JSON method for show ' + show_name)
# get tvshowid by showName
showsCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.GetTVShows","id":1}'
showsResponse = self._send_to_xbmc_json(showsCommand, host)
shows_command = '{"jsonrpc":"2.0","method":"VideoLibrary.GetTVShows","id":1}'
shows_response = self._send_to_xbmc_json(shows_command, host)
if showsResponse and 'result' in showsResponse and 'tvshows' in showsResponse['result']:
shows = showsResponse['result']['tvshows']
if shows_response and 'result' in shows_response and 'tvshows' in shows_response['result']:
shows = shows_response['result']['tvshows']
else:
logger.log(u'XBMC: No tvshows in XBMC TV show list', logger.DEBUG)
self._log_debug(u'No tvshows in TV show list')
return False
for show in shows:
if (show['label'] == showName):
if show['label'] == show_name:
tvshowid = show['tvshowid']
break # exit out of loop otherwise the label and showname will not match up
@ -450,121 +376,145 @@ class XBMCNotifier:
del shows
# we didn't find the show (exact match), thus revert to just doing a full update if enabled
if (tvshowid == -1):
logger.log(u'Exact show name not matched in XBMC TV show list', logger.DEBUG)
if -1 == tvshowid:
self._log_debug(u'Exact show name not matched in TV show list')
return False
# lookup tv-show path
pathCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.GetTVShowDetails","params":{"tvshowid":%d, "properties": ["file"]},"id":1}' % (
tvshowid)
pathResponse = self._send_to_xbmc_json(pathCommand, host)
path_command = '{"jsonrpc":"2.0","method":"VideoLibrary.GetTVShowDetails",' \
'"params":{"tvshowid":%d, "properties": ["file"]},"id":1}' % tvshowid
path_response = self._send_to_xbmc_json(path_command, host)
path = pathResponse['result']['tvshowdetails']['file']
logger.log(u'Received Show: ' + showName + ' with ID: ' + str(tvshowid) + ' Path: ' + path,
logger.DEBUG)
path = path_response['result']['tvshowdetails']['file']
self._log_debug(u'Received Show: ' + show_name + ' with ID: ' + str(tvshowid) + ' Path: ' + path)
if (len(path) < 1):
logger.log(u'No valid path found for ' + showName + ' with ID: ' + str(tvshowid) + ' on ' + host,
logger.WARNING)
if 1 > len(path):
self._log_warning(u'No valid path found for ' + show_name + ' with ID: '
+ str(tvshowid) + ' on ' + host)
return False
logger.log(u'XBMC Updating ' + showName + ' on ' + host + ' at ' + path, logger.DEBUG)
updateCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","params":{"directory":%s},"id":1}' % (
self._log_debug(u'Updating ' + show_name + ' on ' + host + ' at ' + path)
update_command = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","params":{"directory":%s},"id":1}' % (
json.dumps(path))
request = self._send_to_xbmc_json(updateCommand, host)
request = self._send_to_xbmc_json(update_command, host)
if not request:
logger.log(u'Update of show directory failed on ' + showName + ' on ' + host + ' at ' + path,
logger.ERROR)
self._log_error(u'Update of show directory failed on ' + show_name + ' on ' + host + ' at ' + path)
return False
# catch if there was an error in the returned request
# noinspection PyTypeChecker
for r in request:
if 'error' in r:
logger.log(
u'Error while attempting to update show directory for ' + showName + ' on ' + host + ' at ' + path,
logger.ERROR)
self._log_error(
u'Error while attempting to update show directory for ' + show_name
+ ' on ' + host + ' at ' + path)
return False
# do a full update if requested
else:
logger.log(u'Doing Full Library XBMC update on host: ' + host, logger.MESSAGE)
updateCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","id":1}'
request = self._send_to_xbmc_json(updateCommand, host, sickbeard.XBMC_USERNAME, sickbeard.XBMC_PASSWORD)
self._log(u'Doing Full Library update on host: ' + host)
update_command = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","id":1}'
request = self._send_to_xbmc_json(update_command, host, sickbeard.XBMC_USERNAME, sickbeard.XBMC_PASSWORD)
if not request:
logger.log(u'XBMC Full Library update failed on: ' + host, logger.ERROR)
self._log_error(u'Full Library update failed on: ' + host)
return False
return True
##############################################################################
# Public functions which will call the JSON or Legacy HTTP API methods
##############################################################################
def _notify(self, title, body, hosts=None, username=None, password=None, **kwargs):
"""Internal wrapper for the notify_snatch and notify_download functions
def notify_snatch(self, ep_name):
if sickbeard.XBMC_NOTIFY_ONSNATCH:
self._notify_xbmc(ep_name, common.notifyStrings[common.NOTIFY_SNATCH])
Detects JSON-RPC version then branches the logic for either the JSON-RPC or legacy HTTP API methods.
def notify_download(self, ep_name):
if sickbeard.XBMC_NOTIFY_ONDOWNLOAD:
self._notify_xbmc(ep_name, common.notifyStrings[common.NOTIFY_DOWNLOAD])
Args:
title: Title of the notice to send
body: Message body of the notice to send
hosts: XBMC webserver host:port
username: XBMC webserver username
password: XBMC webserver password
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.XBMC_NOTIFY_ONSUBTITLEDOWNLOAD:
self._notify_xbmc(ep_name + ': ' + lang, common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD])
def notify_git_update(self, new_version = '??'):
if sickbeard.USE_XBMC:
update_text=common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title=common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._notify_xbmc(update_text + new_version, title)
Returns:
Returns a list results in the format of host:ip:result
The result will either be 'OK' or False, this is used to be parsed by the calling function.
def test_notify(self, host, username, password):
return self._notify_xbmc('Testing XBMC notifications from SickGear', 'Test Notification', host, username,
password, force=True)
"""
hosts = self._choose(hosts, sickbeard.XBMC_HOST)
username = self._choose(username, sickbeard.XBMC_USERNAME)
password = self._choose(password, sickbeard.XBMC_PASSWORD)
def update_library(self, showName=None):
success = False
result = []
for cur_host in [x.strip() for x in hosts.split(',')]:
cur_host = urllib.unquote_plus(cur_host)
self._log(u'Sending notification to "%s"' % cur_host)
xbmcapi = self._get_xbmc_version(cur_host, username, password)
if xbmcapi:
if 4 >= xbmcapi:
self._log_debug(u'Detected version <= 11, using HTTP API')
command = dict(command='ExecBuiltIn',
parameter='Notification(' + title.encode('utf-8') + ',' + body.encode('utf-8') + ')')
notify_result = self._send_to_xbmc(command, cur_host, username, password)
if notify_result:
result += [cur_host + ':' + str(notify_result)]
success |= 'OK' in notify_result or success
else:
self._log_debug(u'Detected version >= 12, using JSON API')
command = '{"jsonrpc":"2.0","method":"GUI.ShowNotification",' \
'"params":{"title":"%s","message":"%s", "image": "%s"},"id":1}' % \
(title.encode('utf-8'), body.encode('utf-8'), self._sg_logo_url)
notify_result = self._send_to_xbmc_json(command, cur_host, username, password)
if notify_result.get('result'):
result += [cur_host + ':' + notify_result['result'].decode(sickbeard.SYS_ENCODING)]
success |= 'OK' in notify_result or success
else:
if sickbeard.XBMC_ALWAYS_ON or self._testing:
self._log_error(u'Failed to detect version for "%s", check configuration and try again' % cur_host)
result += [cur_host + ':No response']
success = False
return self._choose(('Success, all hosts tested', '<br />\n'.join(result))[not bool(success)], bool(success))
def update_library(self, show_name=None, **kwargs):
"""Public wrapper for the update library functions to branch the logic for JSON-RPC or legacy HTTP API
Checks the XBMC API version to branch the logic to call either the legacy HTTP API or the newer JSON-RPC over HTTP methods.
Do the ability of accepting a list of hosts deliminated by comma, only one host is updated, the first to respond with success.
Checks the XBMC API version to branch the logic to call either the legacy HTTP API
or the newer JSON-RPC over HTTP methods.
Do the ability of accepting a list of hosts delimited by comma, only one host is updated,
the first to respond with success.
This is a workaround for SQL backend users as updating multiple clients causes duplicate entries.
Future plan is to revist how we store the host/ip/username/pw/options so that it may be more flexible.
Args:
showName: Name of a TV show to specifically target the library update for
show_name: Name of a TV show to specifically target the library update for
Returns:
Returns True or False
"""
if not sickbeard.XBMC_HOST:
self._log_debug(u'No hosts specified, check your settings')
return False
if sickbeard.USE_XBMC and sickbeard.XBMC_UPDATE_LIBRARY:
if not sickbeard.XBMC_HOST:
logger.log(u'No XBMC hosts specified, check your settings', logger.DEBUG)
return False
# either update each host, or only attempt to update until one successful result
result = 0
for host in [x.strip() for x in sickbeard.XBMC_HOST.split(',')]:
if self._send_update_library(host, showName):
if sickbeard.XBMC_UPDATE_ONLYFIRST:
logger.log(u'Successfully updated "' + host + '", stopped sending update library commands.',
logger.DEBUG)
return True
else:
if sickbeard.XBMC_ALWAYS_ON:
logger.log(
u'Failed to detect XBMC version for "' + host + '", check configuration and try again.',
logger.ERROR)
result = result + 1
# needed for the 'update xbmc' submenu command
# as it only cares of the final result vs the individual ones
if result == 0:
return True
# either update each host, or only attempt to update until one successful result
result = 0
for host in [x.strip() for x in sickbeard.XBMC_HOST.split(',')]:
if self._send_update_library(host, show_name):
if sickbeard.XBMC_UPDATE_ONLYFIRST:
self._log_debug(u'Successfully updated "%s", stopped sending update library commands' % host)
return True
else:
return False
if sickbeard.XBMC_ALWAYS_ON:
self._log_error(u'Failed to detect version for "%s", check configuration and try again' % host)
result = result + 1
# needed for the 'update xbmc' submenu command
# as it only cares of the final result vs the individual ones
if not 0 != result:
return False
return True
notifier = XBMCNotifier

View file

@ -247,7 +247,7 @@ class PostProcessor(object):
self._log(u'Deleted file ' + cur_file, logger.DEBUG)
# do the library update for synoindex
notifiers.synoindex_notifier.deleteFile(cur_file)
notifiers.NotifierFactory().get('SYNOINDEX').deleteFile(cur_file)
def _combined_file_operation(self, file_path, new_path, new_base_name, associated_files=False, action=None,
subtitles=False, action_tmpl=None):
@ -900,7 +900,7 @@ class PostProcessor(object):
try:
ek.ek(os.mkdir, ep_obj.show.location)
# do the library update for synoindex
notifiers.synoindex_notifier.addFolder(ep_obj.show.location)
notifiers.NotifierFactory().get('SYNOINDEX').addFolder(ep_obj.show.location)
except (OSError, IOError):
raise exceptions.PostProcessingFailed(u'Unable to create show directory: ' + ep_obj.show.location)
@ -1054,29 +1054,8 @@ class PostProcessor(object):
# send notifications
notifiers.notify_download(ep_obj._format_pattern('%SN - %Sx%0E - %EN - %QN'))
# do the library update for Emby
notifiers.emby_notifier.update_library(ep_obj.show)
# do the library update for Kodi
notifiers.kodi_notifier.update_library(ep_obj.show.name)
# do the library update for XBMC
notifiers.xbmc_notifier.update_library(ep_obj.show.name)
# do the library update for Plex
notifiers.plex_notifier.update_library(ep_obj)
# do the library update for NMJ
# nmj_notifier kicks off its library update when the notify_download is issued (inside notifiers)
# do the library update for Synology Indexer
notifiers.synoindex_notifier.addFile(ep_obj.location)
# do the library update for pyTivo
notifiers.pytivo_notifier.update_library(ep_obj)
# do the library update for Trakt
notifiers.trakt_notifier.update_collection(ep_obj)
# trigger library updates
notifiers.notify_update_library(ep_obj=ep_obj)
self._run_extra_scripts(ep_obj)

View file

@ -410,8 +410,7 @@ class GitUpdateManager(UpdateManager):
self._find_installed_version()
# Notify update successful
if sickbeard.NOTIFY_ON_UPDATE:
notifiers.notify_git_update(sickbeard.CUR_COMMIT_HASH if sickbeard.CUR_COMMIT_HASH else "")
notifiers.notify_git_update(sickbeard.CUR_COMMIT_HASH if sickbeard.CUR_COMMIT_HASH else "")
return True
return False

View file

@ -625,10 +625,10 @@ class Home(MainHandler):
def HomeMenu(self):
return [
{'title': 'Process Media', 'path': 'home/postprocess/'},
{'title': 'Update Emby', 'path': 'home/updateEMBY/', 'requires': self.haveEMBY},
{'title': 'Update Kodi', 'path': 'home/updateKODI/', 'requires': self.haveKODI},
{'title': 'Update XBMC', 'path': 'home/updateXBMC/', 'requires': self.haveXBMC},
{'title': 'Update Plex', 'path': 'home/updatePLEX/', 'requires': self.havePLEX}
{'title': 'Update Emby', 'path': 'home/update_emby/', 'requires': self.haveEMBY},
{'title': 'Update Kodi', 'path': 'home/update_kodi/', 'requires': self.haveKODI},
{'title': 'Update XBMC', 'path': 'home/update_xbmc/', 'requires': self.haveXBMC},
{'title': 'Update Plex', 'path': 'home/update_plex/', 'requires': self.havePLEX}
]
@staticmethod
@ -783,236 +783,191 @@ class Home(MainHandler):
client = clients.get_client_instance(torrent_method)
connection, accesMsg = client(host, username, password).test_authentication()
connection, acces_msg = client(host, username, password).test_authentication()
return accesMsg
return acces_msg
def testGrowl(self, host=None, password=None):
@staticmethod
def discover_emby():
return notifiers.NotifierFactory().get('EMBY').discover_server()
def test_emby(self, host=None, apikey=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
hosts = config.clean_hosts(host, default_port=8096)
if not hosts:
return 'Fail: No valid host(s)'
result = notifiers.NotifierFactory().get('EMBY').test_notify(hosts, apikey)
ui.notifications.message('Tested Emby:', urllib.unquote_plus(hosts.replace(',', ', ')))
return result
def test_kodi(self, host=None, username=None, password=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
hosts = config.clean_hosts(host, default_port=8080)
if not hosts:
return 'Fail: No valid host(s)'
if None is not password and set('*') == set(password):
password = sickbeard.KODI_PASSWORD
result = notifiers.NotifierFactory().get('KODI').test_notify(hosts, username, password)
ui.notifications.message('Tested Kodi:', urllib.unquote_plus(hosts.replace(',', ', ')))
return result
def test_plex(self, host=None, username=None, password=None, server=False):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
hosts = config.clean_hosts(host, default_port=32400)
if not hosts:
return 'Fail: No valid host(s)'
if None is not password and set('*') == set(password):
password = sickbeard.PLEX_PASSWORD
server = 'true' == server
n = notifiers.NotifierFactory().get('PLEX')
method = n.test_update_library if server else n.test_notify
result = method(hosts, username, password)
ui.notifications.message('Tested Plex %s(s): ' % ('client', 'Media Server host')[server],
urllib.unquote_plus(hosts.replace(',', ', ')))
return result
# def test_xbmc(self, host=None, username=None, password=None):
# self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
#
# hosts = config.clean_hosts(host, default_port=80)
# if not hosts:
# return 'Fail: No valid host(s)'
#
# if None is not password and set('*') == set(password):
# password = sickbeard.XBMC_PASSWORD
#
# result = notifiers.NotifierFactory().get('XBMC').test_notify(hosts, username, password)
#
# ui.notifications.message('Tested XBMC: ', urllib.unquote_plus(hosts.replace(',', ', ')))
# return result
def test_nmj(self, host=None, database=None, mount=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
host = config.clean_host(host)
if not hosts:
return 'Fail: No valid host(s)'
return notifiers.NotifierFactory().get('NMJ').test_notify(urllib.unquote_plus(host), database, mount)
def settings_nmj(self, host=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
host = config.clean_host(host)
if not hosts:
return 'Fail: No valid host(s)'
return notifiers.NotifierFactory().get('NMJ').notify_settings(urllib.unquote_plus(host))
def test_nmj2(self, host=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
host = config.clean_host(host)
if not hosts:
return 'Fail: No valid host(s)'
return notifiers.NotifierFactory().get('NMJV2').test_notify(urllib.unquote_plus(host))
def settings_nmj2(self, host=None, dbloc=None, instance=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
host = config.clean_host(host)
return notifiers.NotifierFactory().get('NMJV2').notify_settings(urllib.unquote_plus(host), dbloc, instance)
def test_boxcar2(self, access_token=None, sound=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not access_token and starify(access_token, True):
access_token = sickbeard.BOXCAR2_ACCESSTOKEN
return notifiers.NotifierFactory().get('BOXCAR2').test_notify(access_token, sound)
def test_pushbullet(self, access_token=None, device_iden=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not access_token and starify(access_token, True):
access_token = sickbeard.PUSHBULLET_ACCESS_TOKEN
return notifiers.NotifierFactory().get('PUSHBULLET').test_notify(access_token, device_iden)
def get_pushbullet_devices(self, access_token=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not access_token and starify(access_token, True):
access_token = sickbeard.PUSHBULLET_ACCESS_TOKEN
return notifiers.NotifierFactory().get('PUSHBULLET').get_devices(access_token)
def test_pushover(self, user_key=None, api_key=None, priority=None, device=None, sound=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not user_key and starify(user_key, True):
user_key = sickbeard.PUSHOVER_USERKEY
if None is not api_key and starify(api_key, True):
api_key = sickbeard.PUSHOVER_APIKEY
return notifiers.NotifierFactory().get('PUSHOVER').test_notify(user_key, api_key, priority, device, sound)
def get_pushover_devices(self, user_key=None, api_key=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not user_key and starify(user_key, True):
user_key = sickbeard.PUSHOVER_USERKEY
if None is not api_key and starify(api_key, True):
api_key = sickbeard.PUSHOVER_APIKEY
return notifiers.NotifierFactory().get('PUSHOVER').get_devices(user_key, api_key)
def test_growl(self, host=None, password=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
host = config.clean_host(host, default_port=23053)
if None is not password and set('*') == set(password):
password = sickbeard.GROWL_PASSWORD
result = notifiers.growl_notifier.test_notify(host, password)
if password is None or password == '':
pw_append = ''
else:
pw_append = ' with password: ' + password
return notifiers.NotifierFactory().get('GROWL').test_notify(host, password)
if result:
return 'Registered and Tested growl successfully ' + urllib.unquote_plus(host) + pw_append
else:
return 'Registration and Testing of growl failed ' + urllib.unquote_plus(host) + pw_append
def testProwl(self, prowl_api=None, prowl_priority=0):
def test_prowl(self, prowl_api=None, prowl_priority=0):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not prowl_api and starify(prowl_api, True):
prowl_api = sickbeard.PROWL_API
result = notifiers.prowl_notifier.test_notify(prowl_api, prowl_priority)
if result:
return 'Test prowl notice sent successfully'
else:
return 'Test prowl notice failed'
return notifiers.NotifierFactory().get('PROWL').test_notify(prowl_api, prowl_priority)
def testBoxcar2(self, accesstoken=None, sound=None):
def test_nma(self, nma_api=None, nma_priority=0):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not accesstoken and starify(accesstoken, True):
accesstoken = sickbeard.BOXCAR2_ACCESSTOKEN
if None is not nma_api and starify(nma_api, True):
nma_api = sickbeard.NMA_API
result = notifiers.boxcar2_notifier.test_notify(accesstoken, sound)
if result:
return 'Boxcar2 notification succeeded. Check your Boxcar2 clients to make sure it worked'
else:
return 'Error sending Boxcar2 notification'
return notifiers.NotifierFactory().get('NMA').test_notify(nma_api, nma_priority)
def testPushover(self, userKey=None, apiKey=None, priority=None, device=None, sound=None):
def test_libnotify(self, *args, **kwargs):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not userKey and starify(userKey, True):
userKey = sickbeard.PUSHOVER_USERKEY
return notifiers.NotifierFactory().get('LIBNOTIFY').test_notify()
if None is not apiKey and starify(apiKey, True):
apiKey = sickbeard.PUSHOVER_APIKEY
result = notifiers.pushover_notifier.test_notify(userKey, apiKey, priority, device, sound)
if result:
return 'Pushover notification succeeded. Check your Pushover clients to make sure it worked'
else:
return 'Error sending Pushover notification'
def getPushoverDevices(self, userKey=None, apiKey=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not userKey and starify(userKey, True):
userKey = sickbeard.PUSHOVER_USERKEY
if None is not apiKey and starify(apiKey, True):
apiKey = sickbeard.PUSHOVER_APIKEY
result = notifiers.pushover_notifier.get_devices(userKey, apiKey)
if result:
return result
else:
return "{}"
def twitterStep1(self, *args, **kwargs):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
return notifiers.twitter_notifier._get_authorization()
def twitterStep2(self, key):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
result = notifiers.twitter_notifier._get_credentials(key)
logger.log(u'result: ' + str(result))
if result:
return 'Key verification successful'
else:
return 'Unable to verify key'
def testTwitter(self, *args, **kwargs):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
result = notifiers.twitter_notifier.test_notify()
if result:
return 'Tweet successful, check your twitter to make sure it worked'
else:
return 'Error sending tweet'
@staticmethod
def discover_emby():
return notifiers.emby_notifier.discover_server()
def testEMBY(self, host=None, apikey=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
hosts = config.clean_hosts(host)
if not hosts:
return 'Fail: At least one invalid host'
total_success, cur_message = notifiers.emby_notifier.test_notify(hosts, apikey)
return (cur_message, u'Success. All Emby hosts tested.')[total_success]
def testKODI(self, host=None, username=None, password=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
hosts = config.clean_hosts(host)
if not hosts:
return 'Fail: At least one invalid host'
if None is not password and set('*') == set(password):
password = sickbeard.KODI_PASSWORD
total_success, cur_message = notifiers.kodi_notifier.test_notify(hosts, username, password)
return (cur_message, u'Success. All Kodi hosts tested.')[total_success]
def testXBMC(self, host=None, username=None, password=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
host = config.clean_hosts(host)
if None is not password and set('*') == set(password):
password = sickbeard.XBMC_PASSWORD
finalResult = ''
for curHost in [x.strip() for x in host.split(',')]:
curResult = notifiers.xbmc_notifier.test_notify(urllib.unquote_plus(curHost), username, password)
if len(curResult.split(':')) > 2 and 'OK' in curResult.split(':')[2]:
finalResult += 'Test XBMC notice sent successfully to ' + urllib.unquote_plus(curHost)
else:
finalResult += 'Test XBMC notice failed to ' + urllib.unquote_plus(curHost)
finalResult += "<br />\n"
return finalResult
def testPMC(self, host=None, username=None, password=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not password and set('*') == set(password):
password = sickbeard.PLEX_PASSWORD
finalResult = ''
for curHost in [x.strip() for x in host.split(',')]:
curResult = notifiers.plex_notifier.test_notify(urllib.unquote_plus(curHost), username, password)
if len(curResult.split(':')) > 2 and 'OK' in curResult.split(':')[2]:
finalResult += 'Successful test notice sent to Plex client ... ' + urllib.unquote_plus(curHost)
else:
finalResult += 'Test failed for Plex client ... ' + urllib.unquote_plus(curHost)
finalResult += '<br />' + '\n'
ui.notifications.message('Tested Plex client(s): ', urllib.unquote_plus(host.replace(',', ', ')))
return finalResult
def testPMS(self, host=None, username=None, password=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not password and set('*') == set(password):
password = sickbeard.PLEX_PASSWORD
cur_result = notifiers.plex_notifier.test_notify(urllib.unquote_plus(host), username, password, server=True)
if '<br />' == cur_result:
cur_result += 'Fail: No valid host set to connect with'
final_result = (('Test result for', 'Successful test of')['Fail' not in cur_result]
+ ' Plex server(s) ... %s<br />\n' % cur_result)
ui.notifications.message('Tested Plex Media Server host(s): ', urllib.unquote_plus(host.replace(',', ', ')))
return final_result
def testLibnotify(self, *args, **kwargs):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if notifiers.libnotify_notifier.test_notify():
return 'Tried sending desktop notification via libnotify'
else:
return notifiers.libnotify.diagnose()
def testNMJ(self, host=None, database=None, mount=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
host = config.clean_host(host)
result = notifiers.nmj_notifier.test_notify(urllib.unquote_plus(host), database, mount)
if result:
return 'Successfully started the scan update'
else:
return 'Test failed to start the scan update'
def settingsNMJ(self, host=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
host = config.clean_host(host)
result = notifiers.nmj_notifier.notify_settings(urllib.unquote_plus(host))
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": ""}'
def testNMJv2(self, host=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
host = config.clean_host(host)
result = notifiers.nmjv2_notifier.test_notify(urllib.unquote_plus(host))
if result:
return 'Test notice sent successfully to ' + urllib.unquote_plus(host)
else:
return 'Test notice failed to ' + urllib.unquote_plus(host)
def settingsNMJv2(self, host=None, dbloc=None, instance=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
host = config.clean_host(host)
result = notifiers.nmjv2_notifier.notify_settings(urllib.unquote_plus(host), dbloc, instance)
if result:
return '{"message": "NMJ Database found at: %(host)s", "database": "%(database)s"}' % {"host": host,
"database": sickbeard.NMJv2_DATABASE}
else:
return '{"message": "Unable to find NMJ Database at location: %(dbloc)s. Is the right location selected and PCH running?", "database": ""}' % {
"dbloc": dbloc}
# def test_pushalot(self, authorization_token=None):
# self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
#
# if None is not authorization_token and starify(authorization_token, True):
# authorization_token = sickbeard.PUSHALOT_AUTHORIZATIONTOKEN
#
# return notifiers.NotifierFactory().get('PUSHALOT').test_notify(authorization_token)
def trakt_authenticate(self, pin=None, account=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
@ -1063,7 +1018,7 @@ class Home(MainHandler):
return json.dumps({'result': 'Not found: Account to delete'})
return json.dumps({'result': 'Not found: Invalid account id'})
def loadShowNotifyLists(self, *args, **kwargs):
def load_show_notify_lists(self, *args, **kwargs):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
my_db = db.DBConnection()
@ -1086,6 +1041,51 @@ class Home(MainHandler):
return json.dumps(response)
def test_slack(self, channel=None, as_authed=False, bot_name=None, icon_url=None, access_token=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
return notifiers.NotifierFactory().get('SLACK').test_notify(
channel=channel, as_authed='true' == as_authed,
bot_name=bot_name, icon_url=icon_url, access_token=access_token)
def test_discordapp(self, as_authed=False, username=None, icon_url=None, as_tts=False, access_token=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
return notifiers.NotifierFactory().get('DISCORDAPP').test_notify(
as_authed='true' == as_authed, username=username, icon_url=icon_url,
as_tts='true' == as_tts, access_token=access_token)
def test_gitter(self, room_name=None, access_token=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
return notifiers.NotifierFactory().get('GITTER').test_notify(
room_name=room_name, access_token=access_token)
def test_twitter(self, *args, **kwargs):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
return notifiers.NotifierFactory().get('TWITTER').test_notify()
def twitter_step1(self, *args, **kwargs):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
return notifiers.NotifierFactory().get('TWITTER').get_authorization()
def twitter_step2(self, key):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
return notifiers.NotifierFactory().get('TWITTER').get_credentials(key)
def test_email(self, host=None, port=None, smtp_from=None, use_tls=None, user=None, pwd=None, to=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not pwd and set('*') == set(pwd):
pwd = sickbeard.EMAIL_PASSWORD
host = config.clean_host(host)
return notifiers.NotifierFactory().get('EMAIL').test_notify(host, port, smtp_from, use_tls, user, pwd, to)
@staticmethod
def save_show_email(show=None, emails=None):
# self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
@ -1099,64 +1099,6 @@ class Home(MainHandler):
success = True
return json.dumps({'id': show, 'success': success})
def testEmail(self, host=None, port=None, smtp_from=None, use_tls=None, user=None, pwd=None, to=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not pwd and set('*') == set(pwd):
pwd = sickbeard.EMAIL_PASSWORD
host = config.clean_host(host)
if notifiers.email_notifier.test_notify(host, port, smtp_from, use_tls, user, pwd, to):
return 'Success. Test email sent. Check inbox.'
return 'ERROR: %s' % notifiers.email_notifier.last_err
def testNMA(self, nma_api=None, nma_priority=0):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not nma_api and starify(nma_api, True):
nma_api = sickbeard.NMA_API
result = notifiers.nma_notifier.test_notify(nma_api, nma_priority)
if result:
return 'Test NMA notice sent successfully'
else:
return 'Test NMA notice failed'
def testPushalot(self, authorizationToken=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not authorizationToken and starify(authorizationToken, True):
authorizationToken = sickbeard.PUSHALOT_AUTHORIZATIONTOKEN
result = notifiers.pushalot_notifier.test_notify(authorizationToken)
if result:
return 'Pushalot notification succeeded. Check your Pushalot clients to make sure it worked'
else:
return 'Error sending Pushalot notification'
def testPushbullet(self, accessToken=None, device_iden=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not accessToken and starify(accessToken, True):
accessToken = sickbeard.PUSHBULLET_ACCESS_TOKEN
return notifiers.pushbullet_notifier.test_notify(accessToken, device_iden)
def getPushbulletDevices(self, accessToken=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is not accessToken and starify(accessToken, True):
accessToken = sickbeard.PUSHBULLET_ACCESS_TOKEN
return notifiers.pushbullet_notifier.get_devices(accessToken)
def testSlack(self, access_token=None, channel=None, as_user=False, bot_name=None, icon_url=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
result = notifiers.slack_notifier.test_notify(channel, 'true' == as_user, bot_name, icon_url, access_token)
return ('Error sending notification, Slack response: "%s"' % result,
'Successful test notice sent. (Note: Slack clients display icon once in a sequence)')[True is result]
def viewchanges(self):
t = PageTemplate(headers=self.request.headers, file='viewchanges.tmpl')
@ -1345,14 +1287,14 @@ class Home(MainHandler):
t.submenu.append(
{'title': 'Force Full Update', 'path': t.force_update})
t.submenu.append({'title': 'Update show in Emby',
'path': 'home/updateEMBY%s' %
'path': 'home/update_emby%s' %
(INDEXER_TVDB == showObj.indexer and ('?show=%s' % showObj.indexerid) or '/'),
'requires': self.haveEMBY})
t.submenu.append({'title': 'Update show in Kodi',
'path': 'home/updateKODI?showName=%s' % urllib.quote_plus(
'path': 'home/update_kodi?show_name=%s' % urllib.quote_plus(
showObj.name.encode('utf-8')), 'requires': self.haveKODI})
t.submenu.append({'title': 'Update show in XBMC',
'path': 'home/updateXBMC?showName=%s' % urllib.quote_plus(
'path': 'home/update_xbmc?show_name=%s' % urllib.quote_plus(
showObj.name.encode('utf-8')), 'requires': self.haveXBMC})
t.submenu.append({'title': 'Media Renamer', 'path': 'home/testRename?show=%d' % showObj.indexerid})
if sickbeard.USE_SUBTITLES and not sickbeard.showQueueScheduler.action.isBeingSubtitled(
@ -2039,16 +1981,16 @@ class Home(MainHandler):
self.redirect('/home/displayShow?show=' + str(showObj.indexerid))
def updateEMBY(self, show=None):
def update_emby(self, show=None):
if notifiers.emby_notifier.update_library(
sickbeard.helpers.findCertainShow(sickbeard.showList,helpers.tryInt(show, None)), force=True):
if notifiers.NotifierFactory().get('EMBY').update_library(
sickbeard.helpers.findCertainShow(sickbeard.showList, helpers.tryInt(show, None))):
ui.notifications.message('Library update command sent to Emby host(s): ' + sickbeard.EMBY_HOST)
else:
ui.notifications.error('Unable to contact one or more Emby host(s): ' + sickbeard.EMBY_HOST)
self.redirect('/home/')
def updateKODI(self, showName=None):
def update_kodi(self, show_name=None):
# only send update to first host in the list -- workaround for kodi sql backend users
if sickbeard.KODI_UPDATE_ONLYFIRST:
@ -2057,29 +1999,14 @@ class Home(MainHandler):
else:
host = sickbeard.KODI_HOST
if notifiers.kodi_notifier.update_library(showName=showName, force=True):
if notifiers.NotifierFactory().get('KODI').update_library(show_name=show_name):
ui.notifications.message('Library update command sent to Kodi host(s): ' + host)
else:
ui.notifications.error('Unable to contact one or more Kodi host(s): ' + host)
self.redirect('/home/')
def updateXBMC(self, showName=None):
# only send update to first host in the list -- workaround for xbmc sql backend users
if sickbeard.XBMC_UPDATE_ONLYFIRST:
# only send update to first host in the list -- workaround for xbmc sql backend users
host = sickbeard.XBMC_HOST.split(',')[0].strip()
else:
host = sickbeard.XBMC_HOST
if notifiers.xbmc_notifier.update_library(showName=showName):
ui.notifications.message('Library update command sent to XBMC host(s): ' + host)
else:
ui.notifications.error('Unable to contact one or more XBMC host(s): ' + host)
self.redirect('/home/')
def updatePLEX(self, *args, **kwargs):
result = notifiers.plex_notifier.update_library()
def update_plex(self, *args, **kwargs):
result = notifiers.NotifierFactory().get('PLEX').update_library()
if 'Fail' not in result:
ui.notifications.message(
'Library update command sent to', 'Plex Media Server host(s): ' + sickbeard.PLEX_SERVER_HOST.replace(',', ', '))
@ -2087,6 +2014,21 @@ class Home(MainHandler):
ui.notifications.error('Unable to contact', 'Plex Media Server host(s): ' + result)
self.redirect('/home/')
# def update_xbmc(self, show_name=None):
#
# # only send update to first host in the list -- workaround for xbmc sql backend users
# if sickbeard.XBMC_UPDATE_ONLYFIRST:
# # only send update to first host in the list -- workaround for xbmc sql backend users
# host = sickbeard.XBMC_HOST.split(',')[0].strip()
# else:
# host = sickbeard.XBMC_HOST
#
# if notifiers.NotifierFactory().get('XBMC').update_library(show_name=show_name):
# ui.notifications.message('Library update command sent to XBMC host(s): ' + host)
# else:
# ui.notifications.error('Unable to contact one or more XBMC host(s): ' + host)
# self.redirect('/home/')
def setStatus(self, show=None, eps=None, status=None, direct=False):
if show is None or eps is None or status is None:
@ -5711,55 +5653,64 @@ class ConfigNotifications(Config):
'b64': base64.urlsafe_b64encode(location)})
return t.respond()
def saveNotifications(self,
use_emby=None, emby_update_library=None, emby_host=None, emby_apikey=None,
use_kodi=None, kodi_always_on=None, kodi_notify_onsnatch=None, kodi_notify_ondownload=None,
kodi_notify_onsubtitledownload=None, kodi_update_onlyfirst=None,
kodi_update_library=None, kodi_update_full=None,
kodi_host=None, kodi_username=None, kodi_password=None,
use_xbmc=None, xbmc_always_on=None, xbmc_notify_onsnatch=None, xbmc_notify_ondownload=None,
xbmc_notify_onsubtitledownload=None, xbmc_update_onlyfirst=None,
xbmc_update_library=None, xbmc_update_full=None,
xbmc_host=None, xbmc_username=None, xbmc_password=None,
use_plex=None, plex_notify_onsnatch=None, plex_notify_ondownload=None,
plex_notify_onsubtitledownload=None, plex_update_library=None,
plex_server_host=None, plex_host=None, plex_username=None, plex_password=None,
use_growl=None, growl_notify_onsnatch=None, growl_notify_ondownload=None,
growl_notify_onsubtitledownload=None, growl_host=None, growl_password=None,
use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None,
prowl_notify_onsubtitledownload=None, prowl_api=None, prowl_priority=0,
use_twitter=None, twitter_notify_onsnatch=None, twitter_notify_ondownload=None,
twitter_notify_onsubtitledownload=None,
use_boxcar2=None, boxcar2_notify_onsnatch=None, boxcar2_notify_ondownload=None,
boxcar2_notify_onsubtitledownload=None, boxcar2_accesstoken=None, boxcar2_sound=None,
use_pushover=None, pushover_notify_onsnatch=None, pushover_notify_ondownload=None,
pushover_notify_onsubtitledownload=None, pushover_userkey=None, pushover_apikey=None,
pushover_priority=None, pushover_device=None, pushover_sound=None, pushover_device_list=None,
use_libnotify=None, libnotify_notify_onsnatch=None, libnotify_notify_ondownload=None,
libnotify_notify_onsubtitledownload=None,
use_nmj=None, nmj_host=None, nmj_database=None, nmj_mount=None, use_synoindex=None,
use_nmjv2=None, nmjv2_host=None, nmjv2_dbloc=None, nmjv2_database=None,
use_trakt=None, trakt_pin=None,
trakt_remove_watchlist=None, trakt_use_watchlist=None, trakt_method_add=None,
trakt_start_paused=None, trakt_sync=None,
trakt_default_indexer=None, trakt_remove_serieslist=None, trakt_collection=None, trakt_accounts=None,
use_synologynotifier=None, synologynotifier_notify_onsnatch=None,
synologynotifier_notify_ondownload=None, synologynotifier_notify_onsubtitledownload=None,
use_pytivo=None, pytivo_notify_onsnatch=None, pytivo_notify_ondownload=None,
pytivo_notify_onsubtitledownload=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_notify_onsubtitledownload=None, nma_api=None, nma_priority=0,
use_pushalot=None, pushalot_notify_onsnatch=None, pushalot_notify_ondownload=None,
pushalot_notify_onsubtitledownload=None, pushalot_authorizationtoken=None,
use_pushbullet=None, pushbullet_notify_onsnatch=None, pushbullet_notify_ondownload=None,
pushbullet_notify_onsubtitledownload=None, pushbullet_access_token=None,
pushbullet_device_iden=None, pushbullet_device_list=None,
use_email=None, email_notify_onsnatch=None, email_notify_ondownload=None,
email_notify_onsubtitledownload=None, email_host=None, email_port=25, email_from=None,
email_tls=None, email_user=None, email_password=None, email_list=None, email_show_list=None,
email_show=None, use_slack=None, slack_notify_onsnatch=None, slack_notify_ondownload=None,
slack_access_token=None, slack_channel=None, slack_as_user=None, slack_bot_name=None, slack_icon_url=None, **kwargs):
def save_notifications(
self,
use_emby=None, emby_update_library=None, emby_host=None, emby_apikey=None,
use_kodi=None, kodi_always_on=None, kodi_update_library=None, kodi_update_full=None,
kodi_update_onlyfirst=None, kodi_host=None, kodi_username=None, kodi_password=None,
kodi_notify_onsnatch=None, kodi_notify_ondownload=None, kodi_notify_onsubtitledownload=None,
use_plex=None, plex_update_library=None, plex_username=None, plex_password=None, plex_server_host=None,
plex_notify_onsnatch=None, plex_notify_ondownload=None, plex_notify_onsubtitledownload=None, plex_host=None,
# use_xbmc=None, xbmc_always_on=None, xbmc_notify_onsnatch=None, xbmc_notify_ondownload=None,
# xbmc_notify_onsubtitledownload=None, xbmc_update_onlyfirst=None,
# xbmc_update_library=None, xbmc_update_full=None,
# xbmc_host=None, xbmc_username=None, xbmc_password=None,
use_nmj=None, nmj_host=None, nmj_database=None, nmj_mount=None,
use_nmjv2=None, nmjv2_host=None, nmjv2_dbloc=None, nmjv2_database=None,
use_synoindex=None, use_synologynotifier=None, synologynotifier_notify_onsnatch=None,
synologynotifier_notify_ondownload=None, synologynotifier_notify_onsubtitledownload=None,
use_pytivo=None, pytivo_host=None, pytivo_share_name=None, pytivo_tivo_name=None,
# pytivo_notify_onsnatch=None, pytivo_notify_ondownload=None, pytivo_notify_onsubtitledownload=None,
# pytivo_update_library=None,
use_boxcar2=None, boxcar2_notify_onsnatch=None, boxcar2_notify_ondownload=None,
boxcar2_notify_onsubtitledownload=None, boxcar2_access_token=None, boxcar2_sound=None,
use_pushbullet=None, pushbullet_notify_onsnatch=None, pushbullet_notify_ondownload=None,
pushbullet_notify_onsubtitledownload=None, pushbullet_access_token=None, pushbullet_device_iden=None,
use_pushover=None, pushover_notify_onsnatch=None, pushover_notify_ondownload=None,
pushover_notify_onsubtitledownload=None, pushover_userkey=None, pushover_apikey=None,
pushover_priority=None, pushover_device=None, pushover_sound=None, pushover_device_list=None,
use_growl=None, growl_notify_onsnatch=None, growl_notify_ondownload=None,
growl_notify_onsubtitledownload=None, growl_host=None, growl_password=None,
use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None,
prowl_notify_onsubtitledownload=None, prowl_api=None, prowl_priority=0,
use_nma=None, nma_notify_onsnatch=None, nma_notify_ondownload=None,
nma_notify_onsubtitledownload=None, nma_api=None, nma_priority=0,
use_libnotify=None, libnotify_notify_onsnatch=None, libnotify_notify_ondownload=None,
libnotify_notify_onsubtitledownload=None,
# use_pushalot=None, pushalot_notify_onsnatch=None, pushalot_notify_ondownload=None,
# pushalot_notify_onsubtitledownload=None, pushalot_authorizationtoken=None,
use_trakt=None,
# trakt_pin=None, trakt_remove_watchlist=None, trakt_use_watchlist=None, trakt_method_add=None,
# trakt_start_paused=None, trakt_sync=None, trakt_default_indexer=None, trakt_remove_serieslist=None,
# trakt_collection=None, trakt_accounts=None,
use_slack=None, slack_notify_onsnatch=None, slack_notify_ondownload=None,
slack_notify_onsubtitledownload=None, slack_access_token=None, slack_channel=None,
slack_as_authed=None, slack_bot_name=None, slack_icon_url=None,
use_discordapp=None, discordapp_notify_onsnatch=None, discordapp_notify_ondownload=None,
discordapp_notify_onsubtitledownload=None, discordapp_access_token=None,
discordapp_as_authed=None, discordapp_username=None, discordapp_icon_url=None,
discordapp_as_tts=None,
use_gitter=None, gitter_notify_onsnatch=None, gitter_notify_ondownload=None,
gitter_notify_onsubtitledownload=None, gitter_access_token=None, gitter_room=None,
use_twitter=None, twitter_notify_onsnatch=None, twitter_notify_ondownload=None,
twitter_notify_onsubtitledownload=None,
use_email=None, email_notify_onsnatch=None, email_notify_ondownload=None,
email_notify_onsubtitledownload=None, email_host=None, email_port=25, email_from=None,
email_tls=None, email_user=None, email_password=None, email_list=None,
# email_show_list=None, email_show=None,
**kwargs):
results = []
@ -5795,18 +5746,18 @@ class ConfigNotifications(Config):
if set('*') != set(kodi_password):
sickbeard.KODI_PASSWORD = kodi_password
sickbeard.USE_XBMC = config.checkbox_to_value(use_xbmc)
sickbeard.XBMC_ALWAYS_ON = config.checkbox_to_value(xbmc_always_on)
sickbeard.XBMC_NOTIFY_ONSNATCH = config.checkbox_to_value(xbmc_notify_onsnatch)
sickbeard.XBMC_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(xbmc_notify_ondownload)
sickbeard.XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(xbmc_notify_onsubtitledownload)
sickbeard.XBMC_UPDATE_LIBRARY = config.checkbox_to_value(xbmc_update_library)
sickbeard.XBMC_UPDATE_FULL = config.checkbox_to_value(xbmc_update_full)
sickbeard.XBMC_UPDATE_ONLYFIRST = config.checkbox_to_value(xbmc_update_onlyfirst)
sickbeard.XBMC_HOST = config.clean_hosts(xbmc_host)
sickbeard.XBMC_USERNAME = xbmc_username
if set('*') != set(xbmc_password):
sickbeard.XBMC_PASSWORD = xbmc_password
# sickbeard.USE_XBMC = config.checkbox_to_value(use_xbmc)
# sickbeard.XBMC_ALWAYS_ON = config.checkbox_to_value(xbmc_always_on)
# sickbeard.XBMC_NOTIFY_ONSNATCH = config.checkbox_to_value(xbmc_notify_onsnatch)
# sickbeard.XBMC_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(xbmc_notify_ondownload)
# sickbeard.XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(xbmc_notify_onsubtitledownload)
# sickbeard.XBMC_UPDATE_LIBRARY = config.checkbox_to_value(xbmc_update_library)
# sickbeard.XBMC_UPDATE_FULL = config.checkbox_to_value(xbmc_update_full)
# sickbeard.XBMC_UPDATE_ONLYFIRST = config.checkbox_to_value(xbmc_update_onlyfirst)
# sickbeard.XBMC_HOST = config.clean_hosts(xbmc_host)
# sickbeard.XBMC_USERNAME = xbmc_username
# if set('*') != set(xbmc_password):
# sickbeard.XBMC_PASSWORD = xbmc_password
sickbeard.USE_PLEX = config.checkbox_to_value(use_plex)
sickbeard.PLEX_NOTIFY_ONSNATCH = config.checkbox_to_value(plex_notify_onsnatch)
@ -5845,7 +5796,7 @@ class ConfigNotifications(Config):
sickbeard.BOXCAR2_NOTIFY_ONSNATCH = config.checkbox_to_value(boxcar2_notify_onsnatch)
sickbeard.BOXCAR2_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(boxcar2_notify_ondownload)
sickbeard.BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(boxcar2_notify_onsubtitledownload)
key = boxcar2_accesstoken.strip()
key = boxcar2_access_token.strip()
if not starify(key, True):
sickbeard.BOXCAR2_ACCESSTOKEN = key
sickbeard.BOXCAR2_SOUND = boxcar2_sound
@ -5888,8 +5839,8 @@ class ConfigNotifications(Config):
synologynotifier_notify_onsubtitledownload)
sickbeard.USE_TRAKT = config.checkbox_to_value(use_trakt)
# sickbeard.traktCheckerScheduler.silent = not sickbeard.USE_TRAKT
sickbeard.TRAKT_UPDATE_COLLECTION = build_config(**kwargs)
# sickbeard.traktCheckerScheduler.silent = not sickbeard.USE_TRAKT
# sickbeard.TRAKT_DEFAULT_INDEXER = int(trakt_default_indexer)
# sickbeard.TRAKT_SYNC = config.checkbox_to_value(trakt_sync)
# sickbeard.TRAKT_USE_WATCHLIST = config.checkbox_to_value(trakt_use_watchlist)
@ -5901,12 +5852,30 @@ class ConfigNotifications(Config):
sickbeard.USE_SLACK = config.checkbox_to_value(use_slack)
sickbeard.SLACK_NOTIFY_ONSNATCH = config.checkbox_to_value(slack_notify_onsnatch)
sickbeard.SLACK_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(slack_notify_ondownload)
sickbeard.SLACK_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(slack_notify_onsubtitledownload)
sickbeard.SLACK_ACCESS_TOKEN = slack_access_token
sickbeard.SLACK_CHANNEL = slack_channel
sickbeard.SLACK_AS_USER = config.checkbox_to_value(slack_as_user)
sickbeard.SLACK_AS_AUTHED = config.checkbox_to_value(slack_as_authed)
sickbeard.SLACK_BOT_NAME = slack_bot_name
sickbeard.SLACK_ICON_URL = slack_icon_url
sickbeard.USE_DISCORDAPP = config.checkbox_to_value(use_discordapp)
sickbeard.DISCORDAPP_NOTIFY_ONSNATCH = config.checkbox_to_value(discordapp_notify_onsnatch)
sickbeard.DISCORDAPP_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(discordapp_notify_ondownload)
sickbeard.DISCORDAPP_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(discordapp_notify_onsubtitledownload)
sickbeard.DISCORDAPP_ACCESS_TOKEN = discordapp_access_token
sickbeard.DISCORDAPP_AS_AUTHED = config.checkbox_to_value(discordapp_as_authed)
sickbeard.DISCORDAPP_USERNAME = discordapp_username
sickbeard.DISCORDAPP_ICON_URL = discordapp_icon_url
sickbeard.DISCORDAPP_AS_TTS = config.checkbox_to_value(discordapp_as_tts)
sickbeard.USE_GITTER = config.checkbox_to_value(use_gitter)
sickbeard.GITTER_NOTIFY_ONSNATCH = config.checkbox_to_value(gitter_notify_onsnatch)
sickbeard.GITTER_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(gitter_notify_ondownload)
sickbeard.GITTER_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(gitter_notify_onsubtitledownload)
sickbeard.GITTER_ACCESS_TOKEN = gitter_access_token
sickbeard.GITTER_ROOM = gitter_room
sickbeard.USE_EMAIL = config.checkbox_to_value(use_email)
sickbeard.EMAIL_NOTIFY_ONSNATCH = config.checkbox_to_value(email_notify_onsnatch)
sickbeard.EMAIL_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(email_notify_ondownload)
@ -5921,13 +5890,13 @@ class ConfigNotifications(Config):
sickbeard.EMAIL_LIST = email_list
sickbeard.USE_PYTIVO = config.checkbox_to_value(use_pytivo)
sickbeard.PYTIVO_NOTIFY_ONSNATCH = config.checkbox_to_value(pytivo_notify_onsnatch)
sickbeard.PYTIVO_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(pytivo_notify_ondownload)
sickbeard.PYTIVO_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(pytivo_notify_onsubtitledownload)
sickbeard.PYTIVO_UPDATE_LIBRARY = config.checkbox_to_value(pytivo_update_library)
sickbeard.PYTIVO_HOST = config.clean_host(pytivo_host)
sickbeard.PYTIVO_SHARE_NAME = pytivo_share_name
sickbeard.PYTIVO_TIVO_NAME = pytivo_tivo_name
# sickbeard.PYTIVO_NOTIFY_ONSNATCH = config.checkbox_to_value(pytivo_notify_onsnatch)
# sickbeard.PYTIVO_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(pytivo_notify_ondownload)
# sickbeard.PYTIVO_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(pytivo_notify_onsubtitledownload)
# sickbeard.PYTIVO_UPDATE_LIBRARY = config.checkbox_to_value(pytivo_update_library)
sickbeard.USE_NMA = config.checkbox_to_value(use_nma)
sickbeard.NMA_NOTIFY_ONSNATCH = config.checkbox_to_value(nma_notify_onsnatch)
@ -5938,13 +5907,13 @@ class ConfigNotifications(Config):
sickbeard.NMA_API = key
sickbeard.NMA_PRIORITY = nma_priority
sickbeard.USE_PUSHALOT = config.checkbox_to_value(use_pushalot)
sickbeard.PUSHALOT_NOTIFY_ONSNATCH = config.checkbox_to_value(pushalot_notify_onsnatch)
sickbeard.PUSHALOT_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(pushalot_notify_ondownload)
sickbeard.PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(pushalot_notify_onsubtitledownload)
key = pushalot_authorizationtoken.strip()
if not starify(key, True):
sickbeard.PUSHALOT_AUTHORIZATIONTOKEN = key
# sickbeard.USE_PUSHALOT = config.checkbox_to_value(use_pushalot)
# sickbeard.PUSHALOT_NOTIFY_ONSNATCH = config.checkbox_to_value(pushalot_notify_onsnatch)
# sickbeard.PUSHALOT_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(pushalot_notify_ondownload)
# sickbeard.PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(pushalot_notify_onsubtitledownload)
# key = pushalot_authorizationtoken.strip()
# if not starify(key, True):
# sickbeard.PUSHALOT_AUTHORIZATIONTOKEN = key
sickbeard.USE_PUSHBULLET = config.checkbox_to_value(use_pushbullet)
sickbeard.PUSHBULLET_NOTIFY_ONSNATCH = config.checkbox_to_value(pushbullet_notify_onsnatch)