def format_list_column_entry (self, columndefs, columndata): """ Return a list item formatted to comply with the column definitions. Arguments: columndefs: (list) maximum spaces per column columndata: (list) the data to enter into the columns Returns: list representative of the column configuration """ try: colidx = 0 output = "" for c in columndata : length = columndefs[colidx] if len(str(c)) > (length): # Shorten the value so it fits, it should be tabs * 4 - 1 to leave a space between columns x = (length) - 1 # Shorten by one extra for space between columns x = x - 3 # Shorted by 3 more so we can add '...' to let the user know its been truncated c = c[0:x] + "..." column = u"{}".format(c).ljust(length) output += column colidx = colidx + 1 return output except Exception as e: self.logger.error (ex.stack_trace(e))
def list_extensions(self, filter="", valuesDict=None, typeId="", targetId=0): """ Return a custom list of extensions - will not include extensions that don't have a userman account so it may be incomplete. """ try: listData = [("default", "No data")] if not 'server' in valuesDict: return listData if valuesDict['server'] == '': return listData server = indigo.devices[int(valuesDict['server'])] result = self.invoke_api(server, 'userman', '', '', 'extensions') if result: listData = [] for extension_num, userman in result.iteritems(): listData.append((extension_num, extension_num)) except Exception as e: self.logger.error(ex.stack_trace(e)) return listData
def update_state(self, dev): try: status = self.update_address_status(dev) keyValueList = [] onOffState = True # Assume on until decided otherwise # Only analyze conditions that will set the onstate to off if dev.pluginProps['ison'] == 'notready' and status == 'Ready': onOffState = False elif dev.pluginProps['ison'] == 'ready' and status != 'Ready': onOffState = False elif dev.pluginProps['ison'] == 'dnd' and 'DND ' not in status: onOffState = False elif dev.pluginProps['ison'] == 'notdnd' and 'DND ' in status: onOffState = False elif dev.pluginProps['ison'] == 'cf' and 'CF ' not in status: onOffState = False elif dev.pluginProps['ison'] == 'notcf' and 'CF ' in status: onOffState = False elif dev.pluginProps['ison'] == 'cfu' and 'CFU ' not in status: onOffState = False elif dev.pluginProps['ison'] == 'notcfu' and 'CFU ' in status: onOffState = False elif dev.pluginProps['ison'] == 'cfb' and 'CFB ' not in status: onOffState = False elif dev.pluginProps['ison'] == 'notcfb' and 'CFB ' in status: onOffState = False keyValueList.append({'key': 'onOffState', 'value': onOffState}) dev.updateStatesOnServer(keyValueList) except Exception as e: self.logger.error(ex.stack_trace(e))
def actionControlDimmerRelay(self, action, dev): try: if dev.deviceTypeId == 'CallFlow': return self.device_control_flow(action, dev) except Exception as e: self.logger.error(ex.stack_trace(e))
def action_dnd(self, action): try: method = 'donotdisturb' dev = indigo.devices[action.deviceId] server = int(dev.pluginProps["server"]) if not server in indigo.devices: self.logger.error( u"PBX Server {} is not in the Indigo device list, was it removed? {} action cannot complete" .format(server, dev.name)) return server = indigo.devices[server] params = {} if action.pluginTypeId == 'dndEnable': params["status"] = 'enabled' else: params["status"] = False result = self.invoke_api(server, method, dev.pluginProps["extension"], json.dumps(params)) self.update_extension_status(dev) except Exception as e: self.logger.error(ex.stack_trace(e))
def add_record (self, record, valuesDict): """ Add record to the current record name that was passed on initialization. Arguments: record: (list) field data in the same order as the structure passed during init valuesDict (dict) dictionary of form values """ try: self.set_form_records (valuesDict) rec = {} rec["jkey"] = self.create_unique_key() for i in range (0, len(record)): rec[self.structure[i]] = record[i] self.records.append (rec) valuesDict = self.apply_form_records(valuesDict) except Exception as e: self.logger.error (ex.stack_trace(e)) return valuesDict
def apply_form_records (self, valuesDict): """ JSON encode the current record set into the larger record set and save it to valuesDict. Arguments: valuesDict (dict) dictionary of form values to read from and write to Returns: dict valuesDict modified """ try: currentrecords = self.records self.set_form_records (valuesDict) # Always refresh in case another process updated a record if not JSON_FIELD in valuesDict: allrecords = {} valuesDict[JSON_FIELD] = json.dumps(allrecords) allrecords = json.loads(valuesDict[JSON_FIELD]) allrecords[self.recordName] = currentrecords valuesDict[JSON_FIELD] = json.dumps(allrecords) except Exception as e: self.logger.error (ex.stack_trace(e)) return valuesDict
def apply_form_records(self, valuesDict): """ JSON encode the current record set into the larger record set and save it to valuesDict. Arguments: valuesDict (dict) dictionary of form values to read from and write to Returns: dict valuesDict modified """ try: currentrecords = self.records self.set_form_records( valuesDict ) # Always refresh in case another process updated a record if not JSON_FIELD in valuesDict: allrecords = {} valuesDict[JSON_FIELD] = json.dumps(allrecords) allrecords = json.loads(valuesDict[JSON_FIELD]) allrecords[self.recordName] = currentrecords valuesDict[JSON_FIELD] = json.dumps(allrecords) except Exception as e: self.logger.error(ex.stack_trace(e)) return valuesDict
def __init__(self, plugin, name = '', structure = [], obj = None): """ Start the factory with references back to the base plugin. Arguments: plugin: Indigo plugin name: (str) JSON record name structure: (list) JSON record structure """ try: if plugin is None: return # pre-loading before init self.logger = logging.getLogger ("Plugin.{}".format(self.__class__.__name__.replace("_",""))) self.plugin = plugin # References the Indigo plugin self.records = [] # All JSON records for this type if not "jkey" in structure: structure.append("jkey") self.structure = structure # Record structure for this data type self.recordName = name self.obj = obj self.logger.debug ("{} {} loaded".format(__modname__, __version__)) except Exception as e: self.logger.error (ex.stack_trace(e))
def add_record(self, record, valuesDict): """ Add record to the current record name that was passed on initialization. Arguments: record: (list) field data in the same order as the structure passed during init valuesDict (dict) dictionary of form values """ try: self.set_form_records(valuesDict) rec = {} rec["jkey"] = self.create_unique_key() for i in range(0, len(record)): rec[self.structure[i]] = record[i] self.records.append(rec) valuesDict = self.apply_form_records(valuesDict) except Exception as e: self.logger.error(ex.stack_trace(e)) return valuesDict
def __init__(self, plugin, name='', structure=[], obj=None): """ Start the factory with references back to the base plugin. Arguments: plugin: Indigo plugin name: (str) JSON record name structure: (list) JSON record structure """ try: if plugin is None: return # pre-loading before init self.logger = logging.getLogger("Plugin.{}".format( self.__class__.__name__.replace("_", ""))) self.plugin = plugin # References the Indigo plugin self.records = [] # All JSON records for this type if not "jkey" in structure: structure.append("jkey") self.structure = structure # Record structure for this data type self.recordName = name self.obj = obj self.logger.debug("{} {} loaded".format(__modname__, __version__)) except Exception as e: self.logger.error(ex.stack_trace(e))
def ssh (self, props, activity): """ Establish SSH to remote computer and return the prompt. """ try: if not props["localhost"]: s = pxssh.pxssh() if not s.login (props["computerip"], props["username"], props["password"]): self.logger.error ("SSH session failed to login to '{}'. Check your IP, username and password and make sure you can SSH to that computer from the Indigo server.".format(props['credentials'][0])) return False #rootprompt = re.compile('.*[$#]') #i = self.expect(["(?i)are you sure you want to continue connecting", "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", "(?i)connection closed by remote host"], timeout=20) #indigo.server.log(u'{}'.format(i)) s.sendline('sudo -s') s.sendline(props["password"]) return s else: self.logger.error ("{} the Indigo computer is not currently supported".format(activity)) except Exception as e: self.logger.error (ex.stack_trace(e)) return None
def list_call_flows(self, filter="", valuesDict=None, typeId="", targetId=0): """ Build a custom list based on the information in the filter field. """ try: listData = [("default", "No data")] if not 'server' in valuesDict: return listData if valuesDict['server'] == '': return listData server = indigo.devices[int(valuesDict['server'])] result = self.invoke_api(server, 'daynight', '', '', '') if result: listData = [] for r in result: listData.append((r['ext'], r['dest'])) except Exception as e: self.logger.error(ex.stack_trace(e)) return listData
def update_address(self, dev): try: if dev.deviceTypeId == 'Server': props = dev.pluginProps props['address'] = props['ipaddress'] dev.replacePluginPropsOnServer(props) if dev.deviceTypeId == 'CallFlow': server = int(dev.pluginProps["server"]) if not server in indigo.devices: self.logger.error( u"PBX Server {} is not in the Indigo device list, was it removed? {} action cannot complete" .format(server, dev.name)) return server = indigo.devices[server] props = dev.pluginProps props['address'] = server.name dev.replacePluginPropsOnServer(props) if dev.deviceTypeId == 'Extension': server = int(dev.pluginProps["server"]) if not server in indigo.devices: self.logger.error( u"PBX Server {} is not in the Indigo device list, was it removed? {} action cannot complete" .format(server, dev.name)) return server = indigo.devices[server] props = dev.pluginProps if props['method'] == 'ext': props['address'] = u'Ext {} on {}'.format( props['extension'], server.name) elif props['method'] == 'status': props['address'] = self.update_address_status(dev) elif props['method'] == 'fwd': status = self.update_address_status(dev) if 'CF ' in status: status = u'CF to {}'.format( dev.states['cfunconditionalNumber']) elif 'CFU ' in status: status = u'CFU to {}'.format( dev.states['cfunavailableNumber']) elif 'CFB ' in status: status = u'CFB to {}'.format( dev.states['cfbusyNumber']) props['address'] = status dev.replacePluginPropsOnServer(props) self.update_state(dev) except Exception as e: self.logger.error(ex.stack_trace(e))
def sample_computer_data (self, dev): try: keyValueList = [] profile = self.sample_computer_profile(dev) #indigo.server.log(unicode(profile)) keyValueList.append({'key':'name', 'value':profile['Computer Name']}) keyValueList.append({'key':'model', 'value':u'{} ({})'.format(profile['Model Name'], profile['Model ID'])}) keyValueList.append({'key':'cpu_model', 'value':profile['CPU']}) keyValueList.append({'key':'cpu_speed', 'value':profile['CPU Speed']}) keyValueList.append({'key':'serial', 'value':profile['Serial Number']}) keyValueList.append({'key':'cpu', 'value':profile['CPU Utilization']}) keyValueList.append({'key':'cpu_count', 'value':profile['Processors']}) keyValueList.append({'key':'cpu_cores', 'value':profile['Cores']}) mem = psutil.virtual_memory() keyValueList.append({'key':'memory', 'value':mem.total / 1024 / 1024}) keyValueList.append({'key':'memory_available', 'value':mem.available / 1024 / 1024}) keyValueList.append({'key':'memory_percent', 'value':profile['Memory Utilization']}) disk = psutil.disk_usage('/') keyValueList.append({'key':'disk', 'value':disk.total / 1024 / 1024}) keyValueList.append({'key':'disk_available', 'value':disk.free / 1024 / 1024}) keyValueList.append({'key':'disk_percent', 'value':disk.percent}) # Not supported in the 1.x that comes with macOS, need to wait until a newer version is out #temps = psutil.sensors_temperatures() #fans = psutil.sensors_fans() #batt = psutil.sensors_battery() boot = datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S") keyValueList.append({'key':'last_boot', 'value':boot}) cmd = [ 'sw_vers', '-productVersion'] ver = subprocess.Popen( cmd, stdout=subprocess.PIPE ).communicate()[0] ver = ver.replace("\n","") keyValueList.append({'key':'macOS', 'value':ver}) keyValueList.append({'key':'last_sample', 'value':datetime.now().strftime("%Y-%m-%d %H:%M:%S")}) #/usr/sbin/system_profiler SPHardwareDataType | fgrep 'Serial' | awk '{print $NF}' if dev.pluginProps['state'] == 'cpu': keyValueList.append({'key':'sensorValue', 'value':profile['CPU Utilization'], 'uiValue': u'{}%'.format(profile['CPU Utilization'])}) if dev.pluginProps['state'] == 'memory_percent': keyValueList.append({'key':'sensorValue', 'value':profile['Memory Utilization'], 'uiValue': u'{}%'.format(profile['Memory Utilization'])}) props = dev.pluginProps props['address'] = u'CPU: {}% | Mem: {}%'.format(profile['CPU Utilization'], profile['Memory Utilization']) dev.replacePluginPropsOnServer (props) if keyValueList: dev.updateStatesOnServer(keyValueList) except Exception as e: self.logger.error (ex.stack_trace(e))
def format_list_column_entry_tabs (self, columndefs, columndata): """ Return a list item formatted to comply with the column definitions. Arguments: columndefs: (list) maximum tabs (4 spaces) per column columndata: (list) the data to enter into the columns Returns: list representative of the column configuration """ try: colidx = 0 output = "" for c in columndata : tabs = columndefs[colidx] if len(str(c)) > (tabs * 4): # Shorten the value so it fits, it should be tabs * 4 - 1 to leave a space between columns x = (tabs * 4) - 1 # Shorten by one extra for space between columns x = x - 3 # Shorted by 3 more so we can add '...' to let the user know its been truncated c = c[0:x] + "..." if len(str(c)) > 23: tabs = tabs - 6 elif len(str(c)) > 19: tabs = tabs - 5 elif len(str(c)) > 15: tabs = tabs - 4 elif len(str(c)) > 11: tabs = tabs - 3 elif len(str(c)) > 7: tabs = tabs - 2 elif len(str(c)) > 3: tabs = tabs - 1 if tabs < 1: tabs = 1 # Failsafe in case we got messed up tabstring = "" for i in range (0, tabs): tabstring += "\t" column = u"{}{}".format(c, tabstring) output += column colidx = colidx + 1 return output except Exception as e: self.logger.error (ex.stack_trace(e))
def commander_field_changed (self, valuesDict, typeId, devId = ''): """ Triggers form actions. """ try: errorsDict = indigo.Dict() except Exception as e: self.logger.error (ex.stack_trace(e)) return (valuesDict, errorsDict)
def device_field_changed(self, valuesDict, typeId, devId): """ Callback method invoked on device forms, primary method. """ try: errorsDict = indigo.Dict() except Exception as e: self.logger.error(ex.stack_trace(e)) return (valuesDict, errorsDict)
def get_server_extensions(self, dev): try: method = 'userman' params = {} extensions = self.invoke_api(dev, method) for e in extensions: for field, info in e.iteritems(): indigo.server.log(unicode(u"{}: {}".format(field, info))) except Exception as e: self.logger.error(ex.stack_trace(e))
def shutdown_computer (self, dev, method, props = {}): try: if not props: props = dev.pluginProps s = self.ssh (props, "Shutting down") if s: s.sendline ('sudo shutdown -h now') s.prompt() s.logout() except Exception as e: self.logger.error (ex.stack_trace(e))
def clear_form_records(self, valuesDict): """ Empty the current record name in the JSON records and start it fresh. """ try: self.set_form_records(valuesDict) self.records = [] self.apply_form_records(valuesDict) except Exception as e: self.logger.error(ex.stack_trace(e)) return valuesDict
def clear_form_records (self, valuesDict): """ Empty the current record name in the JSON records and start it fresh. """ try: self.set_form_records (valuesDict) self.records = [] self.apply_form_records (valuesDict) except Exception as e: self.logger.error (ex.stack_trace(e)) return valuesDict
def get_saved_credential (self, name = '', ip = ''): try: credentials = self.pluginPrefs['credentials'] for c in credentials: item = base64.b64decode(c).split("||") if not name == '' and item[0] == name: return item elif not ip == '' and item[1] == ip: return item return False except Exception as e: self.logger.error (ex.stack_trace(e))
def sleepComputer (self, dev, method, props = {}): #cmd = " -e 'sleep'" #cmd = self.encapsulateCmd (dev, cmd, "Finder", "Finder", props) #result = self.runOsa (cmd) try: s = self.ssh (props, "Sleeping") if s: s.sendline ('pmset sleepnow') s.prompt() s.logout() except Exception as e: self.logger.error (ex.stack_trace(e))
def validateDeviceConfigUi (self, valuesDict, typeId, devId): try: errorDict = indigo.Dict() dev = indigo.devices[devId] # While we are here add this device to polling if that is enabled if typeId == 'maccmd' or typeId == 'epsmc': self.configurePolling (dev, valuesDict) self.configurePollingMusic (dev, valuesDict) #indigo.server.log(unicode(self.itunespollinglist)) except Exception as e: self.logger.error (ex.stack_trace(e)) return (True, valuesDict, errorDict)
def getActionConfigUiValues(self, valuesDict, typeId, devId): """ Indigo function called prior to the form loading in case we need to do pre-calculations. """ try: errorsDict = indigo.Dict() # Set new device defaults if not 'credentials' in valuesDict: valuesDict['credentials'] = 'manual' if not 'onCommand' in valuesDict: valuesDict['onCommand'] = 'runapp' except Exception as e: self.logger.error (ex.stack_trace(e)) return (valuesDict, errorsDict)
def device_control_flow(self, action, dev): try: keyValueList = [] dev = indigo.devices[action.deviceId] server = int(dev.pluginProps["server"]) if not server in indigo.devices: self.logger.error( u"PBX Server {} is not in the Indigo device list, was it removed? {} action cannot complete" .format(server, dev.name)) return server = indigo.devices[server] params = {} forceState = '' if action.deviceAction == indigo.kDimmerRelayAction.Toggle: if dev.onState: forceState = 'off' else: forceState = 'on' if action.deviceAction == indigo.kDimmerRelayAction.TurnOn or forceState == 'on': params["state"] = 'NIGHT' keyValueList.append({'key': 'onOffState', 'value': True}) elif action.deviceAction == indigo.kDimmerRelayAction.TurnOff or forceState == 'off': params["state"] = 'DAY' keyValueList.append({'key': 'onOffState', 'value': False}) elif unicode(action.deviceAction) == u'RequestStatus': self.update_call_flow_status(dev) return else: indigo.server.error(u'Unknown Call Flow action {}'.format( action.deviceAction)) return result = self.invoke_api(server, 'daynight', dev.pluginProps['callflow'], json.dumps(params), '') self.update_call_flow_status(dev) if keyValueList: dev.updateStatesOnServer(keyValueList) except Exception as e: self.logger.error(ex.stack_trace(e))
def actionControlDevice(self, action, dev): try: if dev.deviceTypeId == 'maccmd': if action.deviceAction == indigo.kDimmerRelayAction.TurnOn: self.command_turn_on(dev) elif action.deviceAction == indigo.kDimmerRelayAction.TurnOff: self.command_turn_off(dev) elif action.deviceAction == indigo.kDimmerRelayAction.Toggle: if dev.onState: self.command_turn_off(dev) else: self.command_turn_on(dev) except Exception as e: self.logger.error (ex.stack_trace(e))
def update_computer (self, dev, method, props = {}): try: if not props: props = dev.pluginProps s = self.ssh (props, "Update") if s: s.sendline ('sudo softwareupdate -iaR') try: s.prompt(timeout=1200) s.logout() except Exception as ex: pass # if we rebooted then it may time out except Exception as e: self.logger.error (ex.stack_trace(e))
def list_computer_states (self, filter="", valuesDict=None, typeId="", targetId=0): """ Return the list of computer states. """ try: listData = [] listData.append (('cpu', "CPU Usage")) listData.append (('memory_available', "Memory Available")) listData.append (('memory_percent', "Memory Used %")) listData.append (('disk_available', "Disk Available")) listData.append (('disk_percent', "Disk Used %")) except Exception as e: self.logger.error (ex.stack_trace(e)) return listData
def _create_hash_key (self, keyString): """ Create a 256K hash key from the provided string. Aguments: keyString: (str) string to use for unique hash value Returns: string value of hashed keyString """ try: hashKey = hashlib.sha256(keyString.encode('ascii', 'ignore')).digest().encode("hex") # [0:16] return hashKey except Exception as e: self.logger.error (ex.stack_trace(e)) return ""
def create_unique_key (self): """ Create a unique identifier (key) using the date and time to the millisecond plus a random number to ensure uniqueness. Returns: string value of hashed keyString """ try: d = indigo.server.getTime() key = self._create_hash_key (d.strftime("%Y-%m-%d %H:%M:%S %f") + str(randint(1000, 1000001))) key = key[0:16] return key except Exception as e: self.logger.error (ex.stack_trace(e)) return ""
def _create_hash_key(self, keyString): """ Create a 256K hash key from the provided string. Aguments: keyString: (str) string to use for unique hash value Returns: string value of hashed keyString """ try: hashKey = hashlib.sha256(keyString.encode( 'ascii', 'ignore')).digest().encode("hex") # [0:16] return hashKey except Exception as e: self.logger.error(ex.stack_trace(e)) return ""
def action_cf(self, action): try: method = 'callforward' dev = indigo.devices[action.deviceId] server = int(dev.pluginProps["server"]) if not server in indigo.devices: self.logger.error( u"PBX Server {} is not in the Indigo device list, was it removed? {} action cannot complete" .format(server, dev.name)) return server = indigo.devices[server] params = {} if action.pluginTypeId == 'callForwarding': if action.props['cfenabled']: params['CF'] = int(action.props['cfnumber']) if action.props['cfuenabled']: params['CFU'] = int(action.props['cfunumber']) if action.props['cfuenabled']: params['CFB'] = int(action.props['cfbnumber']) elif action.pluginTypeId == 'cfDisableAll': params['CF'] = False params['CFB'] = False params['CFU'] = False elif action.pluginTypeId == 'cfDisableUC': params['CF'] = False elif action.pluginTypeId == 'cfDisableBusy': params['CFB'] = False elif action.pluginTypeId == 'cfDisableUA': params['CFU'] = False #indigo.server.log(unicode(json.dumps(params))) result = self.invoke_api(server, method, dev.pluginProps["extension"], json.dumps(params)) self.update_extension_status(dev) except Exception as e: self.logger.error(ex.stack_trace(e))
def __init__(self, plugin): """ Start the factory with references back to the base plugin. Arguments: plugin: Indigo plugin """ try: if plugin is None: return # pre-loading before init self.logger = logging.getLogger ("Plugin.{}".format(self.__class__.__name__.replace("_",""))) self.plugin = plugin # References the Indigo plugin self.logger.debug ("{} {} loaded".format(__modname__, __version__)) except Exception as e: self.logger.error (ex.stack_trace(e))
def update_address_status(self, dev): """ Determine the phone status and return it. """ try: status = '' if dev.states['dnd.enabled']: status += 'DND ' if dev.states['cfunconditional.enabled']: status += 'CF ' if dev.states['cfbusy.enabled']: status += 'CFB ' if dev.states['cfunavailable.enabled']: status += 'CFU ' if status == '': status = 'Ready' return status except Exception as e: self.logger.error(ex.stack_trace(e))
def action_list_changed (self, valuesDict, typeId): """ Drop down list was changed, enable/disable edit fields. """ try: errorsDict = indigo.Dict() if not 'action' in valuesDict: return (valuesDict, errorsDict) if valuesDict['action'] == 'delete': valuesDict['showfields'] = False elif valuesDict['action'] == 'add': valuesDict['showfields'] = True else: valuesDict['showfields'] = False except Exception as e: self.logger.error (ex.stack_trace(e)) return (valuesDict, errorsDict)
def create_unique_key(self): """ Create a unique identifier (key) using the date and time to the millisecond plus a random number to ensure uniqueness. Returns: string value of hashed keyString """ try: d = indigo.server.getTime() key = self._create_hash_key( d.strftime("%Y-%m-%d %H:%M:%S %f") + str(randint(1000, 1000001))) key = key[0:16] return key except Exception as e: self.logger.error(ex.stack_trace(e)) return ""
def set_form_records (self, valuesDict): """ Look for the presense of the JSON_FIELD in the form data and retrieve it or create a blank dictionary. This can be called from a child process to force the record list into the variable so it can be iterated. """ try: if not JSON_FIELD in valuesDict: allrecords = {} valuesDict[JSON_FIELD] = json.dumps(allrecords) allrecords = json.loads(valuesDict[JSON_FIELD]) if not self.recordName in allrecords: allrecords[self.recordName] = [] self.records = allrecords[self.recordName] except Exception as e: self.logger.error (ex.stack_trace(e))
def set_form_records(self, valuesDict): """ Look for the presense of the JSON_FIELD in the form data and retrieve it or create a blank dictionary. This can be called from a child process to force the record list into the variable so it can be iterated. """ try: if not JSON_FIELD in valuesDict: allrecords = {} valuesDict[JSON_FIELD] = json.dumps(allrecords) allrecords = json.loads(valuesDict[JSON_FIELD]) if not self.recordName in allrecords: allrecords[self.recordName] = [] self.records = allrecords[self.recordName] except Exception as e: self.logger.error(ex.stack_trace(e))
def timer_update_extension_status(self, serverId): try: delay = int(indigo.devices[serverId].pluginProps['frequency']) while True: for dev in indigo.devices.iter(self.pluginId): if dev.deviceTypeId == 'Extension' and dev.pluginProps[ 'server'] == str(serverId): self.update_extension_status(dev) elif dev.deviceTypeId == 'CallFlow': self.update_call_flow_status(dev) time.sleep(delay) #self.timer_update_extension_status() except self.StopThread: pass except Exception as e: self.logger.error(ex.stack_trace(e))
def get_status(self, dev, method): try: if dev.deviceTypeId == 'Server': result = self.invoke_api(dev, method) elif dev.deviceTypeId == 'Extension': server = int(dev.pluginProps["server"]) if not server in indigo.devices: self.logger.error( u"PBX Server {} is not in the Indigo device list, was it removed? {} action cannot complete" .format(server, dev.name)) return server = indigo.devices[server] result = self.invoke_api(server, method, dev.pluginProps["extension"]) return result except Exception as e: self.logger.error(ex.stack_trace(e))
def list_commands (self, filter="", valuesDict=None, typeId="", targetId=0): """ Return the list of all commands. """ try: listData = [] listData.append (('none', '- Do Nothing -')) listData.append (('runapp', 'Run Application')) listData.append (('quitapp', 'Quit Application')) listData.append (('showmessage', 'Display Notification Message')) listData.append (('screensaver', 'Start Screensaver')) listData.append (('restart', 'Restart Computer')) listData.append (('sleep', 'Sleep Computer')) listData.append (('shutdown', 'Turn Off Computer')) listData.append (('update', 'Install Software Updates')) listData.append (('builtin', 'iTunes')) except Exception as e: self.logger.error (ex.stack_trace(e)) return listData
def list_credentials (self, filter="", valuesDict=None, typeId="", targetId=0): """ Build a custom list of the saved credentials. """ try: listData = [] if filter == 'device': listData = [('manual', 'Manual Login')] credentials = self.pluginPrefs['credentials'] if not credentials: return listData for c in credentials: keyString = base64.b64decode(c) #indigo.server.log(u'{}'.format(keyString)) item = keyString.split("||") if item[0] != '': listData.append ((item[0], u'{} ({})'.format(item[0], item[1]))) except Exception as e: self.logger.error (ex.stack_trace(e)) return listData
def save_credential (self, name, ip, user, password): try: credentials = self.pluginPrefs['credentials'] newcredentials = [] for c in credentials: item = base64.b64decode(c).split("||") if not name == '' and item[0] == name: return False newcredentials.append(c) item = u'{}||{}||{}||{}'.format(name, ip, user, password) newcredentials.append(base64.b64encode(bytes(item))) self.pluginPrefs['credentials'] = newcredentials return True except Exception as e: self.logger.error (ex.stack_trace(e)) return False
def update_call_flow_status(self, dev): try: keyValueList = [] server = int(dev.pluginProps["server"]) if not server in indigo.devices: self.logger.error( u"PBX Server {} is not in the Indigo device list, was it removed? {} action cannot complete" .format(server, dev.name)) return result = self.invoke_api(indigo.devices[server], 'daynight', dev.pluginProps['callflow'], '', '') if result: #indigo.server.log(unicode(result)) if result['state'] == 'DAY': keyValueList.append({ 'key': 'onOffState', 'value': False, 'uiValue': 'DAY' }) dev.updateStateImageOnServer( indigo.kStateImageSel.TimerOff) else: keyValueList.append({ 'key': 'onOffState', 'value': True, 'uiValue': 'NIGHT' }) dev.updateStateImageOnServer(indigo.kStateImageSel.TimerOn) if keyValueList: dev.updateStatesOnServer(keyValueList) self.update_address(dev) except Exception as e: self.logger.error(ex.stack_trace(e))
def list_computer_info (self, filter="", valuesDict=None, typeId="", targetId=0): """ Return the list of computer stats. """ try: listData = [] if targetId == 0: return listData dev = indigo.devices[targetId] listData.append (('name', 'Name:\t\t\t\t{}'.format(dev.states['name']))) listData.append (('macOS', 'macOS Version:\t\t{}'.format(dev.states['macOS']))) listData.append (('model', 'Model:\t\t\t\t{}'.format(dev.states['model']))) listData.append (('cpu_model', 'CPU Info:\t\t\t{} {}'.format(dev.states['cpu_model'], dev.states['cpu_speed']))) #listData.append (('serial', 'Serial Number:\t\t{}'.format(dev.states['serial']))) listData.append (('serial', 'Serial Number:\t\t{}'.format('XXXXXXXXXX'))) listData.append (('cpu', 'CPU Utilization:\t\t{}%'.format(dev.states['cpu']))) listData.append (('cpu_count', 'CPU Count:\t\t\t{}'.format(dev.states['cpu_count']))) listData.append (('cpu_cores', 'CPU Cores:\t\t\t{}'.format(dev.states['cpu_cores']))) listData.append (('memory', 'Memory:\t\t\t\t{} MB'.format("{:,}".format(dev.states['memory'])))) listData.append (('memory_available', 'Memory Available:\t\t{} MB'.format("{:,}".format(dev.states['memory_available'])))) listData.append (('memory_percent', 'Memory Used:\t\t{}%'.format(dev.states['memory_percent']))) listData.append (('disk', 'Pri Drive Capacity:\t\t{} MB'.format("{:,}".format(dev.states['disk'])))) listData.append (('disk_available', 'Pri Drive Available:\t{} MB'.format("{:,}".format(dev.states['disk_available'])))) listData.append (('disk_percent', 'Pri Drive Used:\t\t{}%'.format(dev.states['disk_percent']))) listData.append (('last_boot', 'Last Reboot:\t\t\t{}'.format(dev.states['last_boot']))) except Exception as e: self.logger.error (ex.stack_trace(e)) return listData
def update_saved_credential (self, name, newname, ip, user, password): try: credentials = self.pluginPrefs['credentials'] newcredentials = [] for c in credentials: item = base64.b64decode(c).split("||") if item[0] == newname and name != newname: return False for c in credentials: item = base64.b64decode(c).split("||") if (not name == '' and item[0] == name) or (not ip == '' and item[1] == ip): item = u'{}||{}||{}||{}'.format(newname, ip, user, password) newcredentials.append(base64.b64encode(bytes(item))) else: newcredentials.append(c) self.pluginPrefs['credentials'] = newcredentials return True except Exception as e: self.logger.error (ex.stack_trace(e))
def command_turn_off (self, dev, props = {}): try: self._command (dev, 'off', props) except Exception as e: self.logger.error (ex.stack_trace(e))
def find_applescript (self): try: self.logger.info ('### PLEASE WAIT WHILE YOUR DATABASE IS EXAMINED FOR EMBEDDED APPLESCRIPT, THIS MAY TAKE A MOMENT ###') # Make copy of the DB from shutil import copyfile dbdir = '{}/Databases/'.format(indigo.server.getInstallFolderPath()) src = u'{}{}.indiDb'.format(dbdir, indigo.server.getDbName()) dst = u'{}{}-mccopy.indiDb'.format(dbdir, indigo.server.getDbName()) copyfile(src, dst) # Parse the DB output = '\nThe following items have an embedded AppleScript:\n\n' dbdir = '{}/Databases/'.format(indigo.server.getInstallFolderPath()) xml = cElementTree.iterparse(u'{}{}-mccopy.indiDb'.format(dbdir, indigo.server.getDbName())) for event, elem in xml: type = '' folderId = '' id = '' name = '' script = '' isas = False if elem.tag == "ActionGroup": type = 'Action Group' for ActionGroup in elem: if ActionGroup.tag == 'FolderID': folderId = ActionGroup.text if ActionGroup.tag == 'Name': name = ActionGroup.text if ActionGroup.tag == 'ID': id = ActionGroup.text if ActionGroup.tag == 'ActionSteps': for ActionSteps in ActionGroup: if ActionSteps.tag == 'Action': for Action in ActionSteps: if Action.tag == 'AppleScriptSCPT': isas = True if isas and Action.tag == 'ScriptSource': script = Action.text if elem.tag == "Trigger": type = 'Trigger' for Trigger in elem: if Trigger.tag == 'FolderID': folderId = Trigger.text if Trigger.tag == 'Name': name = Trigger.text if Trigger.tag == 'ID': id = Trigger.text if Trigger.tag == 'ActionGroup': for ActionGroup in Trigger: if ActionGroup.tag == 'FolderID': folderId = ActionGroup.text if ActionGroup.tag == 'Name': name = ActionGroup.text if ActionGroup.tag == 'ID': id = ActionGroup.text if ActionGroup.tag == 'ActionSteps': for ActionSteps in ActionGroup: if ActionSteps.tag == 'Action': for Action in ActionSteps: if Action.tag == 'AppleScriptSCPT': isas = True if isas and Action.tag == 'ScriptSource': script = Action.text if elem.tag == "TDTrigger": type = 'Schedule' for Schedule in elem: if Schedule.tag == 'FolderID': folderId = Schedule.text if Schedule.tag == 'Name': name = Schedule.text if Schedule.tag == 'ID': id = Schedule.text if Schedule.tag == 'ActionGroup': for ActionGroup in Schedule: if ActionGroup.tag == 'FolderID': folderId = ActionGroup.text if ActionGroup.tag == 'Name': name = ActionGroup.text if ActionGroup.tag == 'ID': id = ActionGroup.text if ActionGroup.tag == 'ActionSteps': for ActionSteps in ActionGroup: if ActionSteps.tag == 'Action': for Action in ActionSteps: if Action.tag == 'AppleScriptSCPT': isas = True if isas and Action.tag == 'ScriptSource': script = Action.text if isas and name != '': #item = base64.b64decode(script) #indigo.server.log(u'{}: {} - \n{}'.format(type, name, script)) #indigo.server.log(u'{}: {}'.format(type, name)) output += u'{}: {}\n'.format(type, name) #break self.logger.info(u'{}'.format(output)) # Remove our DB copy os.remove(dst) except Exception as e: self.logger.error (ex.stack_trace(e))
def update_extension_status(self, dev): try: keyValueList = [] result = self.get_status(dev, 'callforward') if result: #indigo.server.log(unicode(result)) if not result['CF']: keyValueList.append({ 'key': 'cfunconditional', 'value': 'disabled' }) keyValueList.append({ 'key': 'cfunconditionalNumber', 'value': '' }) else: keyValueList.append({ 'key': 'cfunconditional', 'value': 'enabled' }) keyValueList.append({ 'key': 'cfunconditionalNumber', 'value': result['CF'] }) if not result['CFB']: keyValueList.append({'key': 'cfbusy', 'value': 'disabled'}) keyValueList.append({'key': 'cfbusyNumber', 'value': ''}) else: keyValueList.append({'key': 'cfbusy', 'value': 'enabled'}) keyValueList.append({ 'key': 'cfbusyNumber', 'value': result['CFB'] }) if not result['CFU']: keyValueList.append({ 'key': 'cfunavailable', 'value': 'disabled' }) keyValueList.append({ 'key': 'cfunavailableNumber', 'value': '' }) else: keyValueList.append({ 'key': 'cfunavailable', 'value': 'enabled' }) keyValueList.append({ 'key': 'cfunavailableNumber', 'value': result['CFU'] }) result = self.get_status(dev, 'donotdisturb') if result: #indigo.server.log(unicode(result)) if result["status"] == "enabled" or result[ "status"] == "YES": # YES when this is turned on via a phone keyValueList.append({'key': 'dnd', 'value': 'enabled'}) else: keyValueList.append({'key': 'dnd', 'value': 'disabled'}) result = self.get_status(dev, 'callwaiting') if result: #indigo.server.log(unicode(result)) if result[0] == "ENABLED": keyValueList.append({ 'key': 'callwaiting', 'value': 'enabled' }) else: keyValueList.append({ 'key': 'callwaiting', 'value': 'disabled' }) if keyValueList: dev.updateStatesOnServer(keyValueList) self.update_address(dev) except Exception as e: self.logger.error(ex.stack_trace(e))
def _command (self, dev, method = 'on', props = {}): try: if not props: props = dev.pluginProps onState = True if method == 'off': onState = False # We aren't writing props back, so fill in with saved credentials to pass around if 'credentials' in props and props['credentials'] != 'manual': item = self.get_saved_credential (props['credentials']) if item: props['name'] = item[0] props['computerip'] = item[1] props['username'] = item[2] props['password'] = item[3] command = props["{}Command".format(method)] if command == "none": return elif command == "runapp": self.openApp (dev, method, props) if not dev is None: dev.updateStateOnServer("onOffState", onState) self.configurePolling (dev, props) elif command == "quitapp": self.quitApp (dev, method, props) if not dev is None: dev.updateStateOnServer("onOffState", onState) self.configurePolling (dev, props) elif command == "sleep": self.sleepComputer (dev, method, props) if not dev is None: dev.updateStateOnServer("onOffState", onState) self.configurePolling (dev, props) elif command == "restart": self.restart_computer (dev, method, props) if not dev is None: dev.updateStateOnServer("onOffState", onState) self.configurePolling (dev, props) elif command == "update": thread.start_new_thread (self.update_computer, (dev, method, props)) elif command == "shutdown": self.shutdown_computer (dev, method, props) if not dev is None: dev.updateStateOnServer("onOffState", onState) self.configurePolling (dev, props) elif command == "screensaver": self.screenSaver (dev, method, props) if not dev is None: dev.updateStateOnServer("onOffState", onState) self.configurePolling (dev, props) elif command == "showmessage": self.send_message (dev, method, props) elif command == "builtin": if props["{}Standard".format(method)] == "playpause": self.playPause (dev, method, props) if props["{}Standard".format(method)] == "playlist": self.playList (dev, method, props) if props["{}Standard".format(method)] == "startitunes": self.startStopiTunes (dev, "start", props) if props["{}Standard".format(method)] == "stopitunes": self.startStopiTunes (dev, "stop", props) # Since this was an ON command, turn it on if not dev is None: dev.updateStateOnServer("onOffState", onState) self.configurePolling (dev, props) except Exception as e: self.logger.error (ex.stack_trace(e))
def invoke_api(self, dev, method, suffix='', body='', sub='users'): """ Calls the FreePBX API and returns the results. Arguments: dev (device) the server device where the IP address will be used method (string) the core API method to call suffix (string) generally the extension or list ID if we want granular details body (json dict) to execute or change an API call sub (string) sub folder under the method to direct to """ try: if not suffix == '': suffix = '/' + suffix if body == '': verb = 'GET' else: verb = 'PUT' #url = 'http://{}/admin/rest.php/rest/donotdisturb/users'.format(dev.pluginProps["ipaddress"]) url = '{}/restapi/rest.php/rest/{}/{}{}'.format( dev.pluginProps["ipaddress"], method, sub, suffix) token = dev.pluginProps["token"] key = dev.pluginProps["key"] #body = '' d = indigo.server.getTime() keyString = d.strftime("%Y-%m-%d %H:%M:%S %f") + str( randint(1000, 1000001)) nonce = hashlib.sha1(keyString.encode('ascii', 'ignore')).digest().encode( "hex") # [0:16] keyString = '{}:{}'.format(url, verb.lower()) hash_a = hashlib.sha256(keyString.encode( 'ascii', 'ignore')).digest().encode("hex") keyString = '{}:{}'.format(token, nonce) hash_b = hashlib.sha256(keyString.encode( 'ascii', 'ignore')).digest().encode("hex") #keyString = '{}'.format(body.encode('base64')) keyString = base64.b64encode(bytes(body)) #indigo.server.log(u"Body Encode64: {}".format(keyString)) #hash_c = hashlib.sha256(keyString.encode('ascii', 'ignore')).digest().encode("hex") hash_c = hashlib.sha256(keyString).digest().encode("hex") #hash_d = hashlib.sha256(body.encode('ascii', 'ignore')).digest().encode("hex") #indigo.server.log(u"Body C: {}".format(hash_c)) #indigo.server.log(u"Body D: {}".format(hash_d)) #return keyString = '{}:{}:{}'.format(hash_a, hash_b, hash_c) data = hashlib.sha256(keyString.encode( 'ascii', 'ignore')).digest().encode("hex") signature = hmac.new( str(key), str(data), hashlib.sha256).hexdigest( ) # Py 2.x, in Py 3.x we'll need to use bytes instead #signature = hmac.new(str(key), str(hash_c), hashlib.sha256).hexdigest() #indigo.server.log(u"Body C: {}".format(hash_c)) #indigo.server.log(u"Token Key: {}".format(key)) #indigo.server.log(u"Signature: {}".format(signature)) #indigo.server.log(u"Nonce: {}".format(nonce)) #indigo.server.log(u"Token: {}".format(token)) if body == '': headers = { 'Signature': signature, 'Nonce': nonce, 'Token': token } ret = requests.get(u"http://{}".format(url), headers=headers) else: headers = { 'Signature': signature, 'Nonce': nonce, 'Token': token, 'Content-Type': 'application/json' } ret = requests.put(u"http://{}".format(url), data=body, headers=headers) #headers = {'Signature': signature, 'Nonce': nonce, 'Token': token} #ret = requests.put(u"http://{}".format(url), json=body, headers=headers) #ret = urlopen(Request(url, headers)) #ret = requests.get(u"http://{}".format(url), headers=headers) #ret = requests.get(url, headers=headers, auth=HTTPBasicAuth('101','12345')) # change 12345 to the known 29 password #ret = requests.get(url, headers=headers, auth=HTTPDigestAuth('101','12345')) #indigo.server.log(url) if ret.status_code == 200: self.logger.debug( u"Success on FreePBX RestAPI on {}: {}".format( dev.name, ret.text)) if not ret.text == '': results = json.loads(ret.text) return results else: return {'status': 'no response, successful operation'} #for r in results: # for extension, status in r.iteritems(): # extdata = extension.split("/") # indigo.server.log(unicode(extdata)) elif ret.status_code == 403: self.logger.error( u"Access forbidden to FreePBX RestAPI on {}: {}".format( dev.name, ret.text)) elif ret.status_code == "404": self.logger.error( u"Invalid response to FreePBX RestAPI on {}: {}".format( dev.name, ret.text)) else: self.logger.error( u"Invalid response to FreePBX RestAPI on {}: {}".format( dev.name, ret.text)) #indigo.server.log(u"{}".format(ret)) except Exception as e: self.logger.error(ex.stack_trace(e)) return False