From bef0b8189e161084a0e20e2e2a9ef41b957a5f00 Mon Sep 17 00:00:00 2001 From: risqy Date: Mon, 31 Jul 2023 20:04:38 +0200 Subject: [PATCH] uptime kuma webhook endpoint --- scnserver/api/handler/message.go | 84 ++++++++++++++++++ scnserver/api/router.go | 4 +- scnserver/models/enums_gen.go | 2 +- scnserver/swagger/swagger.json | 144 +++++++++++++++++++++++++++++++ scnserver/swagger/swagger.yaml | 96 +++++++++++++++++++++ scnserver/test/send_test.go | 40 +++++++++ 6 files changed, 368 insertions(+), 2 deletions(-) diff --git a/scnserver/api/handler/message.go b/scnserver/api/handler/message.go index eb2315f..758cdf4 100644 --- a/scnserver/api/handler/message.go +++ b/scnserver/api/handler/message.go @@ -311,3 +311,87 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex CompatMessageID: compatMsgID, }, nil } + +// UptimeKumaWebHook swaggerdoc +// +// @Summary Send a new message +// @Description All parameter can be set via query-parameter or the json body. Only UserID, UserKey and Title are required +// @Tags External +// +// @Param query_data query handler.UptimeKumaWebHook.query false " " +// @Param post_body body handler.UptimeKumaWebHook.uptimeKumaWebhookBody false " " +// +// @Success 200 {object} any +// @Failure 400 {object} ginresp.apiError +// @Failure 401 {object} ginresp.apiError "The user_id was not found or the user_key is wrong" +// @Failure 403 {object} ginresp.apiError "The user has exceeded its daily quota - wait 24 hours or upgrade your account" +// @Failure 500 {object} ginresp.apiError "An internal server error occurred - try again later" +// +// @Router /webhook/uptime-kuma [POST] +func (h MessageHandler) UptimeKumaWebHook(g *gin.Context) ginresp.HTTPResponse { + type query struct { + UserID *models.UserID `form:"user_id" example:"7725"` + KeyToken *string `form:"key" example:"P3TNH8mvv14fm"` + } + + type uptimeKumaWebhookBody struct { + Heartbeat *struct { + Time string `json:"time"` + Status int `json:"status"` + Msg string `json:"msg"` + Timezone string `json:"timezone"` + TimezoneOffset string `json:"timezoneOffset"` + LocalDateTime string `json:"localDateTime"` + } `json:"heartbeat"` + Monitor *struct { + Name string `json:"name"` + Url *string `json:"url"` + } `json:"monitor"` + Msg string `json:"msg"` + } + + var b uptimeKumaWebhookBody + var q query + + ctx, httpErr := h.app.StartRequest(g, nil, &q, &b, nil) + if httpErr != nil { + return *httpErr + } + defer ctx.Cancel() + + var title = "" + + var content = "" + content += fmt.Sprintf("%v\n", b.Msg) + if b.Monitor != nil { + content += fmt.Sprintf("%v\n", b.Monitor.Name) + if b.Monitor.Url != nil { + content += fmt.Sprintf("url: %v\n", *b.Monitor.Url) + } + + if b.Heartbeat != nil { + statusString := "down" + + if b.Heartbeat.Status == 1 { + statusString = "up" + } + title = fmt.Sprintf("%v %v!", b.Monitor.Name, statusString) + } + + } + + if b.Heartbeat != nil { + content += "\n===== Heartbeat ======\n" + content += fmt.Sprintf("msg: %v\n", b.Heartbeat.Msg) + content += fmt.Sprintf("timestamp: %v\n", b.Heartbeat.Time) + content += fmt.Sprintf("timezone: %v\n", b.Heartbeat.Timezone) + content += fmt.Sprintf("timezone offset: %v\n", b.Heartbeat.TimezoneOffset) + content += fmt.Sprintf("local date time: %v\n", b.Heartbeat.TimezoneOffset) + } + okResp, errResp := h.sendMessageInternal(g, ctx, q.UserID, q.KeyToken, nil, &title, &content, langext.Ptr(1), nil, nil, nil) + + if errResp != nil { + return *errResp + } + return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, okResp)) +} diff --git a/scnserver/api/router.go b/scnserver/api/router.go index 29a590a..c60ce62 100644 --- a/scnserver/api/router.go +++ b/scnserver/api/router.go @@ -122,7 +122,6 @@ func (r *Router) Init(e *gin.Engine) error { apiv2 := e.Group("/api/v2/") { - apiv2.POST("/users", r.Wrap(r.apiHandler.CreateUser)) apiv2.GET("/users/:uid", r.Wrap(r.apiHandler.GetUser)) apiv2.PATCH("/users/:uid", r.Wrap(r.apiHandler.UpdateUser)) @@ -164,6 +163,9 @@ func (r *Router) Init(e *gin.Engine) error { sendAPI.POST("/", r.Wrap(r.messageHandler.SendMessage)) sendAPI.POST("/send", r.Wrap(r.messageHandler.SendMessage)) sendAPI.POST("/send.php", r.Wrap(r.messageHandler.SendMessageCompat)) + + sendAPI.POST("/webhook/uptime-kuma", r.Wrap(r.messageHandler.UptimeKumaWebHook)) + } // ================ diff --git a/scnserver/models/enums_gen.go b/scnserver/models/enums_gen.go index ad93ec4..241b07e 100644 --- a/scnserver/models/enums_gen.go +++ b/scnserver/models/enums_gen.go @@ -4,7 +4,7 @@ package models import "gogs.mikescher.com/BlackForestBytes/goext/langext" -const ChecksumGenerator = "a41b8d265c326a65d7be07c74aa2318064c6307256bd92b684c5adb4a8f82d97" +const ChecksumGenerator = "38908fc9adc16eb3a1266e4bca06e50ebc8613c5d3c9a4fea39314115f66544e" type Enum interface { Valid() bool diff --git a/scnserver/swagger/swagger.json b/scnserver/swagger/swagger.json index 38e71eb..f9db824 100644 --- a/scnserver/swagger/swagger.json +++ b/scnserver/swagger/swagger.json @@ -2731,6 +2731,69 @@ } } } + }, + "/webhook/uptime-kuma": { + "post": { + "description": "All parameter can be set via query-parameter or the json body. Only UserID, UserKey and Title are required", + "tags": [ + "External" + ], + "summary": "Send a new message", + "parameters": [ + { + "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "query" + }, + { + "type": "string", + "example": "7725", + "name": "user_id", + "in": "query" + }, + { + "description": " ", + "name": "post_body", + "in": "body", + "schema": { + "$ref": "#/definitions/handler.UptimeKumaWebHook.uptimeKumaWebhookBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + }, + "401": { + "description": "The user_id was not found or the user_key is wrong", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + }, + "403": { + "description": "The user has exceeded its daily quota - wait 24 hours or upgrade your account", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + }, + "500": { + "description": "An internal server error occurred - try again later", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + } + } + } } }, "definitions": { @@ -3386,6 +3449,45 @@ } } }, + "handler.UptimeKumaWebHook.uptimeKumaWebhookBody": { + "type": "object", + "properties": { + "heartbeat": { + "type": "object", + "properties": { + "localDateTime": { + "type": "string" + }, + "msg": { + "type": "string" + }, + "time": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "timezoneOffset": { + "type": "string" + } + } + }, + "monitor": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "msg": { + "type": "string" + } + } + }, "handler.pingResponse": { "type": "object", "properties": { @@ -3633,9 +3735,30 @@ "default_channel": { "type": "string" }, + "default_priority": { + "type": "integer" + }, "is_pro": { "type": "boolean" }, + "max_body_size": { + "type": "integer" + }, + "max_channel_description_length": { + "type": "integer" + }, + "max_channel_name_length": { + "type": "integer" + }, + "max_sender_name_length": { + "type": "integer" + }, + "max_title_length": { + "type": "integer" + }, + "max_user_message_id_length": { + "type": "integer" + }, "messages_sent": { "type": "integer" }, @@ -3680,9 +3803,30 @@ "default_channel": { "type": "string" }, + "default_priority": { + "type": "integer" + }, "is_pro": { "type": "boolean" }, + "max_body_size": { + "type": "integer" + }, + "max_channel_description_length": { + "type": "integer" + }, + "max_channel_name_length": { + "type": "integer" + }, + "max_sender_name_length": { + "type": "integer" + }, + "max_title_length": { + "type": "integer" + }, + "max_user_message_id_length": { + "type": "integer" + }, "messages_sent": { "type": "integer" }, diff --git a/scnserver/swagger/swagger.yaml b/scnserver/swagger/swagger.yaml index c5f70b1..13039c3 100644 --- a/scnserver/swagger/swagger.yaml +++ b/scnserver/swagger/swagger.yaml @@ -458,6 +458,31 @@ definitions: user_id: type: integer type: object + handler.UptimeKumaWebHook.uptimeKumaWebhookBody: + properties: + heartbeat: + properties: + localDateTime: + type: string + msg: + type: string + time: + type: string + timezone: + type: string + timezoneOffset: + type: string + type: object + monitor: + properties: + name: + type: string + url: + type: string + type: object + msg: + type: string + type: object handler.pingResponse: properties: info: @@ -621,8 +646,22 @@ definitions: properties: default_channel: type: string + default_priority: + type: integer is_pro: type: boolean + max_body_size: + type: integer + max_channel_description_length: + type: integer + max_channel_name_length: + type: integer + max_sender_name_length: + type: integer + max_title_length: + type: integer + max_user_message_id_length: + type: integer messages_sent: type: integer quota_max: @@ -652,8 +691,22 @@ definitions: type: array default_channel: type: string + default_priority: + type: integer is_pro: type: boolean + max_body_size: + type: integer + max_channel_description_length: + type: integer + max_channel_name_length: + type: integer + max_sender_name_length: + type: integer + max_title_length: + type: integer + max_user_message_id_length: + type: integer messages_sent: type: integer quota_max: @@ -2548,6 +2601,49 @@ paths: summary: Send a new message (compatibility) tags: - External + /webhook/uptime-kuma: + post: + description: All parameter can be set via query-parameter or the json body. + Only UserID, UserKey and Title are required + parameters: + - example: P3TNH8mvv14fm + in: query + name: key + type: string + - example: "7725" + in: query + name: user_id + type: string + - description: ' ' + in: body + name: post_body + schema: + $ref: '#/definitions/handler.UptimeKumaWebHook.uptimeKumaWebhookBody' + responses: + "200": + description: OK + schema: + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/ginresp.apiError' + "401": + description: The user_id was not found or the user_key is wrong + schema: + $ref: '#/definitions/ginresp.apiError' + "403": + description: The user has exceeded its daily quota - wait 24 hours or upgrade + your account + schema: + $ref: '#/definitions/ginresp.apiError' + "500": + description: An internal server error occurred - try again later + schema: + $ref: '#/definitions/ginresp.apiError' + summary: Send a new message + tags: + - External swagger: "2.0" tags: - name: External diff --git a/scnserver/test/send_test.go b/scnserver/test/send_test.go index c2f89c2..a2dcf78 100644 --- a/scnserver/test/send_test.go +++ b/scnserver/test/send_test.go @@ -1780,3 +1780,43 @@ func TestSendWithPermissionSendKey(t *testing.T) { func TestSendDeliveryRetry(t *testing.T) { t.SkipNow() //TODO } + +func TestUptimeKuma(t *testing.T) { + ws, baseUrl, stop := tt.StartSimpleWebserver(t) + defer stop() + + pusher := ws.Pusher.(*push.TestSink) + + r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/v2/users", gin.H{ + "agent_model": "DUMMY_PHONE", + "agent_version": "4X", + "client_type": "ANDROID", + "fcm_token": "DUMMY_FCM", + }) + + uid := r0["user_id"].(string) + admintok := r0["admin_key"].(string) + sendtok := r0["send_key"].(string) + + suffix := fmt.Sprintf("/webhook/uptime-kuma?user_id=%v&key=%v", uid, sendtok) + _ = tt.RequestPost[gin.H](t, baseUrl, suffix, gin.H{ + "msg": "Uptime Kuma failed with 503!", + "heartbeat": gin.H{ + "status": 0, + }, + "monitor": gin.H{ + "name": "Test-Kuma", + }, + }) + + tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) + tt.AssertStrRepEqual(t, "msg.title", "Test-Kuma down!", pusher.Last().Message.Title) + + type mglist struct { + Messages []gin.H `json:"messages"` + } + + msgList1 := tt.RequestAuthGet[mglist](t, admintok, baseUrl, "/api/v2/messages") + tt.AssertEqual(t, "len(messages)", 1, len(msgList1.Messages)) + tt.AssertStrRepEqual(t, "msg.title", "Test-Kuma down!", msgList1.Messages[0]["title"]) +}