Skip to content

Commit

Permalink
Added OAuth support and the ability to send Test Webhooks
Browse files Browse the repository at this point in the history
You can now integrate with OAuth servers and test out events from the settings page to make sure your integration works like a charm before printing.
  • Loading branch information
2blane committed May 2, 2020
1 parent b5a3f18 commit 75077c8
Show file tree
Hide file tree
Showing 7 changed files with 317 additions and 60 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ Below is a list of variables you can use in your request.
@extra - a dictionary of extra data that was passed along by OctoPrint.


## Events
## Events / Topics
The following is the list of topics that can trigger a webhook.

* Print Started

Expand Down
11 changes: 11 additions & 0 deletions ex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
extra = {"popup":True}
#extra = {}
#extra = "hello world"
#extra = None

print(type(extra), type(extra) is dict)

if type(extra) is dict and "popup" in extra:
print("popup is in extra")
else:
print("not in popup")
116 changes: 101 additions & 15 deletions octoprint_webhooks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,23 @@
def replace_dict_with_data(d, v):
for key in d:
value = d[key]
if value[0:1] == "@":
value_key = value[1:]
start_index = value.find("@")
if start_index >= 0:
# Find the end text by space
end_index = value.find(" ", start_index)
if end_index == -1:
end_index = len(value)
value_key = value[start_index + 1:end_index]
if value_key in v:
d[key] = v[value_key]
if start_index == 0 and end_index == len(value):
d[key] = v[value_key]
else:
d[key] = d[key].replace(value[start_index:end_index], v[value_key])
return d


class WebhooksPlugin(octoprint.plugin.StartupPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.SettingsPlugin,
octoprint.plugin.EventHandlerPlugin, octoprint.plugin.AssetPlugin):
octoprint.plugin.EventHandlerPlugin, octoprint.plugin.AssetPlugin, octoprint.plugin.SimpleApiPlugin):
def __init__(self):
self.triggered = False

Expand All @@ -49,7 +57,12 @@ def get_settings_defaults(self):
eventPrintStarted=True, eventPrintDone=True, eventPrintFailed=True, eventPrintPaused=True,
eventUserActionNeeded=True, eventError=True,
headers='{\n "Content-Type": "application/json"\n}',
data='{\n "deviceIdentifier":"@deviceIdentifier",\n "apiSecret":"@apiSecret",\n "topic":"@topic",\n "message":"@message",\n "extra":"@extra"\n}'
data='{\n "deviceIdentifier":"@deviceIdentifier",\n "apiSecret":"@apiSecret",\n "topic":"@topic",\n "message":"@message",\n "extra":"@extra"\n}',
oauth=False,
oauth_url="",
oauth_headers='{\n "Content-Type": "application/json"\n}',
oauth_data='{\n "client_id":"myClient",\n "client_secret":"mySecret",\n "grant_type":"client_credentials"\n}',
test_event="PrintStarted"
)

def get_template_configs(self):
Expand All @@ -66,6 +79,28 @@ def get_assets(self):
def register_custom_events(self, *args, **kwargs):
return ["notify"]

def get_api_commands(self):
return dict(
testhook=[]
)

def on_api_command(self, command, data):
if command == "testhook":
self._logger.info("API testhook CALLED!")
# TRIGGER A CUSTOM EVENT FOR A TEST PAYLOAD
event_name = ""
if "event" in data:
event_name = data["event"]
self.on_event(event_name, {
"name": "example.gcode",
"path": "example.gcode",
"origin": "local",
"size": 242038,
"owner": "example_user",
"time": 50.237335886,
"popup": True
})

def on_event(self, event, payload):
topic = "Unknown"
message = "Unknown"
Expand All @@ -92,8 +127,49 @@ def on_event(self, event, payload):
if topic == "Unknown":
return
self._logger.info("P EVENT " + topic + " - " + message)
# 1) If necessary, make an OAuth request to get back an access token.
oauth = self._settings.get(["oauth"])
oauth_result = dict()
oauth_passed = False
if oauth:
parsed_oauth_headers = False
try:
# 1.1) Get the request data and headers
oauth_url = self._settings.get(["oauth_url"])
oauth_headers = json.loads(self._settings.get(["oauth_headers"]))
parsed_oauth_headers = True
oauth_data = json.loads(self._settings.get(["oauth_data"]))
# 1.2) Send the request
self._logger.info("Sending OAuth Request")
response = requests.post(oauth_url, json=oauth_data, headers=oauth_headers)
# 1.3) Check to make sure we got a valid response code.
self._logger.info("OAuth Response: " + " - " + response.text)
code = response.status_code
if 200 <= code < 400:
oauth_result = response.json()
oauth_passed = True
self._logger.info("OAuth Passed")
else:
self._logger.info("Invalid OAuth Response Code %s" % code)
self._plugin_manager.send_plugin_message(self._identifier, dict(type="error", hide=False, msg="Invalid OAuth Response: " + response.text))
except requests.exceptions.RequestException as e:
self._logger.info("OAuth API Error: " + str(e))
except Exception as e:
if parsed_oauth_headers:
self._logger.info("OAuth JSON Parse Issue for DATA")
self._plugin_manager.send_plugin_message(self._identifier, dict(type="error", msg="Invalid JSON for Webhooks OAUTH DATA Settings"))
else:
self._logger.info("OAuth JSON Parse Issue for HEADERS")
self._plugin_manager.send_plugin_message(self._identifier, dict(type="error", msg="Invalid JSON for Webhooks OAUTH HEADERS Settings"))
else:
oauth_passed = True
# Make sure we passed the oauth check
if not oauth_passed:
# Oauth not passed
self._logger.info("Not sending request - OAuth not passed")
return
# Send the notification
# 1) Call the API
# 2) Call the API
parsed_headers = False
try:
url = self._settings.get(["url"])
Expand All @@ -102,31 +178,41 @@ def on_event(self, event, payload):
headers = json.loads(self._settings.get(["headers"]))
parsed_headers = True
data = json.loads(self._settings.get(["data"]))
# 1.1) Create a dictionary of all possible replacement variables.
# 2.1) Create a dictionary of all possible replacement variables.
values = {
"topic": topic,
"message": message,
"extra": extra,
"apiSecret": api_secret,
"deviceIdentifier": device_identifier
}
# 1.2) Replace the data and header elements that start with @
# 2.2) Merge these values with the oauth values.
values.update(oauth_result)
# 2.3) Replace the data and header elements that start with @
data = replace_dict_with_data(data, values)
headers = replace_dict_with_data(headers, values)
# 1.3) Send the request
print("headers: " + json.dumps(headers) + " - data: " + json.dumps(data))
# 2.4) Send the request
self._logger.info("headers: " + json.dumps(headers) + " - data: " + json.dumps(data) + " - values: " + json.dumps(values))
response = requests.post(url, json=data, headers=headers)
result = json.loads(response.text)
self._logger.info("API SUCCESS: " + event + " " + json.dumps(result))
code = response.status_code
if 200 <= code < 400:
self._logger.info("API SUCCESS: " + event + " " + json.dumps(result))
# Optionally show a message of success if the payload has popup=True
if type(payload) is dict and "popup" in payload:
self._plugin_manager.send_plugin_message(self._identifier, dict(type="success", hide=True, msg="Response: " + response.text))
else:
self._logger.info("API Bad Response Code: %s" % code)
self._plugin_manager.send_plugin_message(self._identifier, dict(type="error", hide=False, msg="Invalid API Response: " + response.text))
except requests.exceptions.RequestException as e:
self._logger.info("API ERROR" + str(e))
self._logger.info("API ERROR: " + str(e))
except Exception as e:
if parsed_headers:
self._logger.info("JSON Parse DATA Issue")
self._plugin_manager.send_plugin_message(self._identifier, dict(type="info", autoClose=False, msg="Invalid JSON for Webhooks HEADERS Setting"))
self._logger.info("JSON Parse DATA Issue" + str(e))
self._plugin_manager.send_plugin_message(self._identifier, dict(type="error", msg="Invalid JSON for Webhooks DATA Setting"))
else:
self._logger.info("JSON Parse HEADERS Issue")
self._plugin_manager.send_plugin_message(self._identifier, dict(type="info", autoClose=False, msg="Invalid JSON for Webhooks DATA Setting"))
self._plugin_manager.send_plugin_message(self._identifier, dict(type="error", msg="Invalid JSON for Webhooks HEADERS Setting"))

def recv_callback(self, comm_instance, line, *args, **kwargs):
# Found keyword, fire event and block until other text is received
Expand Down
13 changes: 13 additions & 0 deletions octoprint_webhooks/static/css/webhooks.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,16 @@
color: black;
background: #cacbcd;
}
.oAuthContainer {
display: none;
}
.oAuthContainerBlock {
display: block !important;
}
.controls-center {
margin: 0px auto;
text-align: center;
}
.control-header-description {
margin: 10px 0px 10px 0px;
}
49 changes: 49 additions & 0 deletions octoprint_webhooks/static/js/webhooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,58 @@ $(function() {
console.log("Webhooks Reset HEADERS to Defaults Pressed")
}

self.resetOAuthDataToDefaults = function() {
// Update the data to be defaulted.
self.settings.settings.plugins.webhooks.oauth_data('{\n "client_id":"myClient",\n "client_secret":"mySecret",\n "grant_type":"client_credentials"\n}')
console.log("Webhooks Reset OAUTH_DATA to Defaults Pressed")
}

self.resetOAuthHeadersToDefaults = function() {
// Update the data to be defaulted.
self.settings.settings.plugins.webhooks.oauth_headers('{\n "Content-Type": "application/json"\n}')
console.log("Webhooks Reset OAUTH_HEADERS to Defaults Pressed")
}

self.onStartup = function() {
console.log("WebhookSettingsCustomViewModel startup")
}

self.onDataUpdaterPluginMessage = function(plugin, data) {
if (plugin != "webhooks") {
console.log('PLUGS - Ignoring '+plugin);
return;
}
hide = true
if (data["hide"] !== undefined) {
hide = data["hide"]
}
new PNotify({
title: 'Webhooks',
text: data.msg,
type: data.type,
hide: hide
});
}

self.testWebhook = function(data) {
var client = new OctoPrintClient()
client.options.baseurl = "/"
// 1) Save the user settings.
data = ko.mapping.toJS(self.settings.settings.plugins.webhooks);
console.log("WEBHOOKS - ", data)
return OctoPrint.settings.savePluginSettings("webhooks", data)
.done(function(data, status, xhr) {
//saved
console.log("settings saved")
// 2) Send a test event to the python backend.
event = self.settings.settings.plugins.webhooks.test_event()
client.postJson("api/plugin/webhooks", {"command":"testhook", "event":event})
})
.fail(function(xhr, status, error) {
//failed to save
console.log("failed to save settings")
})
}
}

OCTOPRINT_VIEWMODELS.push([
Expand Down
Loading

0 comments on commit 75077c8

Please sign in to comment.