Zabbix 5.0 で Slack に通知するメッセージ内容を編集する

Zabbix5.0はwebhookを利用することでSlack連携出来ますが、今回はその内容をデフォルトから編集して通知内容を好きなようにしてみます。
今回は**①webhook-javascriptを編集する方法**と、**②旧来のアクション設定のメッセージを活用する方法**の2点を記載します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/1b089bea-8cbe-3098-5f81-74f290c661c4.png)


:::note info
Slack-botを作成してデフォルト内容を通知できるようになるまでは以下から。
:::

https://qiita.com/Higemal/items/f0bc18bf66e54628b22a



#1. webhook-javascriptを編集する
メディアタイプのスクリプトの内容を修正することで対応する方法です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/98582e55-1787-1237-2640-1b2e242b9fa5.png)

基本的には、`スクリプト`のjavascriptを修正しますが、その際にはzabbixのマクロを多用して修正します。

https://www.zabbix.com/documentation/5.0/manual/appendix/macros/supported_by_location

</br>
</br>

##1.1 通知タイトルの修正
slackメッセージ最上部の通知タイトルを修正します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/db046cc2-79ea-970f-b2f2-c5fc28e7f146.png)

通知タイトルはマクロ`alert.subject`を利用しています。
こちらの内容はアクション設定の件名を利用していますので、こちらを編集しておきます。

[設定] - [アクション] - [対象のアクション]を開き、[実行内容]タブから[実行内容]の[実行内容の詳細]を開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/a7cf3744-ec19-34dd-0fc4-2b1d523d9b7f.png)


ここで、`メッセージのカスタマイズ`にチェックを入れ、`件名`欄にslackで表示したい内容を入力します。
こちらにもマクロを利用可能ですが、今回は特に入れていません。

:::note info
メッセージ欄は入力不要です。入力しても今回はslackメッセージに反映されません。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/79907ef3-0f3c-9eb9-2d3e-e3208a35ae1d.png)

同様に復旧時の通知内容についても変更しておくと、障害発生時と復旧時のslackメッセージ内容が変更されます。

:::note warn
通知タイトルも下記のマクロのように修正してもよいのですが、既存のwebhook-javascriptは通知タイトルがzabbixへのハイパーリンクとなるようになっていますので、そのまま便利に使わせてもらうため上記のようにしています。
:::

</br>
</br>

##1.2 項目名修正
デフォルトは項目名が英語となっているため、日本語に変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/ff772d40-dab9-0297-f971-7b91128f0f62.png)

以後はメディアタイプのスクリプトを修正していきます。
メディアタイプのスクリプトを展開します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/9032f13b-7b24-ff66-1081-ac2829018bff.png)


デフォルト内容の210行目～288行目あたりがslack通知内容を記載しているところになります。
例として、`Host`と表示されていたホスト名を`ホスト名`と日本語表示する際の記載変更が以下となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/3da28f00-02c3-dea2-fe48-e99a20477c5a.png)

このように、基本的には以下のコードブロックで通知内容を表現できるので、適宜、小タイトルとマクロにて内容を修正することが出来ます。

```javascript:fields
        fields: [
            {
                title: '小タイトル',
                value: 'Zabbixマクロ',
                short: true
            }
        ]
```

</br>
</br>

##1.3 絵文字利用
slack絵文字を利用することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/2efa8eb0-9d5f-d8df-4edb-8df6f19f36d8.png)


slack絵文字をコードとして埋め込むことが可能なので、そちらを適宜内容に追加しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/b04f2425-fdb0-c81d-4038-1e1dff85f4fc.png)

https://www.webfx.com/tools/emoji-cheat-sheet/

例として、`1.2 項目名修正`の小タイトル修正時にも、日本語タイトルの横に`:computer:`と記載することで絵文字をslackメッセージ内に入れ込んでいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/f4f78d1b-7a6b-fd3c-769f-345b503a0ed2.png)


</br>
</br>

##1.4 深刻度の日本語化
深刻度についてはデフォルトで英語表記となっているので日本語化してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/e37e09c7-4194-b93b-580d-eef133485f8c.png)

`event.severity`の内容

マクロの記載 | 日本語転換
--- | ---
Disaster | 致命的な障害
High | 重度の障害
Average | 軽度の障害
Warning | 警告
Information | 情報
Not classified | 未分類

以下のコード部分について、適当にswitch文で置換して表現しています。

```javascript:serverity_before
        message.fields.push(
            {
                title: 'Severity',
                value: params.event_severity,
                short: true
            }
```

　↓↓↓

```javascript:serverity_after
        var SEVERITY_JP = '';
        switch (params.event_severity){
            case 'Disaster': SEVERITY_JP = '致命的な障害'; break;
            case 'High': SEVERITY_JP = '重度の障害'; break;
            case 'Average': SEVERITY_JP = '軽度の障害'; break;
            case 'Warning': SEVERITY_JP = '警告'; break;
            case 'Information': SEVERITY_JP = '情報'; break;
            case 'Not classified': SEVERITY_JP = '未分類'; break;
            default: SEVERITY_JP = 'その他';
            }

        message.fields.push(
            {
                title: '深刻度:boom:',
                value: SEVERITY_JP,
                short: true
            }
```

</br>
</br>

##1.5 回復時の内容
回復時間や障害継続時間については障害回復時のみ表示可能となるため、回復時のみ通知するような部分を作成してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/b5e3ba37-ca65-d8a9-3ac1-b41a33c191c9.png)

まず、障害/回復/更新のステータス変更については、zabbixマクロで以下のフラグ判別が可能です。
よって、`event.value`が`0`の場合のみ、内容が出力されるようブロックを作成します。

ステータス | event.value  | event.update.status
--- | --- | ---
障害発生 | 1 | 0
回復 | 0 | -
更新 | 1 | 1

```javascript:only_repair_message
    if (params.event_value === '0') {
        message.fields.push(
            {
                title: '回復時間:clock10:',
                value: params.event_recovery_date + ' ' + params.event_recovery_time,
                short: true
            },
            {
                title: '継続時間:clock10:',
                value: params.event_duration,
                short: true
            }
        );

    }
```

:::note warn
既存コードの Event time のように value: '{0} {1}'.format(event_recovery_date, event_recovery_time) と記載すると、以下のように undefined undefined となってしまいますので、上記のように結合をしています。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/100d504e-113b-e3b6-ad4f-85cc54d746ba.png)

＃変数内容が障害発生時に固定されてしまうからでしょうか…

</br>
</br>

##1.6 その他(トリガーの説明)
slackのメディアタイプに限らないですが、トリガー名だけでは障害がわかりにくいこともあるので、トリガーの説明を記載して通知に含めます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/93d0e149-ff8c-a400-393a-b8cb8e03a337.png)

こちらはトリガーの設定に内容を記載し、メディアタイプとしては`trigger.description`を利用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/bfbbf9fc-706c-f939-a1d4-9a5f96ac4fae.png)


```javascript:trigger_description
            {
                title: 'トリガーの説明:notebook_with_decorative_cover:',
                value: params.trigger_description,
                short: true
            }
```

</br>
</br>

##1.7 今回利用したコード
なお、下記を利用するほか、メディアタイプに`event_duration`を追加しています（下記の真ん中）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/35859c9e-faa6-9fa4-2484-a4eade38882b.png)

<details><summary>こちら</summary><div>

```javascript:webhook_edit
var SEVERITY_COLORS = [
    '#97AAB3', '#7499FF', '#FFC859',
    '#FFA059', '#E97659', '#E45959'
];

var RESOLVE_COLOR = '#009900';

var SLACK_MODE_HANDLERS = {
    alarm: handlerAlarm,
    event: handlerEvent
};


if (!String.prototype.format) {
    String.prototype.format = function() {
        var args = arguments;

        return this.replace(/{(\d+)}/g, function(match, number) {
            return number in args
                ? args[number]
                : match
            ;
        });
    };
}

function isEventProblem(params) {
    return params.event_value == 1
        && params.event_update_status == 0
    ;
}

function isEventUpdate(params) {
    return params.event_value == 1
        && params.event_update_status == 1
    ;
}

function isEventResolve(params) {
    return params.event_value == 0;
}

function getPermalink(channelId, messageTimestamp) {
    var req = new CurlHttpRequest();

    if (typeof params.HTTPProxy === 'string' && params.HTTPProxy.trim() !== '') {
        req.SetProxy(params.HTTPProxy);
    }

    req.AddHeader('Content-Type: application/x-www-form-urlencoded; charset=utf-8');
    req.AddHeader('Authorization: Bearer ' + params.bot_token);

    var query = '{0}?channel={1}&message_ts={2}'.format(
            Slack.getPermalink,
            encodeURIComponent(channelId),
            encodeURIComponent(messageTimestamp)),
        resp = JSON.parse(req.Get(query));

    if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
        throw 'message was created, but getting message link was failed with reason "' + resp.error + '"';
    }

    return resp.permalink;
}

function createProblemURL(zabbix_url, triggerid, eventid, event_source) {
    var problem_url = '';
    if (event_source === '0') {
        problem_url = '{0}/tr_events.php?triggerid={1}&eventid={2}'
            .format(
                zabbix_url,
                triggerid,
                eventid
            );
    }
    else {
        problem_url = zabbix_url;
    }

    return problem_url;
}

function handlerAlarm(params) {
    var fields = {
        channel: params.channel,
        as_user: params.slack_as_user,
    };

    if (isEventProblem(params)) {
        fields.attachments = [
            createMessage(
                SEVERITY_COLORS[params.event_nseverity] || 0,
                params.event_date,
                params.event_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source)
            )
        ];

        var resp = JSON.parse(req.Post(Slack.postMessage, JSON.stringify(fields)));

        if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
            throw resp.error;
        }

        result.tags.__message_ts = resp.ts;
        result.tags.__channel_id = resp.channel;
        result.tags.__channel_name = params.channel;
        result.tags.__message_link = getPermalink(resp.channel, resp.ts);
    }
    else if (isEventUpdate(params)) {
        fields.thread_ts = params.message_ts;
        fields.attachments = [
            createMessage(
                SEVERITY_COLORS[params.event_nseverity] || 0,
                params.event_update_date,
                params.event_update_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source),
                true
            )
        ];

        resp = JSON.parse(req.Post(Slack.postMessage, JSON.stringify(fields)));
        if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
            throw resp.error;
        }

    }
    else if (isEventResolve(params)) {
        fields.channel = params.channel_id;
        fields.text = '';
        fields.ts = params.message_ts;
        fields.attachments = [
            createMessage(
                RESOLVE_COLOR,
                params.event_date,
                params.event_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source)
            )
        ];

        resp = JSON.parse(req.Post(Slack.chatUpdate, JSON.stringify(fields)));
        if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
            throw resp.error;
        }
    }
}

function handlerEvent(params) {
    var fields = {
        channel: params.channel,
        as_user: params.slack_as_user
    };

    if (isEventProblem(params)) {
        fields.attachments = [
            createMessage(
                SEVERITY_COLORS[params.event_nseverity] || 0,
                params.event_date,
                params.event_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source)
            )
        ];

        var resp = JSON.parse(req.Post(Slack.postMessage, JSON.stringify(fields)));

        if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
            throw resp.error;
        }

        result.tags.__channel_name = params.channel;
        result.tags.__message_link = getPermalink(resp.channel, resp.ts);

    }
    else if (isEventUpdate(params)) {
        fields.attachments = [
            createMessage(
                SEVERITY_COLORS[params.event_nseverity] || 0,
                params.event_update_date,
                params.event_update_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source),
                false
            )
        ];

        resp = JSON.parse(req.Post(Slack.postMessage, JSON.stringify(fields)));

        if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
            throw resp.error;
        }

    }
    else if (isEventResolve(params)) {
        fields.attachments = [
            createMessage(
                RESOLVE_COLOR,
                params.event_recovery_date,
                params.event_recovery_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source)
            )
        ];

        resp = JSON.parse(req.Post(Slack.postMessage, JSON.stringify(fields)));

        if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
            throw resp.error;
        }
    }
}

function createMessage(
    event_severity_color,
    event_date,
    event_time,
    problem_url,
    isShort,
    messageText,
    event_recovery_date,　
    event_recovery_time
) {
    var message = {
        fallback: params.alert_subject,
        title: params.alert_subject,
        color: event_severity_color,
        title_link: problem_url,
        pretext: messageText || '',

        fields: [
            {
                title: 'ホスト名:computer:',
                value: '{0} [{1}]'.format(params.host_name, params.host_conn),
                short: true
            },
            {
                title: 'トリガー名:gun:',
                value: params.event_name,
                short: true

            }
        ],
    };

    if (params.event_source === '0') {
　　　　　var SEVERITY_JP = '';
        switch (params.event_severity){
            case 'Disaster': SEVERITY_JP = '致命的な障害'; break;
            case 'High': SEVERITY_JP = '重度の障害'; break;
            case 'Average': SEVERITY_JP = '軽度の障害'; break;
            case 'Warning': SEVERITY_JP = '警告'; break;
            case 'Information': SEVERITY_JP = '情報'; break;
            case 'Not classified': SEVERITY_JP = '未分類'; break;
            default: SEVERITY_JP = 'その他';
            }

        message.fields.push(
            {
                title: '深刻度:boom:',
                value: SEVERITY_JP,
                short: true
            },
            {
                title: '最新の値:new:',
                value: params.event_opdata,
                short: true
            }
        );
    }

    if (!isShort  && params.event_source === '0') {
        message['actions'] = [
            {
                type: 'button',
                text: 'Zabbixを開く:mag_right:',
                url: problem_url
            }
        ];

        message.fields.push(
            {
                title: '発生時間:clock10:',
                value: '{0} {1}'.format(event_date, event_time),
                short: true
            },
            {
                title: 'トリガーの説明:notebook_with_decorative_cover:',
                value: params.trigger_description,
                short: true
            }
        );

    }

    if (params.event_source !== '0' || params.event_update_status === '1') {
        message.fields.push(
            {
                title: 'Details',
                value: params.alert_message,
                short: false
            }
        );
    }

    if (params.event_value === '0') {
        message.fields.push(
            {
                title: '回復時間:clock10:',
                value: params.event_recovery_date + ' ' + params.event_recovery_time,
                short: true
            },
            {
                title: '継続時間:clock10:',
                value: params.event_duration,
                short: true
            }
        );

    }


    return message;
}

function validateParams(params) {
    if (typeof params.bot_token !== 'string' || params.bot_token.trim() === '') {
        throw 'Field "bot_token" cannot be empty';
    }

    if (typeof params.channel !== 'string' || params.channel.trim() === '') {
        throw 'Field "channel" cannot be empty';
    }

    if (isNaN(params.event_id)) {
        throw 'Field "event_id" is not a number';
    }

    if ([0, 1, 2, 3].indexOf(parseInt(params.event_source)) === -1) {
        throw 'Incorrect "event_source" parameter given: "' + params.event_source + '".\nMust be 0-3.';
    }

    if (params.event_source !== '0') {
        params.event_nseverity = '0';
        params.event_severity = 'Not classified';
        params.event_update_status = '0';
        params.slack_mode = 'event';
    }

    if (params.event_source === '1' || params.event_source === '2') {
        params.event_value = '1';
    }

    if (params.event_source === '1') {
        params.host_name = params.discovery_host_dns;
        params.host_ip = params.discovery_host_ip;
    }

    if (!~[0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity))) {
        throw 'Incorrect "event_nseverity" parameter given: ' + params.event_nseverity + '\nMust be 0-5.';
    }

    if (typeof params.event_severity !== 'string' || params.event_severity.trim() === '') {
        throw 'Field "event_severity" cannot be empty';
    }

    if (params.event_update_status !== '0' && params.event_update_status !== '1') {
        throw 'Incorrect "event_update_status" parameter given: ' + params.event_update_status + '\nMust be 0 or 1.';
    }

    if (params.event_value !== '0' && params.event_value !== '1') {
        throw 'Incorrect "event_value" parameter given: ' + params.event_value + '\nMust be 0 or 1.';
    }

    if (typeof params.host_conn !== 'string' || params.host_conn.trim() === '') {
        throw 'Field "host_conn" cannot be empty';
    }

    if (typeof params.host_name !== 'string' || params.host_name.trim() === '') {
        throw 'Field "host_name" cannot be empty';
    }

    if (!~['true', 'false'].indexOf(params.slack_as_user.toLowerCase())) {
        throw 'Incorrect "slack_as_user" parameter given: ' + params.slack_as_user + '\nMust be "true" or "false".';
    }

    if (!~['alarm', 'event'].indexOf(params.slack_mode)) {
        throw 'Incorrect "slack_mode" parameter given: ' + params.slack_mode + '\nMust be "alarm" or "event".';
    }

    if (isNaN(params.trigger_id) && params.event_source === '0') {
        throw 'field "trigger_id" is not a number';
    }

    if (typeof params.zabbix_url !== 'string' || params.zabbix_url.trim() === '') {
        throw 'Field "zabbix_url" cannot be empty';
    }

    if (!/^(http|https):\/\/.+/.test(params.zabbix_url)) {
        throw 'Field "zabbix_url" must contain a schema';
    }
}

try {
    var params = JSON.parse(value);

    validateParams(params);

    var req = new CurlHttpRequest(),
        result = {tags: {}};

    if (typeof params.HTTPProxy === 'string' && params.HTTPProxy.trim() !== '') {
        req.SetProxy(params.HTTPProxy);
    }

    req.AddHeader('Content-Type: application/json; charset=utf-8');
    req.AddHeader('Authorization: Bearer ' + params.bot_token);

    var slack_endpoint = 'https://slack.com/api/';

    var Slack = {
        postMessage: slack_endpoint + 'chat.postMessage',
        getPermalink: slack_endpoint + 'chat.getPermalink',
        chatUpdate: slack_endpoint + 'chat.update'
    };

    params.slack_mode = params.slack_mode.toLowerCase();
    params.slack_mode = params.slack_mode in SLACK_MODE_HANDLERS
        ? params.slack_mode
        : 'alarm';

    SLACK_MODE_HANDLERS[params.slack_mode](params);

    if (params.event_source === '0') {
        return JSON.stringify(result);
    }
    else {
        return 'OK';
    }
}
catch (error) {
    Zabbix.Log(4, '[ Slack Webhook ] Slack notification failed : ' + error);
    throw 'Slack notification failed : ' + error;
}
```

</div></details>





</br>
</br>

#2. アクション設定のメッセージを活用
zabbixメール通知などで利用していた、アクション内のメッセージを利用してslack連携する方法となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/9a2552ef-fd3f-b6c4-2bcb-09231dd260c0.png)

:::note info
zabbix 4.0などで以下の画面で設定していた内容となります。
:::

</br>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/dc5be68f-7ed7-b20d-5546-bfa32354816d.png)

</br>
</br>

##2.1 アクション設定
アクション設定にて、`メッセージのカスタマイズ`にチェックを入れ、`件名`および`メッセージ`欄にslackで表示したい内容を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/ee532f4e-27e6-42ed-c7bc-6376b85bbdf6.png)


```text:メッセージ
以下の障害が発生しました。

発生時刻　　{EVENT.DATE} {EVENT.TIME} 
発生ホスト　{HOST.NAME}
深刻度　　　{EVENT.SEVERITY}
発生障害　　{EVENT.NAME}

障害の説明
------------------------------------
{TRIGGER.DESCRIPTION}
```

</br>
</br>

##2.2 メディアタイプ設定
続いて、メディアタイプのスクリプト設定を変更します。

先ほど設定した`件名`および`メッセージ`欄は以下のマクロとなりますので、それ以外の内容を全面的に除外しています。
デフォルト内容の210行目～288行目あたりがslack通知内容を記載しているところになります。
下記変更で対応は完了となります。

アクション | マクロ
--- | ---
件名 | alert.subject
メッセージ | alert_message

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/9bf70e29-31b4-ec71-202b-bc69f6c70585.png)

</br></br>

##2.3 今回利用したコード
スクリプト内容は以下となります。

<details><summary>こちら</summary><div>

```javascript:webhook+action
var SEVERITY_COLORS = [
    '#97AAB3', '#7499FF', '#FFC859',
    '#FFA059', '#E97659', '#E45959'
];

var RESOLVE_COLOR = '#009900';

var SLACK_MODE_HANDLERS = {
    alarm: handlerAlarm,
    event: handlerEvent
};


if (!String.prototype.format) {
    String.prototype.format = function() {
        var args = arguments;

        return this.replace(/{(\d+)}/g, function(match, number) {
            return number in args
                ? args[number]
                : match
            ;
        });
    };
}

function isEventProblem(params) {
    return params.event_value == 1
        && params.event_update_status == 0
    ;
}

function isEventUpdate(params) {
    return params.event_value == 1
        && params.event_update_status == 1
    ;
}

function isEventResolve(params) {
    return params.event_value == 0;
}

function getPermalink(channelId, messageTimestamp) {
    var req = new CurlHttpRequest();

    if (typeof params.HTTPProxy === 'string' && params.HTTPProxy.trim() !== '') {
        req.SetProxy(params.HTTPProxy);
    }

    req.AddHeader('Content-Type: application/x-www-form-urlencoded; charset=utf-8');
    req.AddHeader('Authorization: Bearer ' + params.bot_token);

    var query = '{0}?channel={1}&message_ts={2}'.format(
            Slack.getPermalink,
            encodeURIComponent(channelId),
            encodeURIComponent(messageTimestamp)),
        resp = JSON.parse(req.Get(query));

    if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
        throw 'message was created, but getting message link was failed with reason "' + resp.error + '"';
    }

    return resp.permalink;
}

function createProblemURL(zabbix_url, triggerid, eventid, event_source) {
    var problem_url = '';
    if (event_source === '0') {
        problem_url = '{0}/tr_events.php?triggerid={1}&eventid={2}'
            .format(
                zabbix_url,
                triggerid,
                eventid
            );
    }
    else {
        problem_url = zabbix_url;
    }

    return problem_url;
}

function handlerAlarm(params) {
    var fields = {
        channel: params.channel,
        as_user: params.slack_as_user,
    };

    if (isEventProblem(params)) {
        fields.attachments = [
            createMessage(
                SEVERITY_COLORS[params.event_nseverity] || 0,
                params.event_date,
                params.event_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source)
            )
        ];

        var resp = JSON.parse(req.Post(Slack.postMessage, JSON.stringify(fields)));

        if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
            throw resp.error;
        }

        result.tags.__message_ts = resp.ts;
        result.tags.__channel_id = resp.channel;
        result.tags.__channel_name = params.channel;
        result.tags.__message_link = getPermalink(resp.channel, resp.ts);
    }
    else if (isEventUpdate(params)) {
        fields.thread_ts = params.message_ts;
        fields.attachments = [
            createMessage(
                SEVERITY_COLORS[params.event_nseverity] || 0,
                params.event_update_date,
                params.event_update_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source),
                true
            )
        ];

        resp = JSON.parse(req.Post(Slack.postMessage, JSON.stringify(fields)));
        if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
            throw resp.error;
        }

    }
    else if (isEventResolve(params)) {
        fields.channel = params.channel_id;
        fields.text = '';
        fields.ts = params.message_ts;
        fields.attachments = [
            createMessage(
                RESOLVE_COLOR,
                params.event_date,
                params.event_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source)
            )
        ];

        resp = JSON.parse(req.Post(Slack.chatUpdate, JSON.stringify(fields)));
        if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
            throw resp.error;
        }
    }
}

function handlerEvent(params) {
    var fields = {
        channel: params.channel,
        as_user: params.slack_as_user
    };

    if (isEventProblem(params)) {
        fields.attachments = [
            createMessage(
                SEVERITY_COLORS[params.event_nseverity] || 0,
                params.event_date,
                params.event_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source)
            )
        ];

        var resp = JSON.parse(req.Post(Slack.postMessage, JSON.stringify(fields)));

        if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
            throw resp.error;
        }

        result.tags.__channel_name = params.channel;
        result.tags.__message_link = getPermalink(resp.channel, resp.ts);

    }
    else if (isEventUpdate(params)) {
        fields.attachments = [
            createMessage(
                SEVERITY_COLORS[params.event_nseverity] || 0,
                params.event_update_date,
                params.event_update_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source),
                false
            )
        ];

        resp = JSON.parse(req.Post(Slack.postMessage, JSON.stringify(fields)));

        if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
            throw resp.error;
        }

    }
    else if (isEventResolve(params)) {
        fields.attachments = [
            createMessage(
                RESOLVE_COLOR,
                params.event_recovery_date,
                params.event_recovery_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source)
            )
        ];

        resp = JSON.parse(req.Post(Slack.postMessage, JSON.stringify(fields)));

        if (req.Status() != 200 || !resp.ok || resp.ok === 'false') {
            throw resp.error;
        }
    }
}

function createMessage(
    event_severity_color,
    event_date,
    event_time,
    problem_url,
    isShort,
    messageText
) {
    var message = {
        fallback: params.alert_subject,
        title: params.alert_subject,
        color: event_severity_color,
        title_link: problem_url,
        pretext: messageText || '',

        fields: [
            {
                value: params.alert_message,
                short: true

            }
        ],
    };

    return message;
}

function validateParams(params) {
    if (typeof params.bot_token !== 'string' || params.bot_token.trim() === '') {
        throw 'Field "bot_token" cannot be empty';
    }

    if (typeof params.channel !== 'string' || params.channel.trim() === '') {
        throw 'Field "channel" cannot be empty';
    }

    if (isNaN(params.event_id)) {
        throw 'Field "event_id" is not a number';
    }

    if ([0, 1, 2, 3].indexOf(parseInt(params.event_source)) === -1) {
        throw 'Incorrect "event_source" parameter given: "' + params.event_source + '".\nMust be 0-3.';
    }

    if (params.event_source !== '0') {
        params.event_nseverity = '0';
        params.event_severity = 'Not classified';
        params.event_update_status = '0';
        params.slack_mode = 'event';
    }

    if (params.event_source === '1' || params.event_source === '2') {
        params.event_value = '1';
    }

    if (params.event_source === '1') {
        params.host_name = params.discovery_host_dns;
        params.host_ip = params.discovery_host_ip;
    }

    if (!~[0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity))) {
        throw 'Incorrect "event_nseverity" parameter given: ' + params.event_nseverity + '\nMust be 0-5.';
    }

    if (typeof params.event_severity !== 'string' || params.event_severity.trim() === '') {
        throw 'Field "event_severity" cannot be empty';
    }

    if (params.event_update_status !== '0' && params.event_update_status !== '1') {
        throw 'Incorrect "event_update_status" parameter given: ' + params.event_update_status + '\nMust be 0 or 1.';
    }

    if (params.event_value !== '0' && params.event_value !== '1') {
        throw 'Incorrect "event_value" parameter given: ' + params.event_value + '\nMust be 0 or 1.';
    }

    if (typeof params.host_conn !== 'string' || params.host_conn.trim() === '') {
        throw 'Field "host_conn" cannot be empty';
    }

    if (typeof params.host_name !== 'string' || params.host_name.trim() === '') {
        throw 'Field "host_name" cannot be empty';
    }

    if (!~['true', 'false'].indexOf(params.slack_as_user.toLowerCase())) {
        throw 'Incorrect "slack_as_user" parameter given: ' + params.slack_as_user + '\nMust be "true" or "false".';
    }

    if (!~['alarm', 'event'].indexOf(params.slack_mode)) {
        throw 'Incorrect "slack_mode" parameter given: ' + params.slack_mode + '\nMust be "alarm" or "event".';
    }

    if (isNaN(params.trigger_id) && params.event_source === '0') {
        throw 'field "trigger_id" is not a number';
    }

    if (typeof params.zabbix_url !== 'string' || params.zabbix_url.trim() === '') {
        throw 'Field "zabbix_url" cannot be empty';
    }

    if (!/^(http|https):\/\/.+/.test(params.zabbix_url)) {
        throw 'Field "zabbix_url" must contain a schema';
    }
}

try {
    var params = JSON.parse(value);

    validateParams(params);

    var req = new CurlHttpRequest(),
        result = {tags: {}};

    if (typeof params.HTTPProxy === 'string' && params.HTTPProxy.trim() !== '') {
        req.SetProxy(params.HTTPProxy);
    }

    req.AddHeader('Content-Type: application/json; charset=utf-8');
    req.AddHeader('Authorization: Bearer ' + params.bot_token);

    var slack_endpoint = 'https://slack.com/api/';

    var Slack = {
        postMessage: slack_endpoint + 'chat.postMessage',
        getPermalink: slack_endpoint + 'chat.getPermalink',
        chatUpdate: slack_endpoint + 'chat.update'
    };

    params.slack_mode = params.slack_mode.toLowerCase();
    params.slack_mode = params.slack_mode in SLACK_MODE_HANDLERS
        ? params.slack_mode
        : 'alarm';

    SLACK_MODE_HANDLERS[params.slack_mode](params);

    if (params.event_source === '0') {
        return JSON.stringify(result);
    }
    else {
        return 'OK';
    }
}
catch (error) {
    Zabbix.Log(4, '[ Slack Webhook ] Slack notification failed : ' + error);
    throw 'Slack notification failed : ' + error;
}
```

</div></details>

</br>
</br>

#参考
https://assets.zabbix.com/img/zabconf2020_jp/presentations/01_6zabconf2020.pdf

https://app.slack.com/block-kit-builder/

https://www.zabbix.com/documentation/5.0/manual/appendix/macros/supported_by_location

https://www.webfx.com/tools/emoji-cheat-sheet/

https://www.zabbix.com/documentation/2.2/jp/manual/config/triggers/severity

https://pc-pier.com/blog/2016/02/02/hiragana-emoji/