#!/usr/bin/env python import json, httplib, urllib, ssl, copy, os # import stock icons try: stock_icons = json.loads(open(os.path.dirname(__file__)+'/icons.json','r').read()) except: raise Exception('Unable to load icon file') # The Actual class class MessageCard: " Create and send a Teams Webhook " card_template = { '@type': 'MessageCard', '@context': 'http://schema.org/extensions', 'summary': '', 'sections': [], } action_button_template = { '@type': 'ActionCard', 'actions': [{ '@type': 'HttpPOST', }] } action_textinput_template = { '@type': 'ActionCard', 'inputs': [{ '@type': 'TextInput', }], 'actions': [{ '@type': 'HttpPOST', }] } action_dateinput_template = { '@type': 'ActionCard', 'inputs': [{ '@type': 'DateInput', }], 'actions': [{ '@type': 'HttpPOST', }] } action_multichoice_template = { '@type': 'ActionCard', 'inputs': [{ '@type': 'MultichoiceInput', 'choices': [] }], 'actions': [{ '@type': 'HttpPOST', }] } channel_uri = None icon = '' title = '' subtitle = '' facts = [] actions = [] error = '' def __init__(self, tls_verify_host=False, tls_verify_mode=ssl.CERT_NONE): try: self.SetupTLS(tls_verify_host,tls_verify_mode) except: raise Exception('Unable to setup TLS/SSL Context') def SetupTLS(self,tls_verify_host,tls_verify_mode): self.tls_ctx = ssl.create_default_context() self.tls_ctx.check_hostname = tls_verify_host self.tls_ctx.verify_mode = tls_verify_mode def AddFacts(self, data_array): " Add a 'fact' to the message" if isinstance(data_array,dict): for key, value in data_array.items(): self.facts.append({'name': key, 'value': value}) elif isinstance(data_array,list): for fact in data_array: if isinstance(fact,dict): for key, value in fact.items(): self.facts.append({'name': key, 'value': value}) else: raise Exception('No data provided for facts') def AddButton(self, action_array): " Add a button, expects a dict with action_name, button_label and post_uri " thisAction = None thisAction = copy.deepcopy(self.action_button_template) if isinstance(action_array,dict): try: thisAction['name'] = action_array['action_name'] except: raise Exception('action_name not present') try: thisAction['actions'][0]['name'] = action_array['button_label'] except: raise Exception('button_label not present') try: thisAction['actions'][0]['target'] = action_array['post_uri'] except: raise Exception('post_uri not present') self.actions.append(thisAction) else: raise Exception('No data provided for action') def AddTextInput(self, action_array): " Add a Text Input, expects a dict with action_id, action_name, button_label, input_hint and post_uri, optional boolean multiline" thisAction = None thisAction = copy.deepcopy(self.action_textinput_template) if isinstance(action_array,dict): try: thisId = action_array['action_id'] thisAction['inputs'][0]['id'] = thisId thisAction['actions'][0]['body'] = str(thisId+"={{"+thisId+".value}}") except: raise Exception('action_id not present') try: thisAction['name'] = action_array['action_name'] except: raise Exception('action_name not present') try: thisAction['actions'][0]['name'] = action_array['button_label'] except: raise Exception('button_label not present') try: thisAction['actions'][0]['target'] = action_array['post_uri'] except: raise Exception('post_uri not present') try: thisAction['inputs'][0]['title'] = action_array['input_hint'] except: raise Exception('input_hint not present') try: if action_array['multiline'] == True: thisAction['inputs'][0]['isMultiline'] = 'true' except: pass self.actions.append(thisAction) else: raise Exception('No data provided for action') def AddDateInput(self, action_array): " Add a Date Input, expects a dict with action_id, action_name, button_label, input_hint and post_uri, optional boolean includetime" thisAction = None thisAction = copy.deepcopy(self.action_dateinput_template) if isinstance(action_array,dict): try: thisId = action_array['action_id'] thisAction['inputs'][0]['id'] = thisId thisAction['actions'][0]['body'] = str(thisId+"={{"+thisId+".value}}") except: raise Exception('action_id not present') try: thisAction['name'] = action_array['action_name'] except: raise Exception('action_name not present') try: thisAction['actions'][0]['name'] = action_array['button_label'] except: raise Exception('button_label not present') try: thisAction['actions'][0]['target'] = action_array['post_uri'] except: raise Exception('post_uri not present') try: thisAction['inputs'][0]['title'] = action_array['input_hint'] except: raise Exception('input_hint not present') try: if action_array['includetime'] == True: thisAction['inputs'][0]['includeTime'] = 'true' if action_array['includeTime'] == True: thisAction['inputs'][0]['includeTime'] = 'true' except: pass self.actions.append(thisAction) else: raise Exception('No data provided for action') def AddMultichoice(self, action_array): " Add a Multichoice Input, expects a dict with action_id, action_name, button_label, input_hint, post_uri and choices list (with display/value pairs as dicts), optional boolean multiselect" thisAction = None thisAction = copy.deepcopy(self.action_multichoice_template) if isinstance(action_array,dict): try: thisId = action_array['action_id'] thisAction['inputs'][0]['id'] = thisId thisAction['actions'][0]['body'] = str(thisId+"={{"+thisId+".value}}") except: raise Exception('action_id not present') try: thisAction['name'] = action_array['action_name'] except: raise Exception('action_name not present') try: thisAction['actions'][0]['name'] = action_array['button_label'] except: raise Exception('button_label not present') try: thisAction['actions'][0]['target'] = action_array['post_uri'] except: raise Exception('post_uri not present') try: thisAction['inputs'][0]['title'] = action_array['input_hint'] except: raise Exception('input_hint not present') try: if isinstance(action_array['choices'],list): thisAction['inputs'][0]['choices'] = action_array['choices'] except: raise Exception('choices list not present') try: if action_array['multiselect'] == True: thisAction['inputs'][0]['isMultiSelect'] = 'true' if action_array['multiSelect'] == True: thisAction['inputs'][0]['isMultiSelect'] = 'true' except: pass self.actions.append(thisAction) else: raise Exception('No data provided for action') def GenerateCard(self): " Generate the JSON for the card " self.card = self.card_template self.card['sections'].append({}) self.card['sections'][0]['activityTitle'] = self.title self.card['sections'][0]['activitySubtitle'] = self.subtitle self.card['sections'][0]['activityImage'] = self.icon self.card['sections'][0]['facts'] = self.facts self.card['potentialAction'] = self.actions self.card['sections'][0]['markdown'] = 'true' self.card['summary'] = self.title def SendCard(self): " Send the Card to the O365 API " self.GenerateCard() uri = self.channel_uri.split('/') if not len(uri) == 8: raise Exception('Invalid Channel URI') endpoint = uri[2] del uri[2], uri[1], uri[0] uri = '/' + str('/'.join(uri)) try: hookconnection = httplib.HTTPSConnection(endpoint, context=self.tls_ctx) except: raise Exception('Unable to open connection') hookconnection.request('POST',uri,json.dumps(self.card)) self.response = hookconnection.getresponse() #print(self.response.read()) def PrintJSON(self): " Print the JSON of the card " self.GenerateCard() print(json.dumps(self.card,indent=1,sort_keys=True))