zabbix_export:
  version: '7.4'
  media_types:
    - name: Telegram
      type: WEBHOOK
      parameters:
        - name: alert_message
          value: '{ALERT.MESSAGE}'
        - name: alert_subject
          value: '{ALERT.SUBJECT}'
        - name: api_chat_id
          value: '{ALERT.SENDTO}'
        - name: api_parse_mode
          value: '<PLACE PARSE MODE>'
        - name: api_token
          value: '<PLACE YOUR TOKEN>'
        - name: event_nseverity
          value: '{EVENT.NSEVERITY}'
        - name: event_severity
          value: '{EVENT.SEVERITY}'
        - name: event_source
          value: '{EVENT.SOURCE}'
        - name: event_tags
          value: '{EVENT.TAGSJSON}'
        - name: event_update_nseverity
          value: '{EVENT.UPDATE.NSEVERITY}'
        - name: event_update_severity
          value: '{EVENT.UPDATE.SEVERITY}'
        - name: event_update_status
          value: '{EVENT.UPDATE.STATUS}'
        - name: event_value
          value: '{EVENT.VALUE}'
      status: DISABLED
      script: |
        const CLogger = function(serviceName) {
        	this.serviceName = serviceName;
        	this.INFO = 4
        	this.WARN = 3
        	this.ERROR = 2
        	this.log = function(level, msg) {
        		Zabbix.log(level, '[' + this.serviceName + '] ' + msg);
        	}
        }
        
        const CWebhook = function(value) {
        	try {
        		params = JSON.parse(value);
        
        		if (['0', '1', '2', '3', '4'].indexOf(params.event_source) === -1) {
        			throw 'Incorrect "event_source" parameter given: ' + params.event_source + '.\nMust be 0-4.';
        		}
        
        		if (['0', '3', '4'].indexOf(params.event_source) !== -1 && ['0', '1'].indexOf(params.event_value) === -1) {
        			throw 'Incorrect "event_value" parameter given: ' + params.event_value + '.\nMust be 0 or 1.';
        		}
        
        		if (['0', '3', '4'].indexOf(params.event_source) !== -1) {
        			if (params.event_source === '1' && ['0', '1', '2', '3'].indexOf(params.event_value) === -1) {
        				throw 'Incorrect "event_value" parameter given: ' + params.event_value + '.\nMust be 0-3.';
        			}
        
        			if (params.event_source === '0' && ['0', '1'].indexOf(params.event_update_status) === -1) {
        				throw 'Incorrect "event_update_status" parameter given: ' + params.event_update_status + '.\nMust be 0 or 1.';
        			}
        
        			if (params.event_source === '4') {
        				if (['0', '1', '2', '3', '4', '5'].indexOf(params.event_update_nseverity) !== -1 && params.event_update_nseverity != params.event_nseverity) {
        					params.event_nseverity = params.event_update_nseverity;
        					params.event_severity = params.event_update_severity;
        					params.event_update_status = '1';
        				}
        			}
        		}
        
        		this.runCallback = function(name, params) {
        			if (typeof this[name] === 'function') {
        				return this[name].apply(this, [params]);
        			}
        		}
        
        		this.handleEvent = function(source, event) {
        			const alert = { source: source, event: event };
        			return [
        				this.runCallback('on' + source + event, alert),
        				this.runCallback('on' + event, alert),
        				this.runCallback('onEvent', alert)
        			];
        		}
        
        		this.handleEventless = function(source) {
        			const alert = { source: source, event: null };
        			return [
        				this.runCallback('on' + source, alert),
        				this.runCallback('onEvent', alert)
        			];
        		}
        
        		this.run = function() {
        			var results = [];
        			if (typeof this.httpProxy === 'string' && this.httpProxy.trim() !== '') {
        				this.request.setProxy(this.httpProxy);
        			}
        			const types = { '0': 'Trigger', '1': 'Discovery', '2': 'Autoreg', '3': 'Internal', '4': 'Service' };
        
        			if (['0', '3', '4'].indexOf(this.params.event_source) !== -1) {
        				var event = (this.params.event_update_status === '1')
        					? 'Update'
        					: ((this.params.event_value === '1') ? 'Problem' : 'Resolve');
        
        				results = this.handleEvent(types[this.params.event_source], event);
        			}
        			else if (typeof types[this.params.event_source] !== 'undefined') {
        				results = this.handleEventless(types[this.params.event_source]);
        			}
        			else {
        				throw 'Unexpected "event_source": ' + this.params.event_source;
        			}
        
        			for (idx in results) {
        				if (typeof results[idx] !== 'undefined') {
        					return JSON.stringify(results[idx]);
        				}
        			}
        		}
        		this.httpProxy = params.http_proxy;
        		this.params = params;
        		this.runCallback('onCheckParams', {});
        	} catch (error) {
        		throw 'Webhook processing failed: ' + error;
        	}
        }
        
        const CParamValidator = {
        
        	isType: function(value, type) {
        		if (type === 'array') {
        			return Array.isArray(value);
        		}
        		if (type === 'integer') {
        			return CParamValidator.isInteger(value);
        		}
        		if (type === 'float') {
        			return CParamValidator.isFloat(value);
        		}
        
        		return (typeof value === type);
        	},
        
        	isInteger: function(value) {
        		if (!CParamValidator.ifMatch(value, /^-?\d+$/)) {
        			return false;
        		}
        
        		return !isNaN(parseInt(value));
        	},
        
        	isFloat: function(value) {
        		if (!CParamValidator.ifMatch(value, /^-?\d+\.\d+$/)) {
        			return false;
        		}
        
        		return !isNaN(parseFloat(value));
        	},
        
        	isDefined: function(value) {
        		return !CParamValidator.isType(value, 'undefined');
        	},
        
        	isEmpty: function(value) {
        		if (!CParamValidator.isType(value, 'string')) {
        			throw 'Value "' + value + '" must be a string to be checked for emptiness.';
        		}
        
        		return (value.trim() === '');
        	},
        
        	isMacroSet: function(value, macro) {
        		if (CParamValidator.isDefined(macro)) {
        			return !(CParamValidator.ifMatch(value, '^\{' + macro + '\}$'))
        		}
        
        		return !(CParamValidator.ifMatch(value, '^\{[$#]{0,1}[A-Z_\.]+[\:]{0,1}["]{0,1}.*["]{0,1}\}$') || value === '*UNKNOWN*')
        	},
        
        	withinRange: function(value, min, max) {
        		if (!CParamValidator.isType(value, 'number')) {
        			throw 'Value "' + value + '" must be a number to be checked for range.';
        		}
        		if (value < ((CParamValidator.isDefined(min)) ? min : value)
        			|| value > ((CParamValidator.isDefined(max)) ? max : value)) {
        			return false;
        		}
        
        		return true;
        	},
        
        	inArray: function(value, array) {
        		if (!CParamValidator.isType(array, 'array')) {
        			throw 'The array must be an array to check the value for existing in it.';
        		}
        
        		return (array.indexOf((typeof value === 'string') ? value.toLowerCase() : value) !== -1);
        	},
        
        	ifMatch: function(value, regex) {
        		return (new RegExp(regex)).test(value);
        	},
        
        	match: function(value, regex) {
        		if (!CParamValidator.isType(value, 'string')) {
        			throw 'Value "' + value + '" must be a string to be matched with the regular expression.';
        		}
        
        		return value.match(new RegExp(regex));
        	},
        
        	checkURL: function(value) {
        		if (CParamValidator.isEmpty(value)) {
        			throw 'URL value "' + value + '" must be a non-empty string.';
        		}
        		if (!CParamValidator.ifMatch(value, '^(http|https):\/\/.+')) {
        			throw 'URL value "' + value + '" must contain a schema.';
        		}
        
        		return value.endsWith('/') ? value.slice(0, -1) : value;
        	},
        
        	check: function(key, rule, params) {
        		if (!CParamValidator.isDefined(rule.type)) {
        			throw 'Mandatory attribute "type" has not been defined for parameter "' + key + '".';
        		}
        		if (!CParamValidator.isDefined(params[key])) {
        			throw 'Checked parameter "' + key + '" was not found in the list of input parameters.';
        		}
        		var value = params[key],
        			error_message = null;
        		switch (rule.type) {
        			case 'string':
        				if (!CParamValidator.isType(value, 'string')) {
        					throw 'Value "' + key + '" must be a string.';
        				}
        				if (CParamValidator.isEmpty(value)) {
        					error_message = 'Value "' + key + '" must be a non-empty string';
        					break;
        				}
        				if (CParamValidator.isDefined(rule.len) && value.length < rule.len) {
        					error_message = 'Value "' + key + '" must be a string with a length > ' + rule.len;
        				}
        				if (CParamValidator.isDefined(rule.regex) && !CParamValidator.ifMatch(value, rule.regex)) {
        					error_message = 'Value "' + key + '" must match the regular expression "' + rule.regex + '"';
        				}
        				if (CParamValidator.isDefined(rule.url) && rule.url === true) {
        					value = CParamValidator.checkURL(value);
        				}
        				break;
        			case 'integer':
        				if (!CParamValidator.isInteger(value)) {
        					error_message = 'Value "' + key + '" must be an integer';
        					break;
        				}
        				value = parseInt(value);
        				break;
        			case 'float':
        				if (!CParamValidator.isFloat(value)) {
        					error_message = 'Value "' + key + '" must be a floating-point number';
        					break;
        				}
        				value = parseFloat(value);
        				break;
        			case 'boolean':
        				if (CParamValidator.inArray(value, ['1', 'true', 'yes', 'on'])) {
        					value = true;
        				}
        				else if (CParamValidator.inArray(value, ['0', 'false', 'no', 'off'])) {
        					value = false;
        				}
        				else {
        					error_message = 'Value "' + key + '" must be a boolean-like.';
        				}
        				break;
        			case 'array':
        				try {
        					value = JSON.parse(value);
        				} catch (error) {
        					throw 'Value "' + key + '" contains invalid JSON.';
        				}
        				if (!CParamValidator.isType(value, 'array')) {
        					error_message = 'Value "' + key + '" must be an array.';
        				}
        				if (CParamValidator.isDefined(rule.tags) && rule.tags === true) {
        					value = value.reduce(function(acc, obj) {
        						acc[obj.tag] = obj.value || null;
        						return acc;
        					}, {});
        				}
        				break;
        			case 'object':
        				value = JSON.parse(value);
        				if (!CParamValidator.isType(value, 'object')) {
        					error_message = 'Value "' + key + '" must be an object.';
        				}
        				break;
        			default:
        				throw 'Unexpected attribute type "' + rule.type + '" for value "' + key + '". Available: ' +
        				['integer', 'float', 'string', 'boolean', 'array', 'object'].join(', ');
        		}
        		params[key] = value;
        		if (CParamValidator.inArray(rule.type, ['integer', 'float']) && error_message === null && (CParamValidator.isDefined(rule.min)
        			|| CParamValidator.isDefined(rule.max)) && !CParamValidator.withinRange(value, rule.min, rule.max)) {
        			error_message = 'Value "' + key + '" must be a number ' + ((CParamValidator.isDefined(rule.min) && CParamValidator.isDefined(rule.max))
        				? (rule.min + '..' + rule.max) : ((CParamValidator.isDefined(rule.min)) ? '>' + rule.min : '<' + rule.max));
        		}
        		else if (CParamValidator.isDefined(rule.array) && !CParamValidator.inArray(value, rule.array)) {
        			error_message = 'Value "' + key + '" must be in the array ' + JSON.stringify(rule.array);
        		}
        		else if (CParamValidator.isDefined(rule.macro) && !CParamValidator.isMacroSet(value.toString(), rule.macro)) {
        			error_message = 'The macro ' + ((CParamValidator.isDefined(rule.macro)) ? '{' + rule.macro + '} ' : ' ') + 'is not set';
        		}
        		if (error_message !== null) {
        			if (CParamValidator.isDefined(rule.default) && CParamValidator.isType(rule.default, rule.type)) {
        				params[key] = rule.default;
        			}
        			else {
        				Zabbix.log(4, 'Default value for "' + key + '" must be a ' + rule.type + '. Skipped.');
        				throw 'Incorrect value for variable "' + key + '". ' + error_message;
        			}
        		}
        
        		return this;
        	},
        
        	validate: function(rules, params) {
        		if (!CParamValidator.isType(params, 'object') || CParamValidator.isType(params, 'array')) {
        			throw 'Incorrect parameters value. The value must be an object.';
        		}
        		for (var key in rules) {
        			CParamValidator.check(key, rules[key], params);
        		}
        	}
        }
        
        const CHttpRequest = function(logger) {
        	this.request = new HttpRequest();
        	if (typeof logger !== 'object' || logger === null) {
        		this.logger = Zabbix;
        	}
        	else {
        		this.logger = logger;
        	}
        
        	this.clearHeader = function() {
        		this.request.clearHeader();
        	}
        
        	this.addHeaders = function(value) {
        		var headers = [];
        
        		if (typeof value === 'object' && value !== null) {
        			if (!Array.isArray(value)) {
        				Object.keys(value).forEach(function(key) {
        					headers.push(key + ': ' + value[key]);
        				});
        			}
        			else {
        				headers = value;
        			}
        		}
        		else if (typeof value === 'string') {
        			value.split('\r\n').forEach(function(header) {
        				headers.push(header);
        			});
        		}
        
        		for (var idx in headers) {
        			this.request.addHeader(headers[idx]);
        		}
        	}
        
        	this.setProxy = function(proxy) {
        		this.request.setProxy(proxy);
        	}
        
        	this.plainRequest = function(method, url, data) {
        		var resp = null;
        		method = method.toLowerCase();
        		this.logger.log(4, 'Sending ' + method + ' request:' + JSON.stringify(data));
        		if (['get', 'post', 'put', 'patch', 'delete', 'trace'].indexOf(method) !== -1) {
        			resp = this.request[method](url, data);
        		}
        		else if (['connect', 'head', 'options'].indexOf(method) !== -1) {
        			resp = this.request[method](url);
        		}
        		else {
        			throw 'Unexpected method. Method ' + method + ' is not supported.';
        		}
        		this.logger.log(4, 'Response has been received: ' + resp);
        
        		return resp;
        	}
        
        	this.jsonRequest = function(method, url, data) {
        		this.addHeaders('Content-Type: application/json');
        		var resp = this.plainRequest(method, url, JSON.stringify(data));
        		try {
        			resp = JSON.parse(resp);
        		}
        		catch (error) {
        			throw 'Failed to parse response: not well-formed JSON was received';
        		}
        
        		return resp;
        	}
        
        	this.getStatus = function() {
        		return this.request.getStatus();
        	}
        }
        
        var serviceLogName = 'Telegram Webhook',
        	Logger = new CLogger(serviceLogName),
        	Telegram = CWebhook;
        
        function escapeMarkup(str, mode) {
        	switch (mode) {
        		case 'markdown':
        			return str.replace(/([_*\[`])/g, '\\$&');
        		case 'markdownv2':
        			return str.replace(/([_*\[\]()~`>#+\-=|{}.!])/g, '\\$&');
        		case 'html':
        			return str.replace(/<(\s|[^a-z\/])/g, '&lt;$1');
        		default:
        			return str;
        	}
        }
        
        Telegram.prototype.getMessageID = function (chat_id, message_thread_id) {
        	const tag_key = '__telegram_msg_id_' + chat_id + (message_thread_id ? '_' + message_thread_id : '');
        	if (CParamValidator.isDefined(this.params.event_tags[tag_key])) {
        		return this.params.event_tags[tag_key];
        	}
        	return null;
        }
        
        Telegram.prototype.onCheckParams = function () {
        	CParamValidator.validate(
        		{
        			api_token: {type: 'string'},
        			api_chat_id: {type: 'string'},
        			alert_message: {type: 'string'},
        			event_tags: {type: 'array', tags: true, default: {}}
        		},
        		this.params
        	);
        	this.params.url = 'https://api.telegram.org/bot';
        	this.data = {
        		disable_web_page_preview: true,
        		disable_notification: false
        	};
        	const match = this.params.api_chat_id.match(/^(-?\d+|@[a-zA-Z0-9_]+)(?::(\d+))?$/);
        	if (!match) {
        		throw 'Invalid format for api_chat_id: "' + this.params.api_chat_id + '". Must be a numeric group ID or @GroupName, optionally followed by :message_thread_id.';
        	}
        	this.data['chat_id'] = match[1];
        	if (CParamValidator.isDefined(match[2])) {
        		this.data['message_thread_id'] = match[2];
        	}
        	this.data['text'] = ((this.params.alert_subject !== '') ? this.params.alert_subject + '\n' : '') + this.params.alert_message;
        	if (['markdown', 'html', 'markdownv2'].indexOf(this.params.api_parse_mode.toLowerCase()) !== -1) {
        		this.data['parse_mode'] = this.params.api_parse_mode.toLowerCase();
        		this.data['text'] = escapeMarkup(this.data['text'], this.data['parse_mode']);
        	}
        	const reply_to_message_id = this.getMessageID(this.data['chat_id'], this.data['message_thread_id']);
        	if (reply_to_message_id !== null) {
        		this.data['reply_to_message_id'] = reply_to_message_id;
        	}
        	this.result = {tags: {}};
        };
        
        Telegram.prototype.onEvent = function (alert) {
        	Logger.log(Logger.INFO, 'Source: ' + alert.source + '; Event: ' + alert.event);
        	Logger.log(Logger.INFO, 'URL: ' + this.params.url.replace(this.params.api_token, '<TOKEN>'));
        	var response = this.request.jsonRequest('POST', this.params.url + this.params.api_token + '/sendMessage', this.data);
        
        	if (this.request.getStatus() !== 200 || !CParamValidator.isType(response.ok, 'boolean') || response.ok !== true) {
        		Logger.log(Logger.INFO, 'HTTP code: ' + this.request.getStatus());
        		if (CParamValidator.isType(response.description, 'string')) {
        			throw response.description;
        		}
        		else {
        			throw 'Unknown error. Check debug log for more information.';
        		}
        	}
        
        	if (CParamValidator.isDefined(response.result.message_id) && this.getMessageID(this.data['chat_id'], this.data['message_thread_id']) === null) {
        		this.result.tags['__telegram_msg_id_' + this.data['chat_id'] + (this.data['message_thread_id'] ? '_' + this.data['message_thread_id'] : '')] = response.result.message_id;
        	}
        
        	return this.result;
        };
        
        try {
        	var hook = new Telegram(value);
        	hook.request = new CHttpRequest(Logger);
        	return hook.run();
        }
        catch (error) {
        	Logger.log(Logger.WARN, 'notification failed: ' + error);
        	throw 'Sending failed: ' + error;
        }
      process_tags: 'YES'
      description: |
        This media type integrates your Zabbix installation with Telegram using the Zabbix webhook feature.
        
        Telegram configuration:
        
        1. Register a new Telegram bot: send "/newbot" to "@BotFather" and follow the instructions. The token provided by "@BotFather" in the final step will be needed for configuring the Zabbix webhook.
        
        2. Set up personal or group notifications:
        
        2.1 Personal notifications:
        
        2.1.1 Retrieve the chat ID of the user the bot should send messages to. The user should send "/getid" to "@myidbot" in the Telegram messenger.
        
        2.1.2 The user should also send "/start" to the bot created in step 1. If you skip this step, the Telegram bot won't be able to send messages to the user (bots cannot initiate conversations with users).
        
        2.2 Group notifications:
        
        2.2.1 Retrieve the group ID of the group that the bot should send messages to. Add "@myidbot" and the bot created in step 1 to your group.
        
        2.2.2 In the group chat, send: "/getgroupid@myidbot".
        
        2.2.3 If the bot is added to a supergroup and you want the bot to send messages to a specific topic instead of the default "General" channel, right-click any message in that topic and click "Copy Message Link". The copied link will have the following format: "https://t.me/c/<short_group_id>/<topic_id>/<message_id>", for example: "https://t.me/c/1234567890/2/1". In this example, the topic ID is "2".
        
        Note:
        - The group ID is a negative number, for example: "-1234567890".
        - The supergroup ID is a negative number prefixed with "-100", for example: "-1001234567890"
        - The public group or supergroup ID can also be specified in media type properties as a name prefixed by "@", for example: "@MyGroupName".
        
        3. Depending on where you want to send notifications, copy and save the bot token, personal chat ID or group ID, and topic ID (if you want to send messages to a specific supergroup topic), as you will need these later to set up the media type in Zabbix.
        
        Zabbix configuration:
        
        1. Set the following webhook parameters:
        - "api_parse_mode" - the formatting mode applied for messages (possible values: "markdown", "html", "markdownv2")
        - "api_token" - the token of the bot used to send messages
        
        Learn more about message formatting options in Telegram Bot API documentation:
        - Markdown: https://core.telegram.org/bots/api#markdown-style
        - HTML: https://core.telegram.org/bots/api#html-style
        - MarkdownV2: https://core.telegram.org/bots/api#markdownv2-style
        
        Note: Your Telegram-related actions should be separated from other notification types (e.g., SMS); otherwise, if you use Markdown or HTML in the alert subject or body, you may receive plain-text alerts with raw tags.
        
        2. Click the "Enabled" checkbox to enable the media type and click the "Update" button to save the webhook settings.
        
        3. Create a Zabbix user and add media:
        - To create a new user, go to the "Users" → "Users" section, click the "Create user" button in the top right corner. In the "User" tab, fill in all required fields (marked with red asterisks).
        - Make sure this user has access to all hosts for which you would like problem notifications to be sent to Zammad.
        - In the "Media" tab, click "Add" and select the type "Telegram" from the drop-down list.
        - In the "Send to" field, specify the Telegram user chat ID or group ID that you retrieved during Telegram setup. To send notifications to a specific topic within a supergroup, specify the topic ID after the semicolon delimiter in the format "<group_id>:<topic_id>", for example: "-1001234567890:2", "@MyGroupName:2".
        
        4. Done! You can now start using this media type in actions and create tickets.
        
        You can find the latest version of this media and additional information in the official Zabbix repository:
        https://git.zabbix.com/projects/ZBX/repos/zabbix/browse/templates/media/telegram
      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}