Skip to content

Commit

Permalink
Merge pull request #35 from FlyingDiver/main
Browse files Browse the repository at this point in the history
Indigo 2022.1 update
  • Loading branch information
FlyingDiver authored May 11, 2022
2 parents c9e0c3c + e946a2a commit 9df6110
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 167 deletions.
4 changes: 2 additions & 2 deletions Pushover.indigoPlugin/Contents/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<plist version="1.0">
<dict>
<key>PluginVersion</key>
<string>1.7.1</string>
<string>2022.0.0</string>
<key>ServerApiVersion</key>
<string>2.0</string>
<string>3.0</string>
<key>IwsApiVersion</key>
<string>1.0.0</string>
<key>CFBundleDisplayName</key>
Expand Down
7 changes: 1 addition & 6 deletions Pushover.indigoPlugin/Contents/Server Plugin/Actions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@
<Name>Send Pushover Notification</Name>
<CallbackMethod>send</CallbackMethod>
<ConfigUI>
<Field id="appToken" type="menu">
<Label>App Token:</Label>
<List method="get_app_tokens" dynamicReload="true" class="self" filter=""/>
<CallbackMethod>menuChanged</CallbackMethod>
</Field>
<Field id="msgTitle" type="textfield" default="">
<Label>Title:</Label>
</Field>
Expand Down Expand Up @@ -101,7 +96,7 @@
<Name>Cancel Pushover Emergency-Priority Notification</Name>
<CallbackMethod>cancel</CallbackMethod>
<ConfigUI>
<Field id="cancelTag" type="textfield" default="">
<Field id="cancelTag" type="textfield" default="">
<Label>Tag:</Label>
</Field>
<Field id="hlpCancelTag" type="label" fontSize="mini" alignWithControl="true">
Expand Down
33 changes: 0 additions & 33 deletions Pushover.indigoPlugin/Contents/Server Plugin/MenuItems.xml
Original file line number Diff line number Diff line change
@@ -1,35 +1,2 @@
<MenuItems>
<MenuItem id="manageTokens">
<Name>Manage App Tokens...</Name>
<ConfigUI>
<Field id="appName" type="textfield">
<Label>App Name:</Label>
</Field>
<Field id="appToken" type="textfield">
<Label>App Token:</Label>
</Field>
<Field id="hlpApiToken" type="label" fontSize="mini" alignWithControl="true">
<Label>Created at https://pushover.net/apps/clone/indigo_domotics</Label>
</Field>
<Field id="addToken" type="button">
<Label/>
<Title>Add/Update Token</Title>
<CallbackMethod>addToken</CallbackMethod>
</Field>
<Field id="appTokenList" type="list" rows="15">
<Label>Available App Tokens:</Label>
<List class="self" method="get_app_tokens" dynamicReload="true"/>
</Field>
<Field id="deleteTokens" type="button">
<Label/>
<Title>Delete Tokens</Title>
<CallbackMethod>deleteTokens</CallbackMethod>
</Field>
</ConfigUI>
</MenuItem>
<MenuItem id="listTokens">
<CallbackMethod>listTokens</CallbackMethod>
<Name>Print App Token List to Log</Name>
</MenuItem>

</MenuItems>
4 changes: 2 additions & 2 deletions Pushover.indigoPlugin/Contents/Server Plugin/PluginConfig.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0"?>
<PluginConfig>
<Field id="apiToken" type="textfield" default="" hidden="true">
<Field id="apiToken" type="textfield" default="">
<Label>API Token:</Label>
</Field>
<Field id="hlpApiToken" type="label" fontSize="mini" alignWithControl="true" hidden="true">
<Field id="hlpApiToken" type="label" fontSize="mini" alignWithControl="true">
<Label>Required, can be created at https://pushover.net/apps/clone/indigo_domotics</Label>
</Field>
<Field id="userKey" type="textfield" default="">
Expand Down
168 changes: 51 additions & 117 deletions Pushover.indigoPlugin/Contents/Server Plugin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,47 @@
import json
from collections import OrderedDict


class Plugin(indigo.PluginBase):

def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs):
indigo.PluginBase.__init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs)
try:
self.logLevel = int(self.pluginPrefs[u"logLevel"])
except:
self.logLevel = logging.INFO
self.logLevel = int(pluginPrefs.get("logLevel", logging.INFO))
self.indigo_log_handler.setLevel(self.logLevel)
self.logger.debug(u"logLevel = {}".format(self.logLevel))
self.logger.debug(f"logLevel = {self.logLevel}")

self.sounds = None

def __del__(self):
indigo.PluginBase.__del__(self)

def startup(self):
self.logger.debug(u"startup called")

savedList = self.pluginPrefs.get(u"appTokens", None)
if savedList:
self.appTokenList = json.loads(savedList)
else:
self.appTokenList = {}
self.apiToken = pluginPrefs.get('apiToken', None)
if not self.apiToken:
self.logger.warning(f"PI Token not configured")

def startup(self):
self.logger.debug(f"startup called")
try:
appToken = self.pluginPrefs['apiToken']
r = requests.get("https://api.pushover.net/1/sounds.json?token={}".format(appToken))
customdecoder = json.JSONDecoder(object_pairs_hook=OrderedDict)
r = requests.get(f"https://api.pushover.net/1/sounds.json?token={self.apiToken}")
customdecoder = json.JSONDecoder(object_hook=OrderedDict)
rdict = customdecoder.decode(r.text)
self.sounds = rdict['sounds']
except Exception as err:
self.logger.warning(u"Error getting alert sounds list: {}".format(err))
self.sounds = OrderedDict([(u'pushover', u'Pushover (default)'), (u'bike', u'Bike'), (u'bugle', u'Bugle'), (u'cashregister', u'Cash Register'), (u'classical', u'Classical'), (u'cosmic', u'Cosmic'), (u'falling', u'Falling'), (u'gamelan', u'Gamelan'), (u'incoming', u'Incoming'), (u'intermission', u'Intermission'), (u'magic', u'Magic'), (u'mechanical', u'Mechanical'), (u'pianobar', u'Piano Bar'), (u'siren', u'Siren'), (u'spacealarm', u'Space Alarm'), (u'tugboat', u'Tug Boat'), (u'alien', u'Alien Alarm (long)'), (u'climb', u'Climb (long)'), (u'persistent', u'Persistent (long)'), (u'echo', u'Pushover Echo (long)'), (u'updown', u'Up Down (long)'), (u'vibrate', u'Vibrate Only'), (u'none', u'None (silent)')])
self.logger.debug(u"Sounds = {}".format(self.sounds))



self.logger.warning(f"Error getting alert sounds list: {err}")
self.sounds = OrderedDict(
[(u'pushover', u'Pushover (default)'), (u'bike', u'Bike'), (u'bugle', u'Bugle'), (u'cashregister', u'Cash Register'),
(u'classical', u'Classical'), (u'cosmic', u'Cosmic'), (u'falling', u'Falling'), (u'gamelan', u'Gamelan'), (u'incoming', u'Incoming'),
(u'intermission', u'Intermission'), (u'magic', u'Magic'), (u'mechanical', u'Mechanical'), (u'pianobar', u'Piano Bar'),
(u'siren', u'Siren'), (u'spacealarm', u'Space Alarm'), (u'tugboat', u'Tug Boat'), (u'alien', u'Alien Alarm (long)'),
(u'climb', u'Climb (long)'), (u'persistent', u'Persistent (long)'), (u'echo', u'Pushover Echo (long)'),
(u'updown', u'Up Down (long)'), (u'vibrate', u'Vibrate Only'), (u'none', u'None (silent)')])
self.logger.debug(f"Sounds = {self.sounds}")

def shutdown(self):
self.logger.debug(u"shutdown called")
self.logger.debug("shutdown called")

def get_sound_list(self, filter="", valuesDict=None, typeId="", targetId=0):
returnList = list()
for name in self.sounds:
returnList.append((name, self.sounds[name]))
self.logger.debug(u"get_sound_list = {}".format(returnList))
return returnList

def validateActionConfigUi(self, valuesDict, typeId, deviceId):
Expand All @@ -60,52 +56,47 @@ def validateActionConfigUi(self, valuesDict, typeId, deviceId):
if typeId == "send":
if not self.present(valuesDict.get("msgBody")):
errorDict["msgBody"] = "Cannot be blank"
return (False, valuesDict, errorDict)
elif typeId == "cancel":
if not self.present(valuesDict.get("cancelTag")):
errorDict["cancelTag"] = "Cannot be blank"
return (False, valuesDict, errorDict)

return (True, valuesDict, errorDict)
if len(errorDict):
return False, valuesDict, errorDict
else:
return True, valuesDict, errorDict

def present(self, prop):
return (prop and prop.strip() != "")
@staticmethod
def present(prop):
return prop and prop.strip() != ""

# helper functions
def prepareTextValue(self, strInput):

if not strInput:
return strInput
return strInput
return self.substitute(strInput.strip())


# actions go here
def send(self, pluginAction):
self.logger.debug(u"send pluginAction.props = {}".format(pluginAction.props))

appToken = pluginAction.props.get('appToken', None)
if not appToken:
appToken = self.pluginPrefs['apiToken']
self.logger.debug(u"appToken = {}".format(appToken))
self.logger.threaddebug(f"send pluginAction.props = {pluginAction.props}")

msgBody = self.prepareTextValue(pluginAction.props['msgBody'])

#fill params dictionary with required values
# fill params dictionary with required values
params = {
'token': appToken.strip(),
'token': self.apiToken,
'user': self.pluginPrefs['userKey'].strip(),
'message': msgBody
}

attachment = {}

#populate optional parameters
# populate optional parameters
if self.present(pluginAction.props.get('msgTitle')):
msgTitle = self.prepareTextValue(pluginAction.props['msgTitle']).strip()
params['title'] = msgTitle
else:
msgTitle = u''

msgTitle = ''

if self.present(pluginAction.props.get('msgDevice')):
params['device'] = pluginAction.props['msgDevice'].strip()
Expand All @@ -132,103 +123,46 @@ def send(self, pluginAction):
"attachment": (attachFile, open(attachFile, "rb"), "image/jpeg")
}
else:
self.logger.warn(u"Warning: attached file size was too large, attachment was skipped")
self.logger.warn("Warning: attached file size was too large, attachment was skipped")
else:
self.logger.warn(u"Warning: file does not exist, or is not an image file, attachment was skipped")
self.logger.warn("Warning: file does not exist, or is not an image file, attachment was skipped")

if self.present(pluginAction.props.get('msgPriority')):
params['priority'] = pluginAction.props['msgPriority']
if params['priority'] == 2 or params['priority'] == "2":
# Require Confirmation priority requires 2 additional params:
params['retry'] = "600" # show every 10 minutes until confirmted (could expose UI for this...)
params['retry'] = "600" # show every 10 minutes until confirmed (could expose UI for this...)
params['expire'] = "86400" # set expire to maximum (24 hours)

if self.present(pluginAction.props.get('msgTags')):
params['tags'] = self.prepareTextValue(pluginAction.props['msgTags'])

r = requests.post("https://api.pushover.net/1/messages.json", data = params, files = attachment)
r = requests.post("https://api.pushover.net/1/messages.json", data=params, files=attachment)

if r.status_code == 200:
self.logger.debug(u"Result: {}".format(r.text))
self.logger.info(u"Pushover notification was sent sucessfully, title: {}, body: {}".format(msgTitle, msgBody))
self.logger.debug(f"Result: {r.text}")
self.logger.info(f"Pushover notification was sent successfully, title: {msgTitle}, body: {msgBody}")
else:
self.logger.error(u"Post Error - Result: {}".format(r.text))

self.logger.error(f"Post Error - Result: {r.text}")

def cancel(self, pluginAction):

params = {'token': self.pluginPrefs['apiToken'].strip() }
URL = "https://api.pushover.net/1/receipts/cancel_by_tag/" + pluginAction.props['cancelTag'] + ".json"
r = requests.post(URL, data = params)
self.logger.debug(u"Result: %s" % r.text)

########################################
# This is the method that's called by the Add Token button in the menu dialog.
########################################

def addToken(self, valuesDict, typeId=None, devId=None):

appName = valuesDict["appName"]
appToken = valuesDict["appToken"]

tokenItem = {"name" : appName, "token" : appToken}
self.logger.debug(u"Adding Token {}: {}".format(appName, appToken))
self.appTokenList[appName] = appToken
self.listTokens()

indigo.activePlugin.pluginPrefs[u"appTokens"] = json.dumps(self.appTokenList)

return valuesDict

########################################
# This is the method that's called by the Delete Token button
########################################
def deleteTokens(self, valuesDict, typeId=None, devId=None):

for item in valuesDict["appTokenList"]:
self.logger.info(u"Deleting Token {}".format(item))
del self.appTokenList[item]

self.listTokens()
indigo.activePlugin.pluginPrefs[u"appTokens"] = json.dumps(self.appTokenList)


def get_app_tokens(self, filter="", valuesDict=None, typeId="", targetId=0):
returnList = list()
for name in self.appTokenList:
returnList.append((self.appTokenList[name], name))
self.logger.debug(u"get_app_tokens = {}".format(returnList))
return sorted(returnList, key= lambda item: item[1])


########################################

def listTokens(self):
if len(self.appTokenList) == 0:
self.logger.info(u"No App Tokens Devices")
return

fstring = u"{:20} {:^50}"
self.logger.info(fstring.format("App Name", "App Token"))
for name, token in self.appTokenList.iteritems():
self.logger.info(fstring.format(name, token))
params = {'token': self.apiToken}
URL = f"https://api.pushover.net/1/receipts/cancel_by_tag/{pluginAction.props['cancelTag']}.json"
r = requests.post(URL, data=params)
self.logger.debug(f"Result: {r.text}")

########################################
# ConfigUI methods
########################################

def closedPrefsConfigUi(self, valuesDict, userCancelled):
if not userCancelled:
try:
self.logLevel = int(valuesDict[u"logLevel"])
except:
self.logLevel = logging.INFO
self.logLevel = int(valuesDict.get("logLevel", logging.INFO))
self.indigo_log_handler.setLevel(self.logLevel)
self.logger.debug(u"logLevel = {}".format(self.logLevel))

# doesn't do anything, just needed to force other menus to dynamically refresh
self.logger.debug(f"logLevel = {self.logLevel}")

def menuChanged(self, valuesDict, typeId, devId):
# doesn't do anything, just needed to force other menus to dynamically refresh
@staticmethod
def menuChanged(valuesDict, typeId, devId):
return valuesDict


16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
indigo-pushover
===============

[Indigo](http://www.indigodomo.com/) plugin - send push notifications to mobile devices via [Pushover](http://www.pushover.net).
[Indigo](http://www.indigodomo.com/) plugin - send push notifications via [Pushover](http://www.pushover.net).

| Requirement | | |
|------------------------|---------------------|---|
| Minimum Indigo Version | 2022.1 | |
| Python Library (API) | Official | |
| Requires Local Network | No | |
| Requires Internet | Yes | |
| Hardware Interface | None | |

### Requirements

1. [Indigo 6](http://www.indigodomo.com/) or later (pro version only)
2. Valid Pushover [API key](https://pushover.net/apps/clone/indigo_domotics)
3. Valid Pushover [user key](https://pushover.net/faq#overview-what)

### Installation Instructions

1. Download latest release [here](https://github.com/IndigoDomotics/indigo-pushover/releases)
2. Follow [standard plugin installation process](http://bit.ly/1e1Vc7b)

### Actions Supported
* Send Pushover Notification
* customize title, message (supports variables)
Expand Down

0 comments on commit 9df6110

Please sign in to comment.