Arksine / moonraker

Web API Server for Klipper
https://moonraker.readthedocs.io
GNU General Public License v3.0
1.04k stars 398 forks source link

Notifier -> Mattermost: 405 #462

Open jangrewe opened 2 years ago

jangrewe commented 2 years ago

What happened

I'm trying to set up notifications to Mattermost (which works fine with other tools that use Apprise), but i get this error when a notification is supposed to be sent:

2022-07-13 21:19:51,236 [NotifyMattermost.py:send()] - Failed to send Mattermost notification to channel klipper: Method not allowed., error=405.

Client

Fluidd

Browser

Other or N/A

How to reproduce

Add to moonraker.conf and start a print:

[notifier mattermost_print_start]
url: mmost://mattermost.example.com/hooks/<redacted>?channel=klipper
events: started
title: Print started
body: Your printer started printing '{event_args[1].filename}'

Additional information

No response

Arksine commented 2 years ago

I'm not sure there is much I can do here, however if you attach the full log I'm willing to look at it. That snippet is an error logged by the Apprise module itself. It suggests that the server will not accept the request method, which is a POST according to the apprise source.

jangrewe commented 2 years ago

Here's a test with the exact webhook URL (but using https://, for obvious reasons), from the Pi that runs Moonraker:

$ curl -i -X POST -H 'Content-Type: application/json' -d '{"text": "Hello world!"}' https://mattermost.example.com/hooks/<redacted>
HTTP/2 200
server: nginx/1.14.2
date: Wed, 13 Jul 2022 21:30:59 GMT
content-type: text/plain
content-length: 2
vary: Accept-Encoding
x-request-id: 3hbraqk3ufytxkka4nyg7a3fxh
x-version-id: 6.7.3.6.7.3-rc1.13a5fa876741866bfc8e8798eb714f65.false
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
strict-transport-security: max-age=31536000; includeSubDomains; preload

I do receive the message in the intended channel, so this confirms the Mattermost webhook is working as expected.

Here's the log since the restart of the service:

2022-07-13 21:19:33,783 [moonraker.py:start_server()] - Starting Moonraker on (0.0.0.0, 7125), Hostname: voronabox
2022-07-13 21:19:33,784 [app.py:listen()] - SSL Certificate/Key not configured, aborting HTTPS Server startup
2022-07-13 21:19:34,037 [klippy_connection.py:_do_connect()] - Klippy Connection Established
2022-07-13 21:19:34,292 [klippy_connection.py:_init_klippy_connection()] - Webhooks Subscribed
2022-07-13 21:19:34,294 [klippy_connection.py:_init_klippy_connection()] - GCode Output Subscribed
2022-07-13 21:19:34,296 [moonraker.py:add_log_rollover_item()] - Klipper Version: v0.10.0-508-g1636a975
2022-07-13 21:19:34,298 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/info
2022-07-13 21:19:34,299 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.info
2022-07-13 21:19:34,299 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/query_endstops/status
2022-07-13 21:19:34,299 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.query_endstops.status
2022-07-13 21:19:34,299 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/emergency_stop
2022-07-13 21:19:34,299 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.emergency_stop
2022-07-13 21:19:34,299 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/pause_resume/pause
2022-07-13 21:19:34,300 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.pause_resume.pause
2022-07-13 21:19:34,300 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/objects/query
2022-07-13 21:19:34,300 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.objects.query
2022-07-13 21:19:34,300 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/objects/list
2022-07-13 21:19:34,300 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.objects.list
2022-07-13 21:19:34,300 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/adxl345/dump_adxl345
2022-07-13 21:19:34,300 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.adxl345.dump_adxl345
2022-07-13 21:19:34,300 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/motion_report/dump_stepper
2022-07-13 21:19:34,300 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.motion_report.dump_stepper
2022-07-13 21:19:34,300 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/gcode/firmware_restart
2022-07-13 21:19:34,300 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.gcode.firmware_restart
2022-07-13 21:19:34,301 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/objects/subscribe
2022-07-13 21:19:34,301 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.objects.subscribe
2022-07-13 21:19:34,301 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/gcode/script
2022-07-13 21:19:34,301 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.gcode.script
2022-07-13 21:19:34,301 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/gcode/help
2022-07-13 21:19:34,301 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.gcode.help
2022-07-13 21:19:34,301 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/pause_resume/resume
2022-07-13 21:19:34,301 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.pause_resume.resume
2022-07-13 21:19:34,301 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/motion_report/dump_trapq
2022-07-13 21:19:34,301 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.motion_report.dump_trapq
2022-07-13 21:19:34,301 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/pause_resume/cancel
2022-07-13 21:19:34,302 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.pause_resume.cancel
2022-07-13 21:19:34,302 [app.py:register_remote_handler()] - Registering HTTP endpoint: (GET POST) /printer/gcode/restart
2022-07-13 21:19:34,302 [websockets.py:register_api_handler()] - Registering Websocket JSON-RPC methods: printer.gcode.restart
2022-07-13 21:19:34,542 [job_state.py:_handle_started()] - Job state initialized: error
2022-07-13 21:19:34,839 [klippy_connection.py:_check_ready()] - Klippy ready
2022-07-13 21:19:35,086 [app.py:log_request()] - 101 GET /websocket (127.0.0.1) [_API_KEY_USER_] 3.02ms
2022-07-13 21:19:35,087 [websockets.py:open()] - Websocket Opened: ID: 2814809936, Proxied: False, User Agent: Python/3.7 websockets/10.1, Host Name: 127.0.0.1
2022-07-13 21:19:35,295 [data_store.py:_init_sensors()] - Configuring available sensors: ['heater_bed', 'extruder']
2022-07-13 21:19:35,710 [app.py:log_request()] - 101 GET /websocket?token=<redacted> (<redacted>) [jan] 1.98ms
2022-07-13 21:19:35,710 [websockets.py:open()] - Websocket Opened: ID: 2815899120, Proxied: True, User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36, Host Name: voronabox.example.local
2022-07-13 21:19:35,713 [websockets.py:_handle_identify()] - Websocket 2815899120 Client Identified - Name: fluidd, Version: 1.19.0-6184c73, Type: web
2022-07-13 21:19:41,787 [klippy_connection.py:_on_connection_closed()] - Klippy Connection Removed
2022-07-13 21:19:41,804 [websockets.py:build_error()] - JSON-RPC Request Error: 503
2022-07-13 21:19:43,048 [klippy_connection.py:_do_connect()] - Klippy Connection Established
2022-07-13 21:19:44,694 [klippy_connection.py:_init_klippy_connection()] - Webhooks Subscribed
2022-07-13 21:19:44,703 [klippy_connection.py:_init_klippy_connection()] - GCode Output Subscribed
2022-07-13 21:19:47,449 [job_state.py:_handle_started()] - Job state initialized: standby
2022-07-13 21:19:47,719 [klippy_connection.py:_check_ready()] - Klippy ready
2022-07-13 21:19:48,211 [data_store.py:_init_sensors()] - Configuring available sensors: ['heater_bed', 'extruder']
2022-07-13 21:19:51,213 [job_state.py:_status_update()] - Job Started: CarrotPatch_accent.gcode
2022-07-13 21:19:51,216 [notifier.py:_handle()] - 'started' notifier event triggered'
2022-07-13 21:19:51,217 [asyncio.py:notify()] - Notifying 1 service(s) asynchronously.
2022-07-13 21:19:51,236 [NotifyMattermost.py:send()] - Failed to send Mattermost notification to channel klipper: Method not allowed., error=405
2022-07-13 21:20:05,009 [notifier.py:_handle()] - 'error' notifier event triggered'
2022-07-13 21:20:05,012 [asyncio.py:notify()] - Notifying 1 service(s) asynchronously.
2022-07-13 21:20:05,033 [NotifyMattermost.py:send()] - Failed to send Mattermost notification to channel klipper: Method not allowed., error=405.
2022-07-13 21:28:54,487 [app.py:log_request()] - 201 POST /server/files/upload (<redacted>) [jan] 7.52ms
2022-07-13 21:28:55,609 [klippy_connection.py:_on_connection_closed()] - Klippy Connection Removed

(i removed the channel parameter, which is why the error message is a bit different now)

jangrewe commented 2 years ago

As i mentioned that "other tools that use Apprise" are working, here's one that's happily sending messages to my Mattermost instance all the time: https://github.com/dgtlmoon/changedetection.io

jangrewe commented 2 years ago

Okay, i got it to work, and i'm sorry for bothering you before testing more...

The issue was that i started with the webhook, copied from Mattermost's settings, e.g.

https://mattermost.example.com/hooks/<token>

then nothing happened, so i remembers that Apprise uses special URL prefixes, and changed it to

mmost://mattermost.example.com/hooks/<token>

When this didn't work, i opened this issue... After your reply, i tested some more, and saw that Apprise also has the mmosts:// URL prefix, which makes sense, as i'm on HTTPS, so i tried

mmosts://mattermost.example.com/hooks/<token>

... and then eventually i looked at my other tool and then documentation, and found the correct and working format:

mmost://mattermost.example.com/<token>

So that's without /hooks in the path, and also mmost:// and not mmosts://, for whatever reason.

I do receive messages now, but attachments don't seem to work, and the name - although configured in the Webhook as voron_2.4, gets overwritten to Apprise. Can that maybe be fixed? ;-)

jangrewe commented 2 years ago

Okay, i solved the mystery why mmost:// works but mmosts:// doesn't: i accesses Mattermost on the internal port 8065. Forcing port 443 in the URL also makes mmosts:// work as expected.

Regarding the attachments, they config seems to be fine, but there's nothing displayed in the message:

2022-07-14 00:16:02,595 [AppriseAttachment.py:add()] - Loading attachment: http://127.0.0.1/webcam/?action=snapshot&name=snapshot.jpg
2022-07-14 00:16:02,596 [asyncio.py:notify()] - Notifying 1 service(s) asynchronously.
2022-07-14 00:16:02,601 [NotifyMattermost.py:send()] - Mattermost POST URL: https://mattermost.example.com:443/hooks/<redacted> (cert_verify=True)
2022-07-14 00:16:02,601 [NotifyMattermost.py:send()] - Mattermost Payload: {'text': "Print started\r\nYour printer started printing 'CarrotPatch_accent.gcode'", 'icon_url': None, 'username': 'Apprise'}
2022-07-14 00:16:02,717 [NotifyMattermost.py:send()] - Sent Mattermost notification.
2022-07-14 00:16:16,165 [notifier.py:_handle()] - 'error' notifier event triggered'
2022-07-14 00:16:16,166 [AppriseAttachment.py:add()] - Loading attachment: http://127.0.0.1/webcam/?action=snapshot&name=snapshot.jpg
2022-07-14 00:16:16,168 [asyncio.py:notify()] - Notifying 1 service(s) asynchronously.
2022-07-14 00:16:16,171 [NotifyMattermost.py:send()] - Mattermost POST URL: https://mattermost.example.com:443/hooks/<redacted> (cert_verify=True)
2022-07-14 00:16:16,172 [NotifyMattermost.py:send()] - Mattermost Payload: {'text': "Error\r\nExtrude below minimum temp\nSee the 'min_extrude_temp' config option for details", 'icon_url': None, 'username': 'Apprise'}
2022-07-14 00:16:16,303 [NotifyMattermost.py:send()] - Sent Mattermost notification.

According to the Apprise source, images are supported: https://github.com/caronc/apprise/blob/master/apprise/plugins/NotifyMattermost.py - i think?

Attachments don't seem to be possible (yet), when comparing with the plugins for other services... OctoPrint solves this by taking a snapshot, uploading it to S3 (or any other compatible object storage, e.g. Minio), and then embeds and image with the external URL as the source. Maybe that's something you could also implement, to support services that don't support attachments natively? I guess snapshots are kind of a big deal for notifications. ;-) Just as an inspiration: https://medium.com/featurepreneur/upload-files-in-minio-using-python-4f987f902076 If e.g. the body template supports Jinja2, one could put a placeholder there that would render out with the attachment (snaphot) URL, effectively displaying the snapshot in the message.

To be clear, the body template (in moonraker.conf) would have to be a Mattermost message attachment JSON, as defined here: https://developers.mattermost.com/integrate/admin-guide/admin-message-attachments/ But that's of course the job of the user to get this right - as long as there's some way of getting the (uploaded) snapshot URL in there.

But i figured out how to change the displayed username: mmosts://voron@mattermost.example.com/...

Arksine commented 2 years ago

I'm glad you have the configuration issues resolved. With regard to extending the notifier to "work around" the issue with mattermost, its ok if you want to create a feature request for it, however its not something I will have time to look into in the near term. TBH, it seems that this is something that would be better addressed in mattermost itself.