Page 2 of 5

Re: [PP-SCRIPT] Pushbullet notifications (Android, iOS, Chro

Posted: 26 Jun 2014, 13:52
by JVM
Put them all in first post, also renamed API to Access Token.

Re: [PP-SCRIPT] Pushbullet notifications (Android, iOS, Chro

Posted: 22 Jul 2014, 11:27
by JVM
I added average download speed reporting to the 13.0-r1031+ script. It works by going through the "Messages" tab log and finding the first "Downloading NZBNAME @ server" and "Collection NZBNAME completely downloaded" entry times, NZB download size and computing the average speed accordingly. It does not take into account download interruptions. It reports the average speed on the log and the Pushbullet push.
Pushbullet.py
22 July 2014
(13.16 KiB) Downloaded 323 times

Re: [PP-SCRIPT] Pushbullet notifications (Android, iOS, Chro

Posted: 22 Jul 2014, 11:37
by hugbug
In v14 the time statistics are easy to get - [New Feature] Per-nzb time statistics.

I'll update EMail.py soon to include that info and you can adopt it.

Re: [PP-SCRIPT] Pushbullet notifications (Android, iOS, Chro

Posted: 22 Jul 2014, 11:58
by JVM
Cheers humbug, I'll keep an eye out for v14.

Re: [PP-SCRIPT] Pushbullet notifications (Android, iOS, Chro

Posted: 30 Jul 2014, 15:31
by JVM
For NZBGet v14+
  • Pushbullet.py is a pre- and post-download notification script for v14+. It is basically pushbulletnzbprocess.py and pushbullet.py in one.

    Compared to 13.0-r1031+, I have added options regarding [New Feature] Per-nzb time statistics, specifically average download speed (which is more reliable than 22 July 2014) and various times.

    To install Pushbullet.py, copy to your scripts folder. For a notification before a nzb is added to the queue, add Pushbullet.py under Settings, Extension Scripts, ScanScript. For a notification after job is completed, add Pushbullet.py under Settings, Extension Scripts, PostScript or Category, PostScript. Then add your Access Token on the Pushbullet settings page. Testing is the same as described for pushbullet.py in OP.

Re: [PP-SCRIPT] Pushbullet notifications (Android, iOS, Chro

Posted: 31 Jul 2014, 07:39
by hugbug
Great, now I can borrow your code for EMail.py ;)

I've noticed two things:
  • there are too many calls of RPC-method "listgroups". Each time you write "server.listgroups" an RPC-call is executed. You should save the result in a variable instead;
  • the first item in the queue isn't necessary the item being post-processed. The first item can be paused or have lower priority than one of the other items. Instead of taking the first item you should search through the collection looking for an item with NZBID equal to os.environ['NZBPP_NZBID']
Nice work anyway!

Re: [PP-SCRIPT] Pushbullet notifications (Android, iOS, Chro

Posted: 31 Jul 2014, 11:24
by JVM
Cheers, thanks for the tips, I've updated it again.

Re: [PP-SCRIPT] Pushbullet notifications (Android, iOS, Chro

Posted: 31 Jul 2014, 11:49
by hugbug
Yet the listgroups is called multiple times - once for each item in the queue:

Code: Select all

	for i in xrange(len(server.listgroups(0))):
		nzbGroup = server.listgroups(0)[i]
		if nzbGroup['NZBID'] == nzbID:
			break
Better something like this (not tested):

Code: Select all

	groups = server.listgroups(0)
	for i in xrange(len(groups)):
		nzbGroup = groups[i]
		if nzbGroup['NZBID'] == nzbID:
			break

Re: [PP-SCRIPT] Pushbullet notifications (Android, iOS, Chro

Posted: 31 Jul 2014, 12:04
by JVM
Yep I didn't see that one :oops:, thanks again!

Re: [PP-SCRIPT] Pushbullet notifications (Android, iOS, Chro

Posted: 07 Oct 2014, 04:11
by binreader
I'm on windows. I don't know anything much about python. But these scripts are cool. One thing i didn't want to bother with is the curl requirement. So i modified the v14 script to not use curl. Also in iOS the banner notification do not allow too much text so not much of the message was showing up until click through into the pushbullet app, not fun. So i added couple options to show nzb name in the title and also show a status in the title to get better idea what happened to the nzb. Last I added a section on scan and on PP to skip certain categories, cuz there some stuff you just don't want any notification about.
Beats me if i did this stuff properly but it seems to work, I leave to original author to integrate. Oh and I made one bugfix, the part that was trying to write added to the top of queue and the pause detection was not working because it was checking against numbers instead of literal. Also I put in device_iden but i dunno if that is correct.

Summary
-No more curl requirement, not needed
-Option: Add nzb name to title
-Option: Add status tags to title
-Option: Skip specific categories
-Bugfix: Handle pause and top of queue messages

Code: Select all

#!/usr/bin/env python
#
# Modified E-Mail post-processing script for sending Pushbullet notifications NZBGet. 
# Modified for Pushbullet on 5 August 2014.
#
# # # Copyright (C) 2013-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
# # #
# # # This program 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 2 of the License, or
# # # (at your option) any later version.
# # # 
# # # This program 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 this program; if not, write to the Free Software
# # # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
# # #
# # # $Revision: 1031 $
# # # $Date: 2014-05-29 00:19:39 +0200 (Thu, 29 May 2014) $
# # #

##############################################################################
### NZBGET SCAN/POST-PROCESSING SCRIPT                                     ###

# Sends Pushbullet notification.
#
# This script sends Pushbullet notification  before nzb-file is added to queue and/or when the job is done.
#
# NOTE: This script requires Python installed on your system. Requires NZBGet v14+.
#
# Further discussion at: nzbget.net/forum/viewtopic.php?f=8&t=1205
# 
##############################################################################
### OPTIONS                                                                ###

# Your Pushbullet Access Token. 
#
# Find your Access Token at <a href="https://www.pushbullet.com/account">https://www.pushbullet.com/account</a>
#AccessToken=

# Pushbullet device(s) to send to. 
# 
# Leave empty to push to all devices. Can also push to individual devices by device IDs, separate each device ID by a space. 
# Device IDs can be found by using cURL: curl -u your_api_key_here: https://api.pushbullet.com/api/devices
#
# More info at <a href="https://www.pushbullet.com/api">https://www.pushbullet.com/api</a>
#DeviceID=

# Pushbullet push title.
#Title=NZBGet

# Add status to title (yes, no).
# 
# Adds [Received], [Success], [Failure], [Paused] states to the Pushbullet title.
#
#TitleStatus=yes

# Add NZB name to title (yes, no).
# 
# Adds full NZB name to Pushbullet title, may be shortened.
#
#TitleNZB=yes

# Pushbullet push type.
#
# Leave as default. More info at <a href="https://www.pushbullet.com/api">https://www.pushbullet.com/api</a>
#Type=note

# Pushbullet API URL.
#
# Leave as default. More info at <a href="https://www.pushbullet.com/api">https://www.pushbullet.com/api</a>
#URL=https://api.pushbullet.com/api/pushes

# Enable Scan Script notification (yes, no).
# 
# Send Pushbullet notification before nzb-file is added to queue.
#
# NOTE: Following options defined only when ScanScript is enabled.
#
#ScanScript=yes

# Skip certain category.
#
# Specify a category to skip. Pushbullet notification will not be sent when this category is detected.
# Can also put multiple categories comma seperated.
#
#SkipCategory=

# Append top of queue (yes, no).
#
# Notify if added to top of queue. 
#
#Top=yes

# Append added paused (yes, no).
#
# Notify if added to queue paused. 
#
#Paused=yes

# Append category (yes, no).
#
#Category=yes

# Append priority (yes, no).
#
#Priority=yes

# Append source nzb URL (yes, no).
#
#SourceURL=yes

# Enable Post-Processing Script notification (yes, no).
# 
# Send Pushbullet notification before nzb-file is added to queue.
#
# NOTE: Following options defined only when PPScript is enabled.
#
#PPScript=yes

# Append average download speed (yes, no).
#
#AveDownSpeed=yes

# Append download size (yes, no).
#
#DownloadSize=yes

# Append download time (yes, no).
#
#DownloadTime=yes

# Append par time (yes, no).
#
#ParTime=yes

# Append repair time (yes, no).
#
#RepairTime=yes

# Append unpack time (yes, no).
#
#UnpackTime=yes

# Append total post processing script time (yes, no).
#
#PPTime=yes

# Append total time (yes, no).
#
#TotalTime=yes

# Append list of files to the message (yes, no).
#
# Add the list of downloaded files (the content of destination directory).
#FileList=yes

# Append broken-log to the message (yes, no).
#
# Add the content of file _brokenlog.txt. This file contains the list of damaged
# files and the result of par-check/repair. For successful downloads the broken-log
# is usually deleted by cleanup-script and therefore is not sent.
#BrokenLog=yes

# Append post-processing log to the message (Always, Never, OnFailure).
#
# Add the post-processing log of active job.
#PostProcessLog=OnFailure

### NZBGET SCAN/POST-PROCESSING SCRIPT                                     ###
##############################################################################


import os
import sys
import datetime
import urllib2
import urllib

try:
        from xmlrpclib import ServerProxy # python 2
except ImportError:
        from xmlrpc.client import ServerProxy # python 3

# Exit codes used by NZBGet
POSTPROCESS_SUCCESS=93
POSTPROCESS_ERROR=94

# Check if the script is called from nzbget 11.0 or later
if not 'NZBOP_SCRIPTDIR' in os.environ:
        print('*** NZBGet post-processing script ***')
        print('This script is supposed to be called from nzbget (11.0 or later).')
        sys.exit(POSTPROCESS_ERROR)

print('[DETAIL] Script successfully started')
sys.stdout.flush()

required_options = ( 'NZBPO_SCANSCRIPT', 'NZBPO_PPSCRIPT', 'NZBPO_FILELIST', 'NZBPO_BROKENLOG', 'NZBPO_POSTPROCESSLOG', 
'NZBPO_URL', 'NZBPO_ACCESSTOKEN', 'NZBPO_TYPE', 'NZBPO_TITLE', 'NZBPO_TOP', 'NZBPO_PAUSED', 'NZBPO_CATEGORY', 'NZBPO_PRIORITY', 
'NZBPO_SOURCEURL', 'NZBPO_AVEDOWNSPEED', 'NZBPO_DOWNLOADTIME', 'NZBPO_DOWNLOADSIZE', 'NZBPO_PARTIME', 'NZBPO_REPAIRTIME',
'NZBPO_UNPACKTIME', 'NZBPO_PPTIME', 'NZBPO_TOTALTIME')

for optname in required_options:
        if (not optname in os.environ):
                print('[ERROR] Option %s is missing in configuration file. Please check script settings' % optname[6:])
                sys.exit(POSTPROCESS_ERROR)

# Scan Script Notification
                
if 'NZBNP_DIRECTORY' in os.environ:        
        if os.environ['NZBPO_SCANSCRIPT'] == 'no':
                print('[DETAIL] ScanScript Notification not enabled')
                sys.exit(POSTPROCESS_SUCCESS)
                
        skip_category = os.environ['NZBPO_SKIPCATEGORY'].split(',')
        if len( skip_category ) > 0:
                for cat in skip_category:
                        if os.environ['NZBNP_CATEGORY'] == cat:
                                print('[DETAIL] ScanScript Notification skipped')
                                sys.exit(POSTPROCESS_SUCCESS)                   
                
        elif os.environ['NZBNP_CATEGORY'] == os.environ['NZBPO_SKIPCATEGORY']:
               print('[DETAIL] ScanScript Notification skipped')
               sys.exit(POSTPROCESS_SUCCESS)
               
        print('[DETAIL] ScanScript Notification')
        subject = ''
        if os.environ['NZBPO_TitleStatus'] == 'yes':
                subject = ' [Received]'
        text = 'Received: ' + os.environ['NZBNP_NZBNAME']
        if os.environ['NZBPO_TOP'] == 'yes':
                if os.environ['NZBNP_TOP'] == '1':
                        text += '\nAdded to top of queue.'
                elif os.environ['NZBNP_TOP'] == '0':
                        text += '\nNot added to top of queue.'
        if os.environ['NZBPO_PAUSED'] == 'yes':
                if os.environ['NZBNP_PAUSED'] == '1':
                        text += '\nNZB added as paused.'
                        if os.environ['NZBPO_TitleStatus'] == 'yes':
                                subject = ' [Paused]'
                elif os.environ['NZBNP_PAUSED'] == '0':
                        text += '\nNZB is allowed to begin.'
        if os.environ['NZBPO_CATEGORY'] == 'yes':
                if len(os.environ['NZBNP_CATEGORY']) > 0:
                        text += '\nCategory: ' + os.environ['NZBNP_CATEGORY']
        if os.environ['NZBPO_PRIORITY'] == 'yes':
                if len(os.environ['NZBNP_PRIORITY']) > 0:
                        text += '\nPriority: ' + os.environ['NZBNP_PRIORITY']
        if os.environ['NZBPO_SOURCEURL'] == 'yes':      
                text += '\nURL: ' + os.environ['NZBNP_URL']
        if os.environ['NZBPO_TitleNZB'] == 'yes':
                subject += ' ' + os.environ['NZBNP_NZBNAME']
# Post-Processing Notification
                
if 'NZBPP_DIRECTORY' in os.environ:
        if os.environ['NZBPO_PPSCRIPT'] == 'no':
                print('[DETAIL] PPScript Notification not enabled')
                sys.exit(POSTPROCESS_SUCCESS)
        print('[DETAIL] PPScript Notification')
###### EMail.py
        success = os.environ['NZBPP_TOTALSTATUS'] == 'SUCCESS'
        subject = ''
        if success:
                if os.environ['NZBPO_TitleStatus'] == 'yes':
                        subject = ' [Success]'                 
                text = 'Download of "%s" has successfully completed.' % (os.environ['NZBPP_NZBNAME'])
        else:
                if os.environ['NZBPO_TitleStatus'] == 'yes':
                        subject = ' [Failure]'   
                text = 'Download of "%s" has failed.' % (os.environ['NZBPP_NZBNAME'])

        if os.environ['NZBPO_TitleNZB'] == 'yes':
                subject += ' ' + os.environ['NZBPP_NZBNAME']

        text += '\nStatus: %s' % os.environ['NZBPP_STATUS']

        # # add list of downloaded files
        # if os.environ['NZBPO_FILELIST'] == 'yes':
                # text += '\n\nFiles:'
                # for dirname, dirnames, filenames in os.walk(os.environ['NZBPP_DIRECTORY']):
                        # for filename in filenames:
                                # text += '\n' + os.path.join(dirname, filename)[len(os.environ['NZBPP_DIRECTORY']) + 1:]

        # # add _brokenlog.txt (if exists)
        # if os.environ['NZBPO_BROKENLOG'] == 'yes':
                # brokenlog = '%s/_brokenlog.txt' % os.environ['NZBPP_DIRECTORY']
                # if os.path.exists(brokenlog):
                        # text += '\n\nBrokenlog:\n' + open(brokenlog, 'r').read().strip()

        # add post-processing log
        # if os.environ['NZBPO_POSTPROCESSLOG'] == 'Always' or \
                # (os.environ['NZBPO_POSTPROCESSLOG'] == 'OnFailure' and not success):
        # To get the post-processing log we connect to NZBGet via XML-RPC
        # and call method "postqueue", which returns the list of post-processing job.
        # The first item in the list is current job. This item has a field 'Log',
        # containing an array of log-entries.
        # For more info visit http://nzbget.net/RPC_API_reference
        
        # First we need to know connection info: host, port and password of NZBGet server.
        # NZBGet passes all configuration options to post-processing script as
        # environment variables.
        host = os.environ['NZBOP_CONTROLIP'];
        port = os.environ['NZBOP_CONTROLPORT'];
        username = os.environ['NZBOP_CONTROLUSERNAME'];
        password = os.environ['NZBOP_CONTROLPASSWORD'];
        
        if host == '0.0.0.0': host = '127.0.0.1'
        
        # Build an URL for XML-RPC requests
        rpcUrl = 'http://%s:%s@%s:%s/xmlrpc' % (username, password, host, port);
        
        # Create remote server object
        server = ServerProxy(rpcUrl)
        
###### Non EMail.py
        
        groups = server.listgroups(0)
        
        # Find correct nzb in method listgroups 
        nzbID = int(os.environ['NZBPP_NZBID'])
        for i in xrange(len(groups)):
                nzbGroup = groups[i]
                if nzbGroup['NZBID'] == nzbID:
                        break
        
        # add average download speed
        if os.environ['NZBPO_AVEDOWNSPEED'] == 'yes':
                DownloadedSizeMB = float(nzbGroup['DownloadedSizeMB'])
                DownloadTimeSec = float(nzbGroup['DownloadTimeSec'])
                if DownloadTimeSec > 0: # check x/0 errors
                        avespeed = (DownloadedSizeMB/DownloadTimeSec) # MB/s
                        unit = ' MB/s'
                        if avespeed < 1:
                                avespeed = avespeed * 1024 # KB/s
                                unit = ' KB/s'
                        text += '\nAverage download speed: %.1f' % (avespeed) + unit
        
        # add download size
        if os.environ['NZBPO_DOWNLOADSIZE'] == 'yes':                                                           
                DownloadedSize = float(nzbGroup['DownloadedSizeMB'])
                unit = ' MB'
                if DownloadedSize > 1024:
                        DownloadedSize = DownloadedSize / 1024 # GB
                        unit = ' GB'
                text += '\nDownload size: %.1f' % (DownloadedSize) + unit
        
        # add DownloadTime
        if os.environ['NZBPO_DOWNLOADTIME'] == 'yes':           
                DownloadTimeSec = int(nzbGroup['DownloadTimeSec'])
                Hour = DownloadTimeSec/3600
                Min = (DownloadTimeSec - (DownloadTimeSec/3600)*3600)/60
                Sec = (DownloadTimeSec - (DownloadTimeSec/3600)*3600)%60
                text += '\nDownload time: %d:%02d:%02d' % (Hour,Min,Sec)
                
        # add ParTime
        if os.environ['NZBPO_PARTIME'] == 'yes':
                ParTimeSec = int(nzbGroup['ParTimeSec'])
                Hour = ParTimeSec/3600
                Min = (ParTimeSec - (ParTimeSec/3600)*3600)/60
                Sec = (ParTimeSec - (ParTimeSec/3600)*3600)%60
                text += '\nPar time: %d:%02d:%02d' % (Hour,Min,Sec)
                
        # add RepairTime
        if os.environ['NZBPO_REPAIRTIME'] == 'yes':
                RepairTimeSec = int(nzbGroup['RepairTimeSec'])
                Hour = RepairTimeSec/3600
                Min = (RepairTimeSec - (RepairTimeSec/3600)*3600)/60
                Sec = (RepairTimeSec - (RepairTimeSec/3600)*3600)%60
                text += '\nRepair time: %d:%02d:%02d' % (Hour,Min,Sec)
        
        # add UnpackTime
        if os.environ['NZBPO_UNPACKTIME'] == 'yes':
                UnpackTimeSec = int(nzbGroup['UnpackTimeSec'])
                Hour = UnpackTimeSec/3600
                Min = (UnpackTimeSec - (UnpackTimeSec/3600)*3600)/60
                Sec = (UnpackTimeSec - (UnpackTimeSec/3600)*3600)%60
                text += '\nUnpack time: %d:%02d:%02d' % (Hour,Min,Sec)
                
        # add PP script, renaming and moving total time
        if os.environ['NZBPO_PPTIME'] == 'yes':
                PostTotalTimeSec = int(nzbGroup['PostTotalTimeSec']) - \
                        int(nzbGroup['ParTimeSec']) - \
                        int(nzbGroup['UnpackTimeSec'])
                if PostTotalTimeSec < 0: # if -ve, script may not be on first run after download
                        PostTotalTimeSec = int(nzbGroup['PostTotalTimeSec'])
                Hour = PostTotalTimeSec/3600
                Min = (PostTotalTimeSec - (PostTotalTimeSec/3600)*3600)/60
                Sec = (PostTotalTimeSec - (PostTotalTimeSec/3600)*3600)%60
                text += '\nPP script, renaming and moving total time: %d:%02d:%02d' % (Hour,Min,Sec)
        
        # add TotalTime
        if os.environ['NZBPO_TOTALTIME'] == 'yes':
                TotalTimeSec = int(nzbGroup['DownloadTimeSec']) + \
                        int(nzbGroup['PostTotalTimeSec'])
                Hour = TotalTimeSec/3600
                Min = (TotalTimeSec - (TotalTimeSec/3600)*3600)/60
                Sec = (TotalTimeSec - (TotalTimeSec/3600)*3600)%60
                text += '\nTotal time: %d:%02d:%02d' % (Hour,Min,Sec)
                
                # print server.listgroups(0)[0]['TotalTimeSec']
                
###### EMail.py
        
        # add list of downloaded files
        if os.environ['NZBPO_FILELIST'] == 'yes':
                text += '\n\nFiles:'
                for dirname, dirnames, filenames in os.walk(os.environ['NZBPP_DIRECTORY']):
                        for filename in filenames:
                                text += '\n' + os.path.join(dirname, filename)[len(os.environ['NZBPP_DIRECTORY']) + 1:]
                                
        # add _brokenlog.txt (if exists)
        if os.environ['NZBPO_BROKENLOG'] == 'yes':
                brokenlog = '%s/_brokenlog.txt' % os.environ['NZBPP_DIRECTORY']
                if os.path.exists(brokenlog):
                        text += '\n\nBrokenlog:\n' + open(brokenlog, 'r').read().strip()
        
        # add post-processing log
        if os.environ['NZBPO_POSTPROCESSLOG'] == 'Always' or \
                (os.environ['NZBPO_POSTPROCESSLOG'] == 'OnFailure' and not success):
                postqueue = server.postqueue(10000)
                        
                # Get field 'Log' from the first post-processing job
                log = postqueue[0]['Log']
                
                # Now iterate through entries and save them to message text
                if len(log) > 0:
                        text += '\n\nPost-processing log:';
                        for entry in log:
                                text += '\n%s\t%s\t%s' % (entry['Kind'], datetime.datetime.fromtimestamp(int(entry['Time'])), entry['Text'])

# Send message

print('[DETAIL] Sending to Pushbullet')
sys.stdout.flush()
newstr = text.replace("(", "") # these brackets cause errors with cURL, don't know why, occurs with CP entries
text = newstr.replace(")", "")

try:
        password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
        password_mgr.add_password(None, os.environ['NZBPO_URL'], os.environ['NZBPO_ACCESSTOKEN'], '')
        handler = urllib2.HTTPBasicAuthHandler(password_mgr)
        opener = urllib2.build_opener(handler)
        opener.open(os.environ['NZBPO_URL'])
        urllib2.install_opener(opener)       
        
        device_id = os.environ['NZBPO_DEVICEID'].split( )
        if len( device_id ) > 0:
                for i in device_id:
                        values = {'device_iden' : i,
                                  'title' : os.environ['NZBPO_TITLE'] + subject,
                                  'body' : text,
                                  'type' : os.environ['NZBPO_TYPE'] }
                        data = urllib.urlencode(values)
                        req = urllib2.Request(os.environ['NZBPO_URL'], data)
                        urllib2.urlopen(req)
        else:
                values = {'title' : os.environ['NZBPO_TITLE'] + subject,
                          'body' : text,
                          'type' : os.environ['NZBPO_TYPE'] }
                data = urllib.urlencode(values)
                req = urllib2.Request(os.environ['NZBPO_URL'], data)
                urllib2.urlopen(req)
        print('[DETAIL] Sent to Pushbullet')
except Exception as err:
        print('[ERROR] %s' % err)
        sys.exit(POSTPROCESS_ERROR)

# All OK, returning exit status 'POSTPROCESS_SUCCESS' (int <93>) to let NZBGet know
# that our script has successfully completed.
sys.exit(POSTPROCESS_SUCCESS)