Repository: louislam/uptime-kuma
Pull request #5686: feat: Support YZJ notification provider
Author: login: innerpeacez
Created: 2025-03-10T06:08:55Z | Merged: 2025-03-14T01:40:14Z

⚠️⚠️⚠️ Since we do not accept all types of pull requests and do not want to waste your time. Please be sure that you have read pull request rules:
https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma

Tick the checkbox if you understand [x]:
- [x] I have read and understand the pull request rules.

# Description

Support YZJ notification provider 

## Type of change

Please delete any options that are not relevant.


- New feature (non-breaking change which adds functionality)

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I ran ESLint and other linters for modified files
- [x] I have performed a self-review of my own code and tested it
- [x] I have commented my code, particularly in hard-to-understand areas (including JSDoc for methods)
- [x] My changes generates no new warnings
- [ ] My code needed automated testing. I have added them (this is optional task)

## Screenshots (if any)

![image](https://github.com/user-attachments/assets/757d3ee9-76dc-4182-bb8a-9602b11eb288)


| Event |  After |
------------------
| `UP` | ![image](https://github.com/user-attachments/assets/90486e44-2e1a-4138-807e-bd24efd1f883) | 
| `DOWN` | ![image](https://github.com/user-attachments/assets/e23e7645-6c97-41f2-bfbf-6d0282d9bf68) | 
| Certificate-expiry | ![image](https://github.com/user-attachments/assets/ab68158c-0d1b-4ac2-a854-20ff28db9809) | 
| Testing | ![image](https://github.com/user-attachments/assets/1d47b24b-ebad-4ff3-a762-62df9a7cd546) | 











Diff:
diff --git a/server/notification-providers/yzj.js b/server/notification-providers/yzj.js
new file mode 100644
index 00000000000..6bd3cba512d
--- /dev/null
+++ b/server/notification-providers/yzj.js
@@ -0,0 +1,57 @@
+const NotificationProvider = require("./notification-provider");
+const { DOWN, UP } = require("../../src/util");
+const { default: axios } = require("axios");
+
+class YZJ extends NotificationProvider {
+    name = "YZJ";
+
+    /**
+     * @inheritdoc
+     */
+    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
+        let okMsg = "Sent Successfully.";
+
+        try {
+            if (heartbeatJSON !== null) {
+                msg = `${this.statusToString(heartbeatJSON["status"])} ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
+            }
+
+            const config = {
+                headers: {
+                    "Content-Type": "application/json",
+                },
+            };
+            const params = {
+                content: msg
+            };
+            // yzjtype=0 => general robot
+            const url = `${notification.yzjWebHookUrl}?yzjtype=0&yzjtoken=${notification.yzjToken}`;
+
+            const result = await axios.post(url, params, config);
+            if (!result.data?.success) {
+                throw new Error(result.data?.errmsg ?? "yzj's server did not respond with the expected result");
+            }
+            return okMsg;
+        } catch (error) {
+            this.throwGeneralAxiosError(error);
+        }
+    }
+
+    /**
+     * Convert status constant to string
+     * @param {string} status The status constant
+     * @returns {string} status
+     */
+    statusToString(status) {
+        switch (status) {
+            case DOWN:
+                return "❌";
+            case UP:
+                return "✅";
+            default:
+                return status;
+        }
+    }
+}
+
+module.exports = YZJ;
diff --git a/server/notification.js b/server/notification.js
index e7977eb4af3..7ed62ffeccc 100644
--- a/server/notification.js
+++ b/server/notification.js
@@ -69,6 +69,7 @@ const Cellsynt = require("./notification-providers/cellsynt");
 const Onesender = require("./notification-providers/onesender");
 const Wpush = require("./notification-providers/wpush");
 const SendGrid = require("./notification-providers/send-grid");
+const YZJ = require("./notification-providers/yzj");
 
 class Notification {
 
@@ -154,7 +155,8 @@ class Notification {
             new GtxMessaging(),
             new Cellsynt(),
             new Wpush(),
-            new SendGrid()
+            new SendGrid(),
+            new YZJ()
         ];
         for (let item of list) {
             if (! item.name) {
diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue
index f6d72802998..8a2c6269dac 100644
--- a/src/components/NotificationDialog.vue
+++ b/src/components/NotificationDialog.vue
@@ -183,6 +183,7 @@ export default {
                 "ServerChan": "ServerChan (Server酱)",
                 "smsc": "SMSC",
                 "WPush": "WPush(wpush.cn)",
+                "YZJ": "YZJ (云之家自定义机器人)"
             };
 
             // Sort by notification name
diff --git a/src/components/notifications/YZJ.vue b/src/components/notifications/YZJ.vue
new file mode 100644
index 00000000000..63bc4c5307e
--- /dev/null
+++ b/src/components/notifications/YZJ.vue
@@ -0,0 +1,19 @@
+<template>
+    <div class="mb-3">
+        <label for="yzjWebHookUrl" class="form-label">{{ $t("YZJ Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="yzjWebHookUrl" v-model="$parent.notification.yzjWebHookUrl" type="url" class="form-control" required />
+        <i18n-t class="form-text" keypath="wayToGetTeamsURL">
+            <a href="https://www.yunzhijia.com/opendocs/docs.html#/tutorial/index/robot" target="_blank">{{ $t("here") }}</a>
+        </i18n-t>
+    </div>
+    <div class="mb-3">
+        <label for="yzjToken" class="form-label">{{ $t("YZJ Robot Token") }}<span style="color: red;"><sup>*</sup></span></label>
+        <HiddenInput id="yzjToken" v-model="$parent.notification.yzjToken" :required="true" autocomplete="new-password"></HiddenInput>
+        <i18n-t class="form-text" keypath="wayToGetLineNotifyToken">
+            <a href="https://www.yunzhijia.com/opendocs/docs.html#/server-api/im/index?id=%e6%8e%a5%e5%8f%a3%e5%9c%b0%e5%9d%80%e5%92%8c%e6%8e%88%e6%9d%83%e7%a0%81" target="_blank">{{ $t("here") }}</a>
+        </i18n-t>
+    </div>
+</template>
+<script setup lang="ts">
+import HiddenInput from "../HiddenInput.vue";
+</script>
diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js
index efa2af5c493..3bf9affd3c5 100644
--- a/src/components/notifications/index.js
+++ b/src/components/notifications/index.js
@@ -67,6 +67,7 @@ import Cellsynt from "./Cellsynt.vue";
 import WPush from "./WPush.vue";
 import SIGNL4 from "./SIGNL4.vue";
 import SendGrid from "./SendGrid.vue";
+import YZJ from "./YZJ.vue";
 
 /**
  * Manage all notification form.
@@ -142,6 +143,7 @@ const NotificationFormList = {
     "Cellsynt": Cellsynt,
     "WPush": WPush,
     "SendGrid": SendGrid,
+    "YZJ": YZJ,
 };
 
 export default NotificationFormList;
diff --git a/src/lang/en.json b/src/lang/en.json
index e215f1031fb..64958cdc639 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -1051,5 +1051,7 @@
     "RabbitMQ Password": "RabbitMQ Password",
     "rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
     "SendGrid API Key": "SendGrid API Key",
-    "Separate multiple email addresses with commas": "Separate multiple email addresses with commas"
+    "Separate multiple email addresses with commas": "Separate multiple email addresses with commas",
+    "YZJ Webhook URL": "YZJ Webhook URL",
+    "YZJ Robot Token": "YZJ Robot token"
 }


Reviews:
user: login: CommanderStorm
body: Thanks for the PR, here is some feedback what would need to be improved
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-11T01:46:31Z
user: login: CommanderStorm
body: A lot better, thanks.

I am still a bit confused about `YZJ Robot Type`.. What is this? can we remove it?
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-11T14:34:16Z
user: login: innerpeacez
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T05:10:21Z
user: login: CommanderStorm
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T01:31:04Z
user: login: CommanderStorm
body: Thanks for the new notification provider! 🎉 

<img width="200px" src="https://media4.giphy.com/media/3oEdva9BUHPIs2SkGk/giphy.gif"/>

> [!NOTE]
> This PR is part of the [`v2.0` merge window](https://github.com/louislam/uptime-kuma/milestone/24) => see #4500 for the bugs that need to be addressed before we can ship this release ^^

<sub>All changes in this PR are small and uncontroversial ⇒ merging with junior maintainer approval</sub>
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-14T01:34:20Z

Review comments:
diff_hunk: @@ -0,0 +1,76 @@
+const NotificationProvider = require("./notification-provider");
+const { DOWN, UP } = require("../../src/util");
+const { default: axios } = require("axios");
+
+class YZJ extends NotificationProvider {
+    name = "YZJ";
+
+    /**
+     * @inheritdoc
+     */
+    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
+        let okMsg = "Sent Successfully.";
+
+        try {
+            if (heartbeatJSON != null) {
+                let params = {
+                    content: `${this.statusToString(heartbeatJSON["status"])} ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
+                };
+                if (await this.sendToYZJ(notification, params)) {
+                    return okMsg;
+                }
+            } else {
+                let params = {
+                    content: msg
+                };
+                if (await this.sendToYZJ(notification, params)) {
+                    return okMsg;
+                }
+            }
+        } catch (error) {
+            this.throwGeneralAxiosError(error);
+        }
+    }
+
+    /**
+     * Send message to YZJ
+     * @param {object} notification Notification
+     * @param {object} params Parameters of message
+     * @returns {boolean} True if successful else false
+     */
+    async sendToYZJ(notification, params) {
+
+        let config = {
+            method: "POST",
+            headers: {
+                "Content-Type": "application/json",
+            },
+            url: `${notification.yzjWebHookUrl}?yzjtype=${notification.yzjType}&yzjtoken=${notification.yzjToken}`,
+            data: JSON.stringify(params),
+        };
+
+        let result = await axios(config);
+        if (result.data.success === true) {
+            return true;
+        }
+        throw new Error(result.data.errmsg);
path: server/notification-providers/yzj.js
user: login: CommanderStorm
body: please put the error case in the if clause => no bool-return

Also please handle empty data or differently formatted data and return a meaningfull error message
created_at: 2025-03-11T01:28:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,76 @@
+const NotificationProvider = require("./notification-provider");
+const { DOWN, UP } = require("../../src/util");
+const { default: axios } = require("axios");
+
+class YZJ extends NotificationProvider {
+    name = "YZJ";
+
+    /**
+     * @inheritdoc
+     */
+    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
+        let okMsg = "Sent Successfully.";
+
+        try {
+            if (heartbeatJSON != null) {
+                let params = {
+                    content: `${this.statusToString(heartbeatJSON["status"])} ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
+                };
+                if (await this.sendToYZJ(notification, params)) {
+                    return okMsg;
+                }
+            } else {
+                let params = {
+                    content: msg
+                };
+                if (await this.sendToYZJ(notification, params)) {
+                    return okMsg;
+                }
+            }
path: server/notification-providers/yzj.js
user: login: CommanderStorm
body: you are overcomplicating this a bit.

```suggestion
            let msg = msg;
            if (heartbeatJSON != null) {
                msg = `${this.statusToString(heartbeatJSON["status"])} ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
            }
            
            // inline sendToYZJ here.. I think this is simpler to read than currently
```
created_at: 2025-03-11T01:32:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,16 @@
+<template>
+    <div class="mb-3">
+        <label for="yzjWebHookUrl" class="form-label">{{ $t("YZJ Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="yzjWebHookUrl" v-model="$parent.notification.yzjWebHookUrl" type="text" class="form-control" required />
path: src/components/notifications/YZJ.vue
user: login: CommanderStorm
body: ```suggestion
        <input id="yzjWebHookUrl" v-model="$parent.notification.yzjWebHookUrl" type="url" class="form-control" required />
```
created_at: 2025-03-11T01:34:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,16 @@
+<template>
+    <div class="mb-3">
+        <label for="yzjWebHookUrl" class="form-label">{{ $t("YZJ Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="yzjWebHookUrl" v-model="$parent.notification.yzjWebHookUrl" type="text" class="form-control" required />
+        <label for="yzjType" class="form-label">{{ $t("YZJ Robot Type") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="yzjType" v-model="$parent.notification.yzjType" type="text" class="form-control" required />
path: src/components/notifications/YZJ.vue
user: login: CommanderStorm
body: What is this? is it maybe an enum (=> `<select>`)?

Could you please add a helptext what this is?
created_at: 2025-03-11T01:38:10Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,16 @@
+<template>
+    <div class="mb-3">
+        <label for="yzjWebHookUrl" class="form-label">{{ $t("YZJ Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="yzjWebHookUrl" v-model="$parent.notification.yzjWebHookUrl" type="text" class="form-control" required />
path: src/components/notifications/YZJ.vue
user: login: CommanderStorm
body: with these dividers, we "usually" try to keep inputs separated.
Could add them as well?

```suggestion
    <div class="mb-3">
        <label for="yzjWebHookUrl" class="form-label">{{ $t("YZJ Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
        <input id="yzjWebHookUrl" v-model="$parent.notification.yzjWebHookUrl" type="text" class="form-control" required />
    </div>
```
created_at: 2025-03-11T01:39:30Z
author_association: COLLABORATOR
diff_hunk: @@ -1051,5 +1051,9 @@
     "RabbitMQ Password": "RabbitMQ Password",
     "rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
     "SendGrid API Key": "SendGrid API Key",
-    "Separate multiple email addresses with commas": "Separate multiple email addresses with commas"
+    "Separate multiple email addresses with commas": "Separate multiple email addresses with commas",
+    "YZJ": "YZJ",
path: src/lang/en.json
user: login: CommanderStorm
body: ```suggestion
```
created_at: 2025-03-11T01:41:39Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,16 @@
+<template>
+    <div class="mb-3">
+        <label for="yzjWebHookUrl" class="form-label">{{ $t("YZJ Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="yzjWebHookUrl" v-model="$parent.notification.yzjWebHookUrl" type="text" class="form-control" required />
+        <label for="yzjType" class="form-label">{{ $t("YZJ Robot Type") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="yzjType" v-model="$parent.notification.yzjType" type="text" class="form-control" required />
+        <label for="yzjToken" class="form-label">{{ $t("YZJ Robot Token") }}<span style="color: red;"><sup>*</sup></span></label>
+        <hidden-input id="yzjToken" v-model="$parent.notification.yzjToken" type="text" class="form-control" required />
+        <i18n-t class="form-text" keypath="wayToGetTeamsURL">
+            <a href="https://www.yunzhijia.com/opendocs/docs.html#/tutorial/index/robot" target="_blank">{{ $t("here") }}</a>
+        </i18n-t>
path: src/components/notifications/YZJ.vue
user: login: CommanderStorm
body: > You can learn how to create a webhook URL [here](https://www.yunzhijia.com/opendocs/docs.html#/tutorial/index/robot)

Please move this up to the webhook url and add a short helptext here where in the UI `yzjToken`s can be found.
created_at: 2025-03-11T01:43:53Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,25 @@
+<template>
+    <div class="mb-3">
+        <label for="yzjWebHookUrl" class="form-label">{{ $t("YZJ Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="yzjWebHookUrl" v-model="$parent.notification.yzjWebHookUrl" type="url" class="form-control" required />
+        <i18n-t class="form-text" keypath="wayToGetTeamsURL">
+            <a href="https://www.yunzhijia.com/opendocs/docs.html#/tutorial/index/robot" target="_blank">{{ $t("here") }}</a>
+        </i18n-t>
+    </div>
+    <div class="mb-3">
+        <label for="yzjType" class="form-label">{{ $t("YZJ Robot Type") }}<span style="color: red;"><sup>*</sup></span></label>
+        <select id="yzjType" v-model="$parent.notification.yzjType" class="form-control" required>
+            <option :value="0">{{ $t("YZJ General Robot") }}</option>
+        </select>
+    </div>
+    <div class="mb-3">
+        <label for="yzjToken" class="form-label">{{ $t("YZJ Robot Token") }}<span style="color: red;"><sup>*</sup></span></label>
+        <hidden-input id="yzjToken" v-model="$parent.notification.yzjToken" type="text" class="form-control" required />
path: src/components/notifications/YZJ.vue
user: login: CommanderStorm
body: Lets not be special here..
Currently, the `form-control` would be adding the weird halo..

```suggestion
        <HiddenInput id="yzjToken" v-model="$parent.notification.yzjToken" :required="true" autocomplete="new-password"></HiddenInput>
```
created_at: 2025-03-11T14:21:13Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,25 @@
+<template>
+    <div class="mb-3">
+        <label for="yzjWebHookUrl" class="form-label">{{ $t("YZJ Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="yzjWebHookUrl" v-model="$parent.notification.yzjWebHookUrl" type="url" class="form-control" required />
+        <i18n-t class="form-text" keypath="wayToGetTeamsURL">
+            <a href="https://www.yunzhijia.com/opendocs/docs.html#/tutorial/index/robot" target="_blank">{{ $t("here") }}</a>
+        </i18n-t>
+    </div>
+    <div class="mb-3">
+        <label for="yzjType" class="form-label">{{ $t("YZJ Robot Type") }}<span style="color: red;"><sup>*</sup></span></label>
path: src/components/notifications/YZJ.vue
user: login: CommanderStorm
body: what does this field mean?
Could you please add a helptext to clarify this?

Also:
if there is truely only one option for this value, we don't need a selection and can remove it.
created_at: 2025-03-11T14:22:59Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,72 @@
+const NotificationProvider = require("./notification-provider");
+const { DOWN, UP } = require("../../src/util");
+const { default: axios } = require("axios");
+
+class YZJ extends NotificationProvider {
+    name = "YZJ";
+
+    /**
+     * @inheritdoc
+     */
+    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
+        let okMsg = "Sent Successfully.";
+
+        try {
+
+            if (heartbeatJSON != null) {
+                msg = `${this.statusToString(heartbeatJSON["status"])} ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
+            }
+
+            let params = {
+                content: msg
+            };
+
+            if (await this.sendToYZJ(notification, params)) {
+                return okMsg;
+            }
+        } catch (error) {
+            this.throwGeneralAxiosError(error);
+        }
+    }
+
+    /**
+     * Send message to YZJ
+     * @param {object} notification Notification
+     * @param {object} params Parameters of message
+     * @returns {boolean} True if successful else false
+     */
+    async sendToYZJ(notification, params) {
+
+        let config = {
+            method: "POST",
+            headers: {
+                "Content-Type": "application/json",
+            },
+            url: `${notification.yzjWebHookUrl}?yzjtype=${notification.yzjType}&yzjtoken=${notification.yzjToken}`,
+            data: JSON.stringify(params),
+        };
+
+        let result = await axios(config);
+        if (!result.data?.success) {
+            throw new Error(result.data?.errmsg);
+        }
+    }
path: server/notification-providers/yzj.js
user: login: CommanderStorm
body: lets not make this notification provider special

```suggestion
            if (heartbeatJSON !== null) {
                msg = `${this.statusToString(heartbeatJSON["status"])} ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
            }

            const config = {
                headers: {
                    "Content-Type": "application/json",
                },
            };
            const params = {
                content: msg
            };
            const url = `${notification.yzjWebHookUrl}?yzjtype=${notification.yzjType}&yzjtoken=${notification.yzjToken}`;

            const result = await axios.post(url, params, config);
            if (!result.data?.success) {
                throw new Error(result.data?.errmsg ?? "yzj's server did not respond with the expected result");
            }
            return okMsg;
        } catch (error) {
            this.throwGeneralAxiosError(error);
        }
    }
```
created_at: 2025-03-11T14:31:01Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,25 @@
+<template>
+    <div class="mb-3">
+        <label for="yzjWebHookUrl" class="form-label">{{ $t("YZJ Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="yzjWebHookUrl" v-model="$parent.notification.yzjWebHookUrl" type="url" class="form-control" required />
+        <i18n-t class="form-text" keypath="wayToGetTeamsURL">
+            <a href="https://www.yunzhijia.com/opendocs/docs.html#/tutorial/index/robot" target="_blank">{{ $t("here") }}</a>
+        </i18n-t>
+    </div>
+    <div class="mb-3">
+        <label for="yzjType" class="form-label">{{ $t("YZJ Robot Type") }}<span style="color: red;"><sup>*</sup></span></label>
path: src/components/notifications/YZJ.vue
user: login: innerpeacez
body: There can be multiple values, but I've only implemented one of them so far
created_at: 2025-03-12T05:10:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,56 @@
+const NotificationProvider = require("./notification-provider");
+const { DOWN, UP } = require("../../src/util");
+const { default: axios } = require("axios");
+
+class YZJ extends NotificationProvider {
+    name = "YZJ";
+
+    /**
+     * @inheritdoc
+     */
+    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
+        let okMsg = "Sent Successfully.";
+
+        try {
+            if (heartbeatJSON !== null) {
+                msg = `${this.statusToString(heartbeatJSON["status"])} ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
+            }
+
+            const config = {
+                headers: {
+                    "Content-Type": "application/json",
+                },
+            };
+            const params = {
+                content: msg
+            };
+            const url = `${notification.yzjWebHookUrl}?yzjtype=0&yzjtoken=${notification.yzjToken}`;
path: server/notification-providers/yzj.js
user: login: CommanderStorm
body: ```suggestion
            // yzjtype=0 => general robot
            const url = `${notification.yzjWebHookUrl}?yzjtype=0&yzjtoken=${notification.yzjToken}`;
```
created_at: 2025-03-14T01:31:03Z
author_association: COLLABORATOR

PR comments:
user: login: innerpeacez
created_at: 2025-03-10T07:13:17Z
body: I don't quite understand why this ci `prevent-file-change` is reporting an error, need help!
author_association: CONTRIBUTOR
user: login: innerpeacez
created_at: 2025-03-10T09:39:05Z
body: all ci passed.
author_association: CONTRIBUTOR
user: login: innerpeacez
created_at: 2025-03-11T02:24:05Z
body: After：
![image](https://github.com/user-attachments/assets/539deef9-45e1-43d0-9d29-c318836c62c8)

author_association: CONTRIBUTOR
user: login: innerpeacez
created_at: 2025-03-11T02:30:19Z
body: @CommanderStorm Thanks for your feedback. All terms have been done, please let me know if I'm still missing something!
author_association: CONTRIBUTOR
user: login: innerpeacez
created_at: 2025-03-12T05:34:00Z
body: > I am still a bit confused about `YZJ Robot Type`.. What is this? can we remove it?

YZJ supports many types of robots, currently I only support general robot, so I do not recommend deleting this type option to support other types later.

@CommanderStorm 


author_association: CONTRIBUTOR
user: login: innerpeacez
created_at: 2025-03-12T05:38:07Z
body: ![image](https://github.com/user-attachments/assets/b02f35a2-0730-4482-b484-af226d119e31)

author_association: CONTRIBUTOR
user: login: CommanderStorm
created_at: 2025-03-12T16:01:34Z
body: > YZJ supports many types of robots, currently I only support general robot

But what is a general robot and what are the types of robots?
I am really confused as the docs are not avaliable in english. The page you linked only ever has `yzjtype=0`.

I think a helptext would go a long way for this fied..
author_association: COLLABORATOR
user: login: innerpeacez
created_at: 2025-03-13T01:24:50Z
body: You're right. I went through the YZJ documentation carefully. It turns out that yzjType is only for general robots, and other robot types can't use it to indicate their type. So, I removed the yzjType field. @CommanderStorm 

After: 
![image](https://github.com/user-attachments/assets/20008d75-1838-423b-9795-c7c5320cbf81)

author_association: CONTRIBUTOR