zabbix_export:
  version: '6.0'
  media_types:
    - name: 'MS Teams Workflow 6.0'
      type: WEBHOOK
      parameters:
        - name: alert_message
          value: '{ALERT.MESSAGE}'
        - name: alert_subject
          value: '{ALERT.SUBJECT}'
        - name: event_date
          value: '{EVENT.DATE}'
        - name: event_id
          value: '{EVENT.ID}'
        - name: event_nseverity
          value: '{EVENT.NSEVERITY}'
        - name: event_opdata
          value: '{EVENT.OPDATA}'
        - name: event_recovery_date
          value: '{EVENT.RECOVERY.DATE}'
        - name: event_recovery_time
          value: '{EVENT.RECOVERY.TIME}'
        - name: event_severity
          value: '{EVENT.SEVERITY}'
        - name: event_source
          value: '{EVENT.SOURCE}'
        - name: event_status
          value: '{EVENT.STATUS}'
        - name: event_tags
          value: '{EVENT.TAGS}'
        - name: event_time
          value: '{EVENT.TIME}'
        - name: event_update_action
          value: '{EVENT.UPDATE.ACTION}'
        - name: event_update_date
          value: '{EVENT.UPDATE.DATE}'
        - name: event_update_message
          value: '{EVENT.UPDATE.MESSAGE}'
        - name: event_update_status
          value: '{EVENT.UPDATE.STATUS}'
        - name: event_update_time
          value: '{EVENT.UPDATE.TIME}'
        - name: event_update_user
          value: '{USER.FULLNAME}'
        - name: event_value
          value: '{EVENT.VALUE}'
        - name: host_ip
          value: '{HOST.IP}'
        - name: host_name
          value: '{HOST.NAME}'
        - name: teams_endpoint
          value: '<PLACE WEBHOOK URL HERE>'
        - name: trigger_description
          value: '{TRIGGER.DESCRIPTION}'
        - name: trigger_id
          value: '{TRIGGER.ID}'
        - name: use_default_message
          value: 'false'
        - name: zabbix_url
          value: '{$ZABBIX.URL}'
      script: |
        var SEVERITY_COLORS = [
            'default',   // 0 - Not classified (gray)
            'accent',    // 1 - Information (blue)
            'warning',   // 2 - Warning (yellow)
            'attention', // 3 - Average (orange)
            'attention', // 4 - High (red)
            'attention', // 5 - Disaster (dark red)
            'good',      // 6 - Resolved (green)
            'default'    // 7 - Default (gray)
        ];

        var SEVERITY_HEX_COLORS = [
            '808080',    // 0 - Not classified (gray)
            '0000FF',    // 1 - Information (blue)
            'FFA500',    // 2 - Warning (orange)
            'FF0000',    // 3 - Average (red)
            'FF0000',    // 4 - High (red)
            '8B0000',    // 5 - Disaster (dark red)
            '00FF00',    // 6 - Resolved (green)
            '808080'     // 7 - Default (gray)
        ];

        var SEVERITY_NAMES = [
            'Not classified',  // 0
            'Information',     // 1
            'Warning',         // 2
            'Average',         // 3
            'High',            // 4
            'Disaster',        // 5
            'Resolved',        // 6
            'Default'          // 7 (Fallback)
        ];

        function formatDateToFrench(dateStr, timeStr) {
            var dateParts = dateStr.split('.'); 
            var timeParts = timeStr.split(':');
            var frenchDate = dateParts[2] + '.' + dateParts[1] + '.' + dateParts[0];
            var frenchTime = timeParts[0] + ':' + timeParts[1];
            return frenchTime + ' ' + frenchDate;
        }

        function formatOpData(opData) {
            // Check if opData matches the pattern "YYYY.MM.DD HH:MM:SS"
            var timestampPattern = /^\d{4}\.\d{2}\.\d{2}\s\d{2}:\d{2}:\d{2}$/;
            if (timestampPattern.test(opData)) {
                var parts = opData.split(' ');  // Split first, then assign
                var dateStr = parts[0];
                var timeStr = parts[1];
                return formatDateToFrench(dateStr, timeStr);
            }
            // Return unchanged if not a timestamp
            return opData;
        }

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

            if (typeof params.teams_endpoint !== 'string' || params.teams_endpoint.trim() === '') {
                throw 'Cannot get teams_endpoint';
            }
            else if (!params.teams_endpoint.startsWith('http')) {
                throw 'Invalid MS Teams webhook URL: ' + params.teams_endpoint;
            }

            params.zabbix_url = (params.zabbix_url.endsWith('/'))
                ? params.zabbix_url.slice(0, -1) : params.zabbix_url;

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

            // Set "use_default_message" to true for non trigger-based events.
            if (params.event_source !== '0') {
                params.use_default_message = 'true';
            }

            // Check {EVENT.VALUE} for trigger-based and internal events.
            if (params.event_value !== '0' && params.event_value !== '1'
                    && (params.event_source === '0' || params.event_source === '3')) {
                throw 'Incorrect "event_value" parameter given: "' + params.event_value + '".\nMust be 0 or 1.';
            }

            // Check {EVENT.UPDATE.STATUS} only for trigger-based events.
            if (params.event_update_status !== '0' && params.event_update_status !== '1' && params.event_source === '0') {
                throw 'Incorrect "event_update_status" parameter given: "' + params.event_update_status + '".\nMust be 0 or 1.';
            }
            // Convert severity to an integer and validate it
            params.event_nseverity = parseInt(params.event_nseverity, 10);

            // If the issue is resolved, always set severity to 6 (green)
            if (params.event_value == '0') {
                params.event_nseverity = 6;
            } 
            // If event is active, use its actual severity (fallback to 5 if invalid)
            else if (params.event_value == '1') {
                if (isNaN(params.event_nseverity) || params.event_nseverity < 0 || params.event_nseverity > 6) {
                    params.event_nseverity = 5; // Default to Disaster if out of range
                }
            }

            // If severity is out of bounds, set it to default (7)
            if (params.event_nseverity < 0 || params.event_nseverity > 7) {
                params.event_nseverity = 7;
            }

            var request = new HttpRequest(),
                factset = {
                    type: 'FactSet',
                    facts: []
                }

            adaptivecard = {
                '$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',
                type: 'AdaptiveCard',
                version: '1.4',
                "msteams": {
                    "width": "full"
                },
                body: [
                    {
                        type: 'Container',
                        style: SEVERITY_COLORS[params.event_nseverity],
                        backgroundImage: {
                            fillMode: 'RepeatHorizontally',
                            url: 'https://singlecolorimage.com/get/' + SEVERITY_HEX_COLORS[params.event_nseverity] + '/1x2'
                    },
                        items: [
                            {
                                type: 'TextBlock',
                                size: 'Medium',
                                weight: 'Bolder',
                                text: params.alert_subject
                            },
                            {
                                type: 'TextBlock',
                                text: "Status: **" + SEVERITY_NAMES[params.event_nseverity] + "**",
                                color: SEVERITY_COLORS[params.event_nseverity],
                                weight: 'Bolder',
                                spacing: 'Small'
                            }
                        ],
                        bleed: true
                    },
                    {
                        type: 'RichTextBlock',
                        inlines: [
                            (params.use_default_message.toLowerCase() == 'true')
                            ? params.alert_message
                            : params.trigger_description,
                        ]
                    }
                ],
                actions: [
                    {
                        type: 'Action.OpenUrl',
                        title: (params.event_source === '0') ? 'Event Info' : 'Zabbix Home',
                        url: (params.event_source === '0')
                            ? params.zabbix_url + '/tr_events.php?triggerid=' +
                              params.trigger_id + '&eventid=' + params.event_id
                            : params.zabbix_url
                    }
                ]
              }
                body = {
                    type: 'message',
                    attachments: [
                        {
                            contentType: 'application/vnd.microsoft.card.adaptive',
                            contentUrl: null,
                        }
                    ]
                };

            if (params.use_default_message.toLowerCase() !== 'true') {

                // Problem message.
                if (params.event_value === '1' && params.event_update_status === '0') {
                    factset.facts.push({
                        title: 'Event time',
                value: formatDateToFrench(params.event_date, params.event_time)
                        // value: params.event_time + ' ' + params.event_date
                    });
                    factset.facts.push({
                        title: 'Host',
                        value: params.host_name + ' [' + params.host_ip + ']'
                    });

                }
                // Update message.
                else if (params.event_update_status === '1') {
                    adaptivecard.body[1].text = params.event_update_user + ' ' + params.event_update_action + '.';

                    if (params.event_update_message) {
                        adaptivecard.body[1].text += '<br>Message:<br>' + params.event_update_message;
                    }

                    factset.facts.push({
                        title: 'Event update time',
                        value: formatDateToFrench(params.event_update_date, params.event_update_time)
                // value: params.event_update_time + ' ' + params.event_update_date
                    });
                    factset.facts.push({
                        title: 'Host',
                        value: params.host_name + ' [' + params.host_ip + ']'
                    });

                }
                // Resolved message.
                else {
                    factset.facts.push({
                        title: 'Recovery time',
                        value: formatDateToFrench(params.event_recovery_date, params.event_recovery_time)
                // value: params.event_recovery_time + ' ' + params.event_recovery_date
                    });
                    factset.facts.push({
                        title: 'Host',
                        value: params.host_name + ' [' + params.host_ip + ']'
                    });
                }

                if (params.event_opdata && params.event_opdata !== '{EVENT.OPDATA}') {
                    factset.facts.push({
                        title: 'Operational data',
                        value: formatOpData(params.event_opdata) // Apply transformation
                    });
                }

                if (params.event_tags && params.event_tags !== '{EVENT.TAGS}') {
                    factset.facts.push({
                        title: 'Event tags',
                        value: params.event_tags
                    });
                }

                Object.keys(params)
                    .forEach(function (key) {
                        if (key.startsWith('fact_') && params[key] !== '') {
                            factset.facts.push({
                                title: key.substring(5),
                                value: params[key]
                            });
                        }
                        else if (key.startsWith('openUri_') && params[key] !== '' && !params[key].startsWith('{')) {
                            adaptivecard.actions.push({
                                type: 'Action.OpenUrl',
                                title: key.substring(8),
                                url: params[key]
                            });
                        }
                    });
                adaptivecard.body.push(factset);
            }

            body.attachments[0].content = adaptivecard;

            request.addHeader('Content-Type: application/json');

            if (typeof params.HTTPProxy === 'string' && params.HTTPProxy !== '') {
                request.setProxy(params.HTTPProxy);
            }

            Zabbix.log(4, '[ MS Teams Webhook ] JSON: ' + JSON.stringify(body));

            var response = request.post(params.teams_endpoint, JSON.stringify(body));

            Zabbix.log(4, '[ MS Teams Webhook ] Response: ' + response);

            var responsecode = request.getStatus();
            if (responsecode  ===  202) {
                return 'OK';
            }
            else {
                Zabbix.log(4, '[ MS Teams Webhook ] FAILED with response code: ' + responsecode);
                throw 'Response code: ' + responsecode;
            }
        }
        catch (error) {
            Zabbix.log(3, '[ MS Teams Webhook ] ERROR: ' + error);
            throw 'Sending failed: ' + error;
        };
      description: |
        New implementation regarding retirement of Office 365 connectors within Microsoft Teams > https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/
         
        - Create a workflow in MS Teams. You can use the "Post a message in a channel when a webhook request is received" template for it.
        - Copy the endpoint URL and place it in the teams_endpoint parameter.
        - Set up the global macro {$ZABBIX.URL}, which will contain the URL to the Zabbix frontend.
        - Add the MS Teams Workflow media to the user of your choice.

      message_templates:
        - event_source: TRIGGERS
          operation_mode: PROBLEM
          subject: 'Problem: {EVENT.NAME}'
          message: |
            Problem started at {EVENT.TIME} on {EVENT.DATE}
            Problem name: {EVENT.NAME}
            Host: {HOST.NAME}
            Severity: {EVENT.SEVERITY}
            Operational data: {EVENT.OPDATA}
            Original problem ID: {EVENT.ID}
            {TRIGGER.URL}
        - event_source: TRIGGERS
          operation_mode: RECOVERY
          subject: 'Resolved in {EVENT.DURATION}: {EVENT.NAME}'
          message: |
            Problem has been resolved in {EVENT.DURATION} at {EVENT.RECOVERY.TIME} on {EVENT.RECOVERY.DATE}
            Problem name: {EVENT.NAME}
            Host: {HOST.NAME}
            Severity: {EVENT.SEVERITY}
            Original problem ID: {EVENT.ID}
            {TRIGGER.URL}
        - event_source: TRIGGERS
          operation_mode: UPDATE
          subject: 'Updated problem in {EVENT.AGE}: {EVENT.NAME}'
          message: |
            {USER.FULLNAME} {EVENT.UPDATE.ACTION} problem at {EVENT.UPDATE.DATE} {EVENT.UPDATE.TIME}.
            {EVENT.UPDATE.MESSAGE}
            
            Current problem status is {EVENT.STATUS}, age is {EVENT.AGE}, acknowledged: {EVENT.ACK.STATUS}.
        - event_source: DISCOVERY
          operation_mode: PROBLEM
          subject: 'Discovery: {DISCOVERY.DEVICE.STATUS} {DISCOVERY.DEVICE.IPADDRESS}'
          message: |
            Discovery rule: {DISCOVERY.RULE.NAME}
            
            Device IP: {DISCOVERY.DEVICE.IPADDRESS}
            Device DNS: {DISCOVERY.DEVICE.DNS}
            Device status: {DISCOVERY.DEVICE.STATUS}
            Device uptime: {DISCOVERY.DEVICE.UPTIME}
            
            Device service name: {DISCOVERY.SERVICE.NAME}
            Device service port: {DISCOVERY.SERVICE.PORT}
            Device service status: {DISCOVERY.SERVICE.STATUS}
            Device service uptime: {DISCOVERY.SERVICE.UPTIME}
        - event_source: AUTOREGISTRATION
          operation_mode: PROBLEM
          subject: 'Autoregistration: {HOST.HOST}'
          message: |
            Host name: {HOST.HOST}
            Host IP: {HOST.IP}
            Agent port: {HOST.PORT}
        - event_source: INTERNAL
          operation_mode: PROBLEM
          subject: '[{EVENT.STATUS}] {EVENT.NAME}'
          message: |
            Problem started at {EVENT.TIME} on {EVENT.DATE}
            Problem name: {EVENT.NAME}
            Host: {HOST.NAME}
            Original problem ID: {EVENT.ID}
        - event_source: INTERNAL
          operation_mode: RECOVERY
          subject: '[{EVENT.STATUS}] {EVENT.NAME}'
          message: |
            Problem has been resolved in {EVENT.DURATION} at {EVENT.RECOVERY.TIME} on {EVENT.RECOVERY.DATE}
            Problem name: {EVENT.NAME}
            Host: {HOST.NAME}
            Original problem ID: {EVENT.ID}
        - event_source: SERVICE
          operation_mode: PROBLEM
          subject: 'Service "{SERVICE.NAME}" problem: {EVENT.NAME}'
          message: |
            Service problem started at {EVENT.TIME} on {EVENT.DATE}
            Service problem name: {EVENT.NAME}
            Service: {SERVICE.NAME}
            Severity: {EVENT.SEVERITY}
            Original problem ID: {EVENT.ID}
            Service description: {SERVICE.DESCRIPTION}
            
            {SERVICE.ROOTCAUSE}
        - event_source: SERVICE
          operation_mode: RECOVERY
          subject: 'Service "{SERVICE.NAME}" resolved in {EVENT.DURATION}: {EVENT.NAME}'
          message: |
            Service "{SERVICE.NAME}" has been resolved at {EVENT.RECOVERY.TIME} on {EVENT.RECOVERY.DATE}
            Problem name: {EVENT.NAME}
            Problem duration: {EVENT.DURATION}
            Severity: {EVENT.SEVERITY}
            Original problem ID: {EVENT.ID}
            Service description: {SERVICE.DESCRIPTION}
        - event_source: SERVICE
          operation_mode: UPDATE
          subject: 'Changed "{SERVICE.NAME}" service status to {EVENT.UPDATE.SEVERITY} in {EVENT.AGE}'
          message: |
            Changed "{SERVICE.NAME}" service status to {EVENT.UPDATE.SEVERITY} at {EVENT.UPDATE.DATE} {EVENT.UPDATE.TIME}.
            Current problem age is {EVENT.AGE}.
            Service description: {SERVICE.DESCRIPTION}
            
            {SERVICE.ROOTCAUSE}