########## CONFIG ##########
set kuma_webhook_port 8787
set kuma_webhook_path "/kuma"
set kuma_webhook_chan "#status"
# Optional shared secret (must match X-Kuma-Secret header in Kuma)
set kuma_webhook_secret "yourSecretHere"
########## INTERNAL STATE – DO NOT EDIT ##########
array set kuma_state {}
array set kuma_method {}
array set kuma_path {}
array set kuma_len {}
array set kuma_body {}
array set kuma_secret_hdr {}
########## COLORS ##########
# IRC color codes
set kuma_color_reset "\017"
set kuma_color_red "\00304"
set kuma_color_green "\00303"
########## LISTENER ##########
if {![info exists kuma_webhook_initialized]} {
set kuma_webhook_initialized 1
listen $kuma_webhook_port script kuma:accept
putlog "Uptime Kuma webhook: listening on port $kuma_webhook_port, path $kuma_webhook_path"
}
proc kuma:accept {idx} {
global kuma_state kuma_len kuma_body kuma_secret_hdr
set kuma_state($idx) "request"
set kuma_len($idx) 0
set kuma_body($idx) ""
set kuma_secret_hdr($idx) ""
control $idx kuma:control
return 0
}
proc kuma:control {idx text} {
global kuma_state kuma_method kuma_path kuma_len kuma_body kuma_secret_hdr
if {![info exists kuma_state($idx)]} {
set kuma_state($idx) "request"
}
switch -- $kuma_state($idx) {
request {
if {![regexp {^([A-Z]+) ([^ ]+) HTTP/} $text -> method path]} {
kuma:respond $idx 400 "Bad Request"
return 0
}
set kuma_method($idx) $method
set kuma_path($idx) $path
set kuma_state($idx) "headers"
}
headers {
if {$text eq ""} {
set kuma_state($idx) "body"
if {$kuma_len($idx) == 0} {
kuma:handle $idx
}
return 0
}
if {[regexp -nocase {^Content-Length:\s*([0-9]+)} $text -> len]} {
set kuma_len($idx) $len
} elseif {[regexp -nocase {^X-Kuma-Secret:\s*(.+)} $text -> sec]} {
set kuma_secret_hdr($idx) [string trim $sec]
}
}
body {
append kuma_body($idx) $text
if {$kuma_len($idx) == 0 || [string length $kuma_body($idx)] >= $kuma_len($idx)} {
kuma:handle $idx
}
}
}
return 0
}
########## REQUEST HANDLING ##########
proc kuma:handle {idx} {
global kuma_method kuma_path kuma_body kuma_secret_hdr
global kuma_webhook_path kuma_webhook_secret kuma_webhook_chan
global kuma_color_reset kuma_color_red kuma_color_green
if {$kuma_method($idx) ne "POST"} {
kuma:respond $idx 405 "Method Not Allowed"
return
}
set fullPath $kuma_path($idx)
if {[regexp {^([^?]+)} $fullPath -> cleanPath]} {
set pathOnly $cleanPath
} else {
set pathOnly $fullPath
}
if {$pathOnly ne $kuma_webhook_path} {
kuma:respond $idx 404 "Not Found"
return
}
if {$kuma_webhook_secret ne ""} {
if {![info exists kuma_secret_hdr($idx)] || $kuma_secret_hdr($idx) ne $kuma_webhook_secret} {
kuma:respond $idx 403 "Forbidden"
return
}
}
set body ""
if {[info exists kuma_body($idx)]} {
set body [string trim $kuma_body($idx)]
}
# --- Simple JSON-ish parsing for: {"monitor":"...","status":"...","reason":"..."} ---
set monitor ""
set status ""
set reason ""
if {[regexp {"monitor"\s*:\s*"([^"]*)"} $body -> m]} {
set monitor [string trim $m]
}
if {[regexp {"status"\s*:\s*"([^"]*)"} $body -> s]} {
set status [string toupper [string trim $s]]
}
if {[regexp {"reason"\s*:\s*"([^"]*)"} $body -> r]} {
# Unescape basic things
set reason [string map {\\n " " \\r " " \\" "\""} $r]
set reason [string trim $reason]
}
if {$monitor eq ""} { set monitor "Unknown Monitor" }
if {$reason eq ""} { set reason "No reason provided" }
# Build colored status text
if {$status eq "UP"} {
set status_colored "${kuma_color_green}UP${kuma_color_reset}"
} elseif {$status eq "DOWN"} {
set status_colored "${kuma_color_red}DOWN${kuma_color_reset}"
} else {
# Fallback if status is weird
set status_colored $status
}
# Line 1: " is (coloured)UP/DOWN"
set line1 "$monitor is $status_colored"
# Line 2:
# Down: " Failed - "
# Up: " Up - "
if {$status eq "DOWN"} {
set line2 "$monitor Failed - $reason"
} elseif {$status eq "UP"} {
set line2 "$monitor Up - $reason"
} else {
set line2 "$monitor - $reason"
}
if {$kuma_webhook_chan ne ""} {
putserv "PRIVMSG $kuma_webhook_chan :$line1"
putserv "PRIVMSG $kuma_webhook_chan :$line2"
}
kuma:respond $idx 200 "OK"
}
########## RESPONSE & CLEANUP ##########
proc kuma:respond {idx code text} {
set body $text
set len [string length $body]
putdcc $idx "HTTP/1.1 $code $text"
putdcc $idx "Content-Type: text/plain"
putdcc $idx "Content-Length: $len"
putdcc $idx ""
putdcc $idx $body
kuma:cleanup $idx
}
proc kuma:cleanup {idx} {
global kuma_state kuma_method kuma_path kuma_len kuma_body kuma_secret_hdr
catch {unset kuma_state($idx)}
catch {unset kuma_method($idx)}
catch {unset kuma_path($idx)}
catch {unset kuma_len($idx)}
catch {unset kuma_body($idx)}
catch {unset kuma_secret_hdr($idx)}
catch {killdcc $idx}
}