START TRANSACTION;
INSERT INTO role (roleid,name,type,readonly) VALUES ('1','User role','1','0'),
('2','Admin role','2','0'),
('3','Super admin role','3','1'),
('4','Guest role','1','0');
INSERT INTO ugset (ugsetid,hash) VALUES ('1','b21d61561974b06cbeb97a71ca880e92f0fc06d49ac3246280afb88dfa6050a6');
INSERT INTO users (userid,username,name,surname,passwd,url,autologin,autologout,refresh,rows_per_page,roleid) VALUES ('1','Admin','Zabbix','Administrator','$2y$10$92nDno4n0Zm7Ej7Jfsz8WukBfgSS/U0QkIuu8WkJPihXBb2A1UrEK','','1','0','30s','50','3'),
('2','guest','','','$2y$10$89otZrRNmde97rIyzclecuk6LwKAsHN0BcvoOKGjbT.BwMBfm7G06','','0','15m','30s','50','4');
INSERT INTO hstgrp (groupid,name,flags,uuid,type) VALUES ('1','Templates','0','7df96b18c230490a9a0a9e2307226338','1'),
('2','Linux servers','0','dc579cd7a1a34222933f24f52a68bcd8','0'),
('4','Zabbix servers','0','6f6799aa69e844b4b3918f779f2abf08','0'),
('5','Discovered hosts','0','f2481361f99448eea617b7b1d4765566','0'),
('6','Virtual machines','0','137f19e6e2dc4219b33553b812627bc2','0'),
('7','Hypervisors','0','1b837a3c078647049a0c00c61b4d57b5','0'),
('9','Templates/Network devices','0','36bff6c29af64692839d077febfc7079','1'),
('10','Templates/Operating systems','0','846977d1dfed4968bc5f8bdb363285bc','1'),
('11','Templates/Server hardware','0','e960332b3f6c46a1956486d4f3f99fce','1'),
('12','Templates/Applications','0','a571c0d144b14fd4a87a9d9b2aa9fcd6','1'),
('13','Templates/Databases','0','748ad4d098d447d492bb935c907f652f','1'),
('14','Templates/Virtualization','0','02e4df4f20b848e79267641790f241da','1'),
('15','Templates/Telephony','0','1d12408342854fd5a4436dd6d5d1bd4a','1'),
('16','Templates/SAN','0','7c2cb727f85b492d88cd56e17127c64d','1'),
('17','Templates/Video surveillance','0','d37f71c7e3f7469bab645852a69a2018','1'),
('18','Templates/Power','0','3dcd5bbe90534f9e8eb5c2d53756af63','1'),
('19','Applications','0','a571c0d144b14fd4a87a9d9b2aa9fcd6','0'),
('20','Databases','0','748ad4d098d447d492bb935c907f652f','0'),
('21','Templates/Cloud','0','c2c162144c2d4c5491c8801193af4945','1');
INSERT INTO drules (druleid,proxyid,name,iprange,delay,status,concurrency_max) VALUES ('2',NULL,'Local network','192.168.0.1-254','1h','1','0');
INSERT INTO dchecks (dcheckid,druleid,type,key_,snmp_community,ports,snmpv3_securityname,snmpv3_securitylevel,snmpv3_authpassphrase,snmpv3_privpassphrase,uniq,snmpv3_authprotocol,snmpv3_privprotocol,snmpv3_contextname,host_source,name_source,allow_redirect) VALUES ('2','2','9','system.uname','','10050','','0','','','0','0','0','','1','0','0');
INSERT INTO media_type (mediatypeid,type,name,smtp_server,smtp_helo,smtp_email,exec_path,gsm_modem,username,passwd,smtp_port,smtp_security,smtp_verify_peer,smtp_verify_host,smtp_authentication,maxsessions,maxattempts,attempt_interval,message_format,script,timeout,process_tags,show_event_menu,event_menu_url,event_menu_name,description,provider) VALUES ('1','0','Email','mail.example.com','example.com','zabbix@example.com','','','','','25','0','0','0','0','1','3','10s','0','','30s','0','0','','','','0'),
('3','2','SMS','','','','','/dev/ttyS0','','','25','0','0','0','0','1','3','10s','1','','30s','0','0','','','','0'),
('4','0','Email (HTML)','mail.example.com','example.com','zabbix@example.com','','','','','25','0','0','0','0','1','3','10s','1','','30s','0','0','','','','0'),
('34','0','Gmail','smtp.gmail.com','example.com','zabbix@example.com','','','','','587','1','0','0','1','1','3','10s','1','','30s','0','0','','','','1'),
('35','0','Gmail relay','smtp-relay.gmail.com','example.com','zabbix@example.com','','','','','587','1','0','0','0','1','3','10s','1','','30s','0','0','','','','2'),
('36','0','Office365','smtp.office365.com','example.com','zabbix@example.com','','','','','587','1','0','0','1','1','3','10s','1','','30s','0','0','','','','3'),
('37','0','Office365 relay','example-com.mail.protection.outlook.com','example.com','zabbix@example.com','','','','','25','1','0','0','0','1','3','10s','1','','30s','0','0','','','','4'),
('70','4','Brevis.one','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var BrevisOne = {
    params: [],

    addParam: function (name, value) {
        BrevisOne.params.push(name + ''='' + encodeURIComponent(value));
    },

    setProxy: function (HTTPProxy) {
        BrevisOne.HTTPProxy = HTTPProxy;
    },

    setPayload: function (params) {
        var parts = params.send_to.split('':''),
            defaultValues = {
                ring: ''true'',
                flash: ''true'',
                telauto: ''false''
            };

        BrevisOne.addParam(''username'', params.username);
        BrevisOne.addParam(''password'', params.password);
        BrevisOne.addParam(''text'', params.text);

        if (parts.length > 1) {
            BrevisOne.addParam(''mode'', parts[0]);
            BrevisOne.addParam(''to'', parts[1]);
        }
        else {
            BrevisOne.addParam(''to'', parts[0]);
        }

        Object.keys(defaultValues)
            .forEach(function (key) {
                if (params[key] && params[key].trim() && params[key].toLowerCase() === defaultValues[key]) {
                    BrevisOne.addParam(key, defaultValues[key]);
                }
            });
    },

    request: function () {
        var response,
            request = new HttpRequest(),
            data = ''?'' + BrevisOne.params.join(''&'');

        request.addHeader(''Content-Type: multipart/form-data'');

        if (typeof BrevisOne.HTTPProxy !== ''undefined'' && BrevisOne.HTTPProxy !== '''') {
            request.setProxy(BrevisOne.HTTPProxy);
        }

        Zabbix.log(4, ''[ BrevisOne Webhook ] Sending request.'');

        response = request.post(params.endpoint + data);

        Zabbix.log(4, ''[ BrevisOne Webhook ] Received response with status code '' +
            request.getStatus() + ''\\n'' + response);

        if (request.getStatus() < 200 || request.getStatus() >= 300) {
            var message = ''Request failed with status code '' + request.getStatus();

            if (response) {
                message += '': '' + response;
            }

            throw message + ''. Check debug log for more information.'';
        }
    }
};

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

    [''endpoint'', ''password'', ''username'', ''text'', ''send_to''].forEach(function (field) {
        if (typeof params !== ''object'' || typeof params[field] === ''undefined''
                || !params[field].trim()) {
            throw ''Required parameter is not set: "'' + field + ''".'';
        }
    });

    if (params.send_to === ''{ALERT.SENDTO}'') {
        throw ''Required parameter is not set: "send_to".'';
    }

    BrevisOne.setProxy(params.HTTPProxy);
    BrevisOne.setPayload(params);
    BrevisOne.request();

    return ''OK'';
}
catch (error) {
    Zabbix.log(3, ''[ BrevisOne Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','0','0','','','','0'),
('71','4','Discord','','','','','','','','25','0','0','0','0','1','3','10s','1',E'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();
	}
}

const CWebhookHelper = {

	createProblemURL: function(event_source, zabbix_url, trigger_id, event_id) {
		if (event_source === ''0'') {
			return zabbix_url + ''/tr_events.php?triggerid='' + trigger_id + ''&eventid='' + event_id;
		} else if (event_source === ''4'') {
			return zabbix_url + ''/zabbix.php?action=service.list'';
		}

		return zabbix_url;
	},

};

var SEVERITY_COLORS = [
	''#97AAB3'',
	''#7499FF'',
	''#FFC859'',
	''#FFA059'',
	''#E97659'',
	''#E45959'',
	''#009900''
],
	serviceLogName = ''Discord Webhook'',
	Logger = new CLogger(serviceLogName),
	Discord = CWebhook;

function stringTruncate(str, len) {
	return str.length > len ? str.substring(0, len - 3) + ''...'' : str;
}

Discord.prototype.onCheckParams = function () {
	CParamValidator.validate({discord_endpoint: {type: ''string'', url: true}, zabbix_url: {type: ''string'', url: true}, alert_message: {type: ''string''},
		alert_subject: {type: ''string''}, event_nseverity: {type: ''integer'', default: 1}, user_agent: {type: ''string''}}, this.params);

	this.params.discord_endpoint = this.params.discord_endpoint.replace(''/api/'', ''/api/v10/'') + ''?wait=True'';

	if (this.params.event_source === ''3'' && this.params.event_value === ''1'') {
		this.params.event_nseverity = 4;
	}

	if (this.params.event_value === ''0'') {
		this.params.event_nseverity = 6;
	}

	if (this.params.event_source === ''0'') {
		CParamValidator.validate({event_id: {type: ''integer''}, trigger_id: {type: ''integer''}}, this.params);
	}

	this.data = {
		embeds: [
			{
				color: parseInt(SEVERITY_COLORS[this.params.event_nseverity].replace(''#'', ''''), 16),
				url: CWebhookHelper.createProblemURL(this.params.event_source, this.params.zabbix_url, this.params.trigger_id, this.params.event_id),
				title: stringTruncate(this.params.alert_subject, 256),
				description: stringTruncate(this.params.alert_message, 4096)
			}
		]
	};
};

Discord.prototype.onEvent = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);

	this.request.addHeaders({"User-Agent": this.params.user_agent});
	const response = this.request.jsonRequest(''POST'', this.params.discord_endpoint, this.data);

	if (!response.id) {
		const error_message = CParamValidator.isType(response.message, ''string'') ? response.message : ''Unknown error'';

		throw error_message + ''. For more details check the Zabbix server log.'';
	}

	return ''OK'';
};


try {
	var hook = new Discord(value);
	hook.request = new CHttpRequest(Logger);
	return hook.run();
}
catch (error) {
	Logger.log(Logger.WARN, ''notification failed: '' + error);
	throw ''Sending failed: '' + error;
}','30s','0','0','','','This media type integrates your Zabbix installation with your Discord server using the Zabbix webhook feature.

Discord configuration:

1. Go to `https://discord.com/app` or open the Discord desktop application. Open your *Server Settings* and head to the *Integrations* tab.  
2. Press the *Create Webhook* button to create a new webhook.
3. Click on the webhook that has been created and edit the details if needed.
4. After setting up your Discord webhook press "Save Changes". Now you can copy Discord webhook URL now by pressing "Copy Webhook URL" or you can view it later.

Zabbix configuration:

1. Before you can start using Discord webhook, set up the global macro "{$ZABBIX.URL}":
- In the Zabbix web interface, go to "Administration" → "Macros" section in the dropdown menu in the top left corner.
- Set up the global macro "{$ZABBIX.URL}" which will contain the URL to the Zabbix frontend. The URL should be either an IP address, a fully qualified domain name, or localhost.
- Specifying a protocol is mandatory, whereas the port is optional. Depending on the web server configuration you might also need to append "/zabbix" to the end of URL. Good examples: 
  - http://zabbix.com
  - https://zabbix.lan/zabbix
  - http://server.zabbix.lan/
  - http://localhost
  - http://127.0.0.1:8080
- Bad examples:
  - zabbix.com
  - http://zabbix/

2. Create a Zabbix user and add media:
- If you want 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). 
- In the "Media" tab, add a new media and select "Discord" type from the drop-down list. The "Send to" field must contain the URL of the Discord webhook created before.
- Make sure this user has access to all hosts for which you would like problem notifications to be sent to Discord.

3. Great! You can now start using this media type in actions and receive alerts!

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/discord','0'),
('72','4','Event-Driven Ansible','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var Eda = {
    params: {},

    setParams: function (params) {
        var required_params = [
                ''send_to'',
                ''event_source'',
                ''event_value'',
                ''event_date'',
                ''event_time''],
            integer_fields = [
                ''event_id'',
                ''event_nseverity'',
                ''event_object'',
                ''event_source'',
                ''event_value'',
                ''host_id'',
                ''trigger_id''];

        required_params.forEach(function (field) {
            if (typeof params !== ''object'' || typeof params[field] === ''undefined'' || params[field] === '''') {
                throw ''Required param is not set: "'' + field + ''".'';
            }
        });

        Eda.params = params;

        integer_fields.forEach(function (key) {
                if (typeof Eda.params[key] !== ''undefined'') {
                    if (isNaN(Eda.params[key])) {
                        throw ''Parameter "'' + key + ''" must be integer. Given value: '' + Eda.params[key];
                    }
                    Eda.params[key] = parseInt(Eda.params[key]);
                }
            });

        // Check type of event. Possible values: 0 - Trigger
        if (params.event_source != 0) {
            throw (''Incorrect "event_source" parameter given: '' + params.event_source 
                + ''\\nOnly trigger-based events are supported'');
        }

        // Check trigger update and trigger resolve events
        if (params.event_source == 0 && params.event_value != 1) {
            throw (''Incorrect "event_value" parameter given: "'' + params.event_value 
                + ''".\\nOnly trigger-based events of problem are supported'');
        }
        
        // Check endpoint
        Eda.params.endpoint = (Eda.params.endpoint.startsWith(''/''))
                ? Eda.params.endpoint : ''/'' + Eda.params.endpoint;
        // Prepare groups
        Eda.params.host_groups = (typeof Eda.params[''host_groups''] !== ''undefined'')
                ? this.prepareHostgroups(Eda.params.host_groups): []
        // Prepare tags
        Eda.params.event_tags = (typeof Eda.params[''event_tags''] !== ''undefined'')
                ? this.transformTags(Eda.params.event_tags): {}
        // Prepare timestamp
        Eda.params.event_datetime_timestamp = this.getTimestamp(
            Eda.params.event_date, Eda.params.event_time)
    },

    setProxy: function (HTTPProxy) {
        Eda.HTTPProxy = HTTPProxy;
    },

    prepareHostgroups: function (hostgroups) {
        var host_groups = []; 
        hostgroups.split('','').forEach(function (group) {
            group = group.trim();
            if (group.length > 0) {
                host_groups.push(group);
            }
        });
        return host_groups;
    },

    getTimestamp: function (date, time) {
        try {
            return Date.parse(date.split(''.'').join(''-'') + ''T'' + time + ''.000Z'') / 1000 + new Date().getTimezoneOffset() * 60;
        } catch (_) {}
    
        throw (''Failed to parse event time.'');
    },

    transformTags: function (event_tags) {
        var tags = {};
        if (event_tags) {
            try {
                JSON.parse(event_tags).forEach(function (object) {
                    var tag = object[''tag''];
                    tags[tag] = tags[tag] || [];
                    tags[tag].push(object[''value'']);
                });
            } catch (error) {
                throw ''Event tags format is invalid.'';
            }
        }
    
        return tags;
    },

    sendMessage: function () {
        var response, request = new HttpRequest();
        var url = Eda.params[''send_to''] + Eda.params[''endpoint''];
        var data = JSON.stringify(Eda.params);
        Zabbix.log(4, ''[ Event-Driven Ansible webhook ] URL: '' + url + '' data: '' + data);
        
        if (typeof Eda.HTTPProxy !== ''undefined'' && Eda.HTTPProxy !== '''') {
            request.setProxy(Eda.HTTPProxy);
        }
        request.addHeader(''Content-Type: application/json'');
        response = request.post(url, data);
        
        Zabbix.log(4, ''[ Event-Driven Ansible webhook ] HTTP code: '' + request.getStatus()
            + '' response: '' + response);

        if (request.getStatus() !== 200) {
            throw ''Request failed with status code '' + request.getStatus() + '': '' + response;
        }
    }
};


try {
    var params = JSON.parse(value);
    
    Eda.setParams(params);
    Eda.setProxy(params.HTTPProxy);
    Eda.sendMessage();
    return true;
}
catch (error) {
    Zabbix.log(3, ''[ Event-Driven Ansible webhook ] notification failed: '' + error);
    throw ''Sending failed: '' + error;
}','30s','0','0','','','','0'),
('73','4','Express.ms','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var Express = {
    params: [],

    setParams: function (params) {
        if (typeof params !== ''object'') {
            return;
        }

        Express.params = params;

        if (typeof Express.params.url === ''string'' && !Express.params.url.endsWith(''/'')) {
            Express.params.url += ''/'';
        }
    },

    setProxy: function (HTTPProxy) {
        Express.HTTPProxy = HTTPProxy;
    },

    request: function (query, data) {
        var response,
            url = Express.params.url + query,
            request = new HttpRequest();

        request.addHeader(''Content-Type: application/json'');
        request.addHeader(''Authorization: Bearer '' + Express.params.token);

        if (typeof Express.HTTPProxy !== ''undefined'' && Express.HTTPProxy !== '''') {
            request.setProxy(Express.HTTPProxy);
        }

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        Zabbix.log(4, ''[ Express Webhook ] Sending request: '' + url +
            ((typeof data === ''string'') ? (''\\n'' + data) : ''''));

        response = request.post(url, data);

        Zabbix.log(4, ''[ Express Webhook ] Received response with status code '' +
            request.getStatus() + ''\\n'' + response);

        if (response !== null) {
            try {
                response = JSON.parse(response);
            }
            catch (error) {
                Zabbix.log(4, ''[ Express Webhook ] Failed to parse response received from Express'');
                response = {};
            }
        }

        if (response.status !== ''ok'') {
            var message = ''Request failed with status code '' + request.getStatus();

            if (typeof response.reason !== ''undefined'') {
                message += '': '' + JSON.stringify(response.reason);
            }

            throw message + ''. Check debug log for more information.'';
        }

        return response.result;
    },

    postMessage: function (is_problem) {
        var data,
            url,
            result = {tags: {}},
            response;

        if (is_problem) {
            data = {
                group_chat_id: Express.params.send_to,
                notification: {
                    status: ''ok'',
                    body: Express.params.message
                }
            };
            url = ''api/v4/botx/notifications/direct'';
        }
        else {
            data = {
                reply: {
                    status: ''ok'',
                    body: Express.params.message
                }
            };
            url = ''api/v3/botx/events/reply_event'';

            try {
                var tags = JSON.parse(Express.params.tags);
            }
            catch (error) {
                throw ''Value of "express_tags" is not JSON. Value: '' + Express.params.tags + ''.'';
            }

            tags.forEach(function(tag) {
                if (tag.tag === ''__zbx_ex_sync_id_'' + Express.params.send_to) {
                    data.source_sync_id = tag.value;
                }
            });

            if (!data.source_sync_id) {
                throw ''Cannot update data. sync_id for the provided sender is unknown.'';
            }
        }

        response = Express.request(url, data);

        if (is_problem && response.sync_id) {
            result.tags[''__zbx_ex_sync_id_'' + Express.params.send_to] = response.sync_id;

            return JSON.stringify(result);
        }
        else {
            return ''OK'';
        }
    }
};

try {
    var params = JSON.parse(value),
        express = {},
        required_params = [
            ''express_url'', ''express_send_to'', ''express_message'', ''express_tags'', ''express_token'',
            ''event_source'', ''event_value'', ''event_update_status''
        ];

    Object.keys(params)
        .forEach(function (key) {
            if (key.startsWith(''express_'')) {
                express[key.substring(8)] = params[key];
            }

            if (required_params.indexOf(key) !== -1
                    && (params[key].trim() === '''' || params[key] === ''{ALERT.SENDTO}'')) {
                throw ''Parameter "'' + key + ''" can\\''t be empty.'';
            }
        });

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

    // Forcing event_value and event_update_status for non trigger-based events.
    if (params.event_source !== ''0'' ) {
        params.event_value = ''1'';
        params.event_update_status = ''0'';
    }

    if (params.event_value !== ''0'' && params.event_value !== ''1'') {
        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.'';
    }

    Express.setParams(express);
    Express.setProxy(params.HTTPProxy);

    return Express.postMessage(params.event_value === ''1'' && params.event_update_status === ''0'');
}
catch (error) {
    Zabbix.log(3, ''[ Express Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','1','0','','','','0'),
('74','4','GitHub','','','','','','','','25','0','0','0','0','1','3','10s','1',E'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();
	}
}

const CWebhookHelper = {

	createProblemURL: function(event_source, zabbix_url, trigger_id, event_id) {
		if (event_source === ''0'') {
			return zabbix_url + ''/tr_events.php?triggerid='' + trigger_id + ''&eventid='' + event_id;
		} else if (event_source === ''4'') {
			return zabbix_url + ''/zabbix.php?action=service.list'';
		}

		return zabbix_url;
	},

};

var serviceLogName = ''GitHub Webhook'',
	Logger = new CLogger(serviceLogName),
	GitHub = CWebhook;

GitHub.prototype.onCheckParams = function () {
	CParamValidator.validate({
		alert_message: {type: ''string''},
		alert_subject: {type: ''string''},
		github_api_version: {type: ''string''},
		github_repo: {type: ''string''},
		github_token: {type: ''string''},
		github_url: {type: ''string'', url: true},
		github_user_agent: {type: ''string''},
		github_zabbix_event_priority_label_prefix: {type: ''string'', default: ''Zabbix Event Priority: ''},
		github_zabbix_event_source_label_prefix: {type: ''string'', default: ''Zabbix Event Source: ''},
		github_zabbix_event_status_label_prefix: {type: ''string'', default: ''Zabbix Event Status: ''},
		github_zabbix_generic_label: {type: ''string'', default: ''Zabbix GitHub Webhook''},
		zabbix_url: {type: ''string'', url: true}
	}, this.params);

	this.request_headers = {
		''User-Agent'': this.params.github_user_agent,
		''Accept'': ''application/vnd.github+json'',
		''X-GitHub-Api-Version'': this.params.github_api_version,
		''Authorization'': ''Bearer '' + this.params.github_token
	};

	this.payload_data = {
		title: this.params.alert_subject,
		labels: [
			{
				name: this.params.github_zabbix_generic_label
			}
		]
	};

	this.result = {tags: {}};
};

function checkResponse(response, received_code, required_code, response_field, error_message) {
	if (received_code != required_code || !CParamValidator.isDefined(response[response_field])) {
		var message = error_message + '' Request failed with status code '' + received_code;

		if (CParamValidator.isDefined(response.message) && Object.keys(response.message).length > 0) {
			message += '': '' + response.message;
		}

		throw message + '' Check debug log for more information.'';
	}
}

GitHub.prototype.createIssue = function () {
	this.payload_data.body = this.params.alert_message + ''\\n'' + CWebhookHelper.createProblemURL(this.params.event_source, this.params.zabbix_url, this.params.trigger_id, this.params.event_id);

	this.request.addHeaders(this.request_headers);

	const response = this.request.jsonRequest(''POST'', this.params.github_url + ''/repos/'' + this.params.github_repo + ''/issues'', this.payload_data);

	checkResponse(response, this.request.getStatus(), 201, ''number'', ''Cannot create GitHub issue.'');

	return response;
}

GitHub.prototype.updateIssue = function () {
	this.request.addHeaders(this.request_headers);

	const response = this.request.jsonRequest(''PATCH'', this.params.github_url + ''/repos/'' + this.params.github_repo + ''/issues/'' + this.params.github_issue_number, this.payload_data);

	checkResponse(response, this.request.getStatus(), 200, ''number'', ''Cannot update GitHub issue.'');

	return response;
}

GitHub.prototype.addIssueComment = function () {
	this.payload_data = {
		body: this.params.alert_message
	};

	this.request.addHeaders(this.request_headers);

	const response = this.request.jsonRequest(''POST'', this.params.github_url + ''/repos/'' + this.params.github_repo + ''/issues/'' + this.params.github_issue_number + ''/comments'', this.payload_data);

	checkResponse(response, this.request.getStatus(), 201, ''id'', ''Cannot add comment for GitHub issue.'');

	return response;
}

GitHub.prototype.onProblem = function (alert) {
	if (CParamValidator.isMacroSet(this.params.github_issue_number)) {
		return this.onUpdate(alert);
	}

	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);

	if (this.params.event_source === ''0'') {
		CParamValidator.validate({event_id: {type: ''integer''}, trigger_id: {type: ''integer''}}, this.params);
	}

	this.payload_data.labels.push({name: this.params.github_zabbix_event_source_label_prefix + alert.source});

	this.payload_data.labels.push({name: this.params.github_zabbix_event_status_label_prefix + ''Problem''});

	if (!CParamValidator.isEmpty(this.params.event_severity) && CParamValidator.isMacroSet(this.params.event_severity, ''EVENT.SEVERITY'')) {
		this.payload_data.labels.push({name: this.params.github_zabbix_event_priority_label_prefix + this.params.event_severity});
	}

	const response = this.createIssue();

	this.result.tags = {
		__zbx_github_issue_number: response.number,
		__zbx_github_repo: this.params.github_repo,
		__zbx_github_link: response.html_url
	};

	return this.result;
}

GitHub.prototype.onUpdate = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);

	if (!CParamValidator.isMacroSet(this.params.github_issue_number)) {
		throw "Failed to update the existing issue: no issue number was received."
	}

	this.payload_data.labels.push({name: this.params.github_zabbix_event_source_label_prefix + alert.source});

	if (this.params.event_value === ''0'') {
		this.payload_data.labels.push({name: this.params.github_zabbix_event_status_label_prefix + ''Resolved''});
	} else {
		this.payload_data.labels.push({name: this.params.github_zabbix_event_status_label_prefix + ''Problem''});
	}

	if (!CParamValidator.isEmpty(this.params.event_severity) && CParamValidator.isMacroSet(this.params.event_severity, ''EVENT.SEVERITY'')) {
		this.payload_data.labels.push({name: this.params.github_zabbix_event_priority_label_prefix + this.params.event_severity});
	}

	this.updateIssue();

	this.request.clearHeader();
	this.addIssueComment();

	return this.result;
}

GitHub.prototype.onResolve = function (alert) {
	return this.onUpdate(alert);
}

GitHub.prototype.onDiscovery = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);

	this.payload_data.labels.push({name: this.params.github_zabbix_event_source_label_prefix + alert.source});

	this.createIssue();

	return this.result;
}

GitHub.prototype.onAutoreg = function (alert) {
	return this.onDiscovery(alert);
}

try {
	var hook = new GitHub(value);
	hook.request = new CHttpRequest(Logger);
	return hook.run();
}
catch (error) {
	Logger.log(Logger.WARN, ''notification failed: '' + error);
	throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_github_link}','Github: Issue {EVENT.TAGS.__zbx_github_issue_number}','This media type integrates your Zabbix installation with GitHub using the Zabbix webhook feature.

GitHub configuration:

1. Create an access token.

One of the simplest ways to send authenticated requests is to use a personal access token - either a classic or a fine-grained one:
https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api?apiVersion=2022-11-28#authenticating-with-a-personal-access-token

Classic personal access token

You can create a new classic personal access token by following the instructions in the official documentation:
https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic

The token user must have a permission to create issues and issue comments in the desired repositories. For webhook to work on private repositories, the "repo" scope must be set in token settings to have full control of private repositories.

Additional information about OAuth scopes is available in the official documentation:
https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes

Fine-grained personal access token

Alternatively, you can use a fine-grained personal access token:
https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token

In order to use fine-grained tokens to monitor organization-owned repositories, organizations must opt in to fine-grained personal access tokens and set up a personal access token policy:
https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization

The fine-grained token needs to have the following permission set to provide access to the repository issues:
- "Issues" repository permissions (write)

2. Copy and save the created token somewhere, as it will be shown only once for security reasons.

Zabbix configuration:

1. Before you can start using Zammad webhook, set up the global macro "{$ZABBIX.URL}":
- In the Zabbix web interface, go to "Administration" → "Macros" section in the dropdown menu in the top left corner.
- Set up the global macro "{$ZABBIX.URL}" which will contain the URL to the Zabbix frontend. The URL should be either an IP address, a fully qualified domain name, or localhost.
- Specifying a protocol is mandatory, whereas the port is optional. Depending on the web server configuration you might also need to append "/zabbix" to the end of URL. Good examples:
  - http://zabbix.com
  - https://zabbix.lan/zabbix
  - http://server.zabbix.lan/
  - http://localhost
  - http://127.0.0.1:8080
- Bad examples:
  - zabbix.com
  - http://zabbix/

2. Set the "github_token" webhook parameter value to the access token that you created previously.

You can also adjust the issue labels created by the webhook in the following parameters:
- github_zabbix_event_priority_label_prefix - the prefix for the issue label that displays the Zabbix event priority in the supported event sources. It is set to "Zabbix Event Priority: " by default.
- github_zabbix_event_source_label_prefix - the prefix for the issue label that displays the Zabbix event source. It is set to "Zabbix Event Source: " by default.
- github_zabbix_event_status_label_prefix - the prefix for the issue label that displays the Zabbix event status. It is set to "Zabbix Event Status: " by default.
- github_zabbix_generic_label - the label that is added to all issues created by the webhook. It is set to "Zabbix GitHub Webhook" by default.

Note that the webhook will reuse the labels with the same name that already exist in the repository (including the color, so it can changed from the default value for new labels in GitHub, if needed). Also, the labels are replaced when the issue is updated, so any user-added labels will be removed.

4. Create a Zabbix user and add media:
- If you want 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).
- In the "Media" tab, add a new media and select "Zammad" type from the drop-down list. In field "Send to" specify the full repo name (owner/project name) e.g. johndoe/example-project.
- Make sure this user has access to all hosts for which you would like problem notifications to be sent to GitHub.

5. Great! You can now start using this media type in actions and create GitHub issues!

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/github','0'),
('75','4','GLPi','','','','','','','','25','0','0','0','0','1','1','10s','1',E'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();
	}
}

const CWebhookHelper = {

	createProblemURL: function(event_source, zabbix_url, trigger_id, event_id) {
		if (event_source === ''0'') {
			return zabbix_url + ''/tr_events.php?triggerid='' + trigger_id + ''&eventid='' + event_id;
		} else if (event_source === ''4'') {
			return zabbix_url + ''/zabbix.php?action=service.list'';
		}

		return zabbix_url;
	},

};

const serviceLogName = ''GLPi Webhook'',
	Logger = new CLogger(serviceLogName),
	GLPi = CWebhook;

GLPi.prototype.onCheckParams = function () {
	CParamValidator.validate({
		alert_message: { type: ''string'' },
		alert_subject: { type: ''string'' },
		zabbix_url: { type: ''string'', url: true },
		glpi_url: { type: ''string'', url: true },
		glpi_user_token: { type: ''string'' }
	}, this.params);

	this.params.use_app_token = false;

	if (CParamValidator.isDefined(this.params.glpi_app_token) && !CParamValidator.isEmpty(this.params.glpi_app_token)) {
		this.params.use_app_token = true;
	}

	if (this.params.event_source === ''0'') {
		CParamValidator.validate({ trigger_id: { type: ''integer'' }, event_id: { type: ''integer'' } }, this.params);
		this.params.zabbix_url = CWebhookHelper.createProblemURL(this.params.event_source, this.params.zabbix_url, this.params.trigger_id, this.params.event_id);
	}

	if (params.event_value != ''0'' && CParamValidator.isMacroSet(this.params.glpi_problem_id)) {
		this.params.event_update_status = ''1'';
	}

	this.data = {
		input: {
			name: this.params.alert_subject,
			urgency: (this.params.event_nseverity = isNaN(Number(this.params.event_nseverity)) ? 2 : this.params.event_nseverity)
		}
	};

	this.dataFollowup = {
		input: {
			items_id: this.params.glpi_problem_id,
			itemtype: ''Problem'',
			content: this.params.alert_message + ''<br> <a href='' + this.params.zabbix_url + ''>Link to problem in Zabbix</a>'',
		}
	};

	this.result = { tags: {} };
}

GLPi.prototype.getAuthToken = function () {

	this.request.addHeaders(''Authorization: user_token '' + this.params.glpi_user_token);
	if (this.params.use_app_token) {
		this.request.addHeaders(''App-Token: '' + this.params.glpi_app_token);
	}
	var response = this.request.jsonRequest(''post'', this.params.glpi_url + ''/apirest.php/initSession'');

	if ((this.request.getStatus() !== 200 && this.request.getStatus() !== 201)) {
		Logger.log(Logger.INFO, ''HTTP code: '' + this.request.getStatus());
		if (CParamValidator.isType(response.description, ''string'')) {
			throw response.description;
		}
		else {
			Logger.log(Logger.INFO, ''Request not successful. Received response: '' + JSON.stringify(response));
			throw ''Unknown INFO. Check debug log for more information.'';
		}
	}
	if (!CParamValidator.isType(response.session_token, ''string'') || CParamValidator.isEmpty(response.session_token)) {
		Logger.log(Logger.INFO, ''Check getting GLPi sessionToken: '' + response.session_token);
		throw ''Required GLPi sessionToken is not received.'';
	}

	return response.session_token;
}

GLPi.prototype.sendRequest = function (method, path, data) {

	this.request.clearHeader();
	this.request.addHeaders(''Session-Token:'' + this.params.authToken);
	if (this.params.use_app_token) {
		this.request.addHeaders(''App-Token: '' + this.params.glpi_app_token);
	}
	var response = this.request.jsonRequest(method, this.params.glpi_url + path, data);

	if ((this.request.getStatus() !== 200 && this.request.getStatus() !== 201)) {
		Logger.log(Logger.INFO, ''HTTP code: '' + this.request.getStatus());
		if (CParamValidator.isType(response.description, ''string'')) {
			throw response.description;
		}
		else {
			Logger.log(Logger.INFO, ''Request not successful. Received response: '' + JSON.stringify(response));
			throw ''Unknown INFO. Check debug log for more information.'';
		}
	}

	return response;
}

GLPi.prototype.createProblem = function (status) {
	this.data.input.content = this.params.alert_message + ''<br> <a href='' + this.params.zabbix_url + ''>Link to problem in Zabbix</a>'';

	if (CParamValidator.isDefined(status)) {
		this.data.input.status = status;
	}

	var response = this.sendRequest(''post'', ''/apirest.php/Problem/'', this.data);

	if (!CParamValidator.isDefined(response.id)) {
		throw ''Cannot create GPLi problem. Check debug log for more information.'';
	}

	return response.id
}

GLPi.prototype.updateProblem = function (status) {
	CParamValidator.validate({ glpi_problem_id: { type: ''string'' } }, this.params);
	this.data.id = this.params.glpi_problem_id;

	if (CParamValidator.isDefined(status)) {
		this.data.input.status = status;
	}

	this.sendRequest(''put'', ''/apirest.php/Problem/'' + this.params.glpi_problem_id, this.data);
	this.sendRequest(''post'', ''/apirest.php/Problem/'' + this.params.glpi_problem_id + ''/ITILFollowup'', this.dataFollowup);
}

GLPi.prototype.onProblem = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	const problem_id = this.createProblem(1)

	this.result.tags.__zbx_glpi_problem_id = problem_id;
	this.result.tags.__zbx_glpi_link = this.params.glpi_url + ''/front/problem.form.php?id='' + problem_id;

	return this.result;
}

GLPi.prototype.onUpdate = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	this.updateProblem();

	return this.result;
}

GLPi.prototype.onResolve = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	this.updateProblem(5);

	return this.result;
}

GLPi.prototype.onDiscovery = function () {
	return this.createProblem();
}

GLPi.prototype.onAutoreg = function () {
	return this.createProblem();
}

try {
	var hook = new GLPi(value);
	hook.request = new CHttpRequest(Logger);
	hook.params.authToken = hook.getAuthToken();
	return hook.run();
}
catch (error) {
	Logger.log(Logger.WARN, ''notification failed: '' + error);
	throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_glpi_link}','GLPi: Problem {EVENT.TAGS.__zbx_glpi_problem_id}','This media type integrates your Zabbix installation with your GLPi installation using the Zabbix webhook feature.

GLPi configuration:

1. Enable access to the GLPi REST API:
- In the GLPi web interface, go to "Setup" → "General" → "API".
- Set the "Enable Rest API" and "Enable Rest API" options to "Yes" and click the "Save" button.

2. Add a new API client:
- Click the "Add API client" button.
- Specify the API client name and set the "Active" option to "Yes".
- For security reasons, you may want to restrict the API client to the IP address of Zabbix server and/or create an additional application token (will be generated by default; you can uncheck the "Regenerate" checkbox if you don''t want to use it).
- Click the "Add" button.
- If you''ve opted to create an application token, open the settings of the created API client, and then copy and save the generated application token.

3. Create a new user profile with permissions to create problems and followups (alternatively, you can use an existing profile with sufficient privileges):
- Go to "Administration" → "Profiles" and click the "Add" button on the top of the page.
- Specify the profile name and set the "Profile''s Interface" option to "Standard Interface", and then click the "Add" button.
- Open the created profile and click the "Assistance" tab.
- Set the "Add followup (requester)" permission for the "Followups" line in the "Followups/Tasks" section.
- Set the "Update", "Create", and "See all" permissions in the "Problems" section.
- Click the "Save" button.

4. Create a new user:
- Go to "Administration" → "Users" and click the "Add User" button on the top of the page.
- Specify the user login and set the "Profiles" option to the profile that you created in the previous step (or any other existing profile with permissions to create problems and followups).
- Click the "Add" button.
- Open the profile of the created user and check the "Regenerate" checkbox of the "API token" option; click "Save".
- Copy and save the generated user API token.

Zabbix configuration:

1. Before you can start using the GLPi webhook, set the global macro "{$ZABBIX.URL}":
- In the Zabbix web interface, go to "Administration" → "Macros" in the top-left drop-down menu.
- Set the global macro "{$ZABBIX.URL}" to the URL of the Zabbix frontend. The URL should be either an IP address, a fully qualified domain name, or localhost.
- Specifying a protocol is mandatory, whereas the port is optional. Depending on the web server configuration, you might also need to append "/zabbix" to the end of URL. Good examples:
  - http://zabbix.com
  - https://zabbix.lan/zabbix
  - http://server.zabbix.lan/
  - http://localhost
  - http://127.0.0.1:8080
- Bad examples:
  - zabbix.com
  - http://zabbix/

2. Set the following webhook parameters:
- glpi_app_token - if you''ve opted to use an application token during the creation of API client, specify it here; otherwise leave it empty
- glpi_url - the frontend URL of your GLPi installation
- glpi_user_token - the user token that was generated during creation of GLPi user

3. Click the "Enabled" checkbox to enable the media type and click the "Update" button to save the webhook settings.

4. 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).
- In the "Media" tab, click "Add" and select the type "GLPi" from the drop-down list. Add any value in the "Send to" field: it is not used in the webhook, but is required.
- Make sure this user has access to all hosts for which you would like problem notifications to be sent to GLPi.

5. Done! You can now start using this media type in actions and create problem items in GLPi.

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/glpi','0'),
('76','4','iLert','','','','','','','','25','0','0','0','0','1','3','10s','1','try {
    var result = { tags: {} },
        params = JSON.parse(value),
        req = new HttpRequest(),
        resp = '''';

    if (typeof params.HTTPProxy === ''string'' && params.HTTPProxy.trim() !== '''') {
        req.setProxy(params.HTTPProxy);
    }
    var alertSourceKey = params[''.ILERT.ALERT.SOURCE.KEY''];
    if (!alertSourceKey || (typeof alertSourceKey === ''string'' && alertSourceKey.trim() === '''')) {
        throw ''incorrect value for variable "ILERT.ALERT.SOURCE.KEY". The value must be a non-empty string.'';
    }
    delete params[''.ILERT.ALERT.SOURCE.KEY''];
    
    var ilertApiBaseURL = "https://api.ilert.com";
    var reqURL = encodeURI(ilertApiBaseURL + "/api/v1/events/zabbix-mt/" + alertSourceKey)

    var incidentKey = "zabbix-" + params[''EVENT.ID''];
    var incidentViewURL = ilertApiBaseURL + "/api/v1/incidents/resolve-ik/" + alertSourceKey + "/" + incidentKey;

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

    Zabbix.log(4, ''[iLert Webhook] Sending request:'' + JSON.stringify(params));
    resp = req.post(reqURL, JSON.stringify(params));
    Zabbix.log(4, ''[iLert Webhook] Receiving response:'' + resp);

    try {
        resp = JSON.parse(resp);
    }
    catch (error) {
        throw ''incorrect response. iLert returned a non-JSON object.'';
    }

    if (req.getStatus() == 200) {
        result.tags.__ilert_incident_url = incidentViewURL;
        return JSON.stringify(result);
    }

    if (req.getStatus() == 400 && typeof resp === ''object'' && typeof resp.code === ''string'') {
        if (resp.code === ''NO_OPEN_INCIDENT_WITH_KEY'') {
          return JSON.stringify(result);
        }
        if (resp.code === ''INCIDENT_ALREADY_ACCEPTED'') {
          result.tags.__ilert_incident_url = incidentViewURL;
          return JSON.stringify(result);
        }
    }

    if (typeof resp === ''object'' && typeof resp.message === ''string'') {
        throw resp.message;
    }
    else {
        throw ''Unknown error.'';
    }
}
catch (error) {
    Zabbix.log(3, ''[iLert Webhook] Notification failed : '' + error);
    throw ''iLert notification failed : '' + error;
}','30s','1','1','{EVENT.TAGS.__ilert_incident_url}','iLert incident','Please refer to https://docs.ilert.com/integrations/zabbix/native 
  
Set global macro {$ZABBIX.URL} with your Zabbix server URL.
Add a dedicated user with the media type "iLert". You can also rewrite the incident summary via ".ILERT.INCIDENT.SUMMARY" parameter or leave it empty to use the standard pattern.','0'),
('77','4','iTop','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var Itop = {
    params: {},

    setParams: function (params) {
        if (typeof params !== ''object'') {
            return;
        }

        if (params.log !== ''private_log'' && params.log !== ''public_log'') {
            throw ''Incorrect "itop_log" parameter given: '' + params.log + ''\\nMust be "private_log" or "public_log".'';
        }

        Itop.params = params;
        if (typeof Itop.params.url === ''string'') {
            if (!Itop.params.url.endsWith(''/'')) {
                Itop.params.url += ''/'';
            }

            Itop.params.url += ''webservices/rest.php?version='' + encodeURIComponent(Itop.params.api_version);
        }
    },

    setProxy: function (HTTPProxy) {
        Itop.HTTPProxy = HTTPProxy;
    },

    setCreatePayload: function () {
        json_data.operation = ''core/create'';
        json_data.fields.org_id = Itop.params.organization_id;
        json_data.fields.title = params.alert_subject;
        json_data.fields.description = params.alert_message.replace(''<'', ''&lt;'')
            .replace(''>'', ''&gt;'')
            .replace(/(?:\\r\\n|\\r|\\n)/g, ''<br>'');
    },

    setUpdatePayload: function () {
        json_data.operation = ''core/update'';
        json_data.key = Itop.params.id;
        json_data.fields.title = params.alert_subject;
        json_data.fields[Itop.params.log] = {
            add_item: {
                message: params.alert_subject + ''\\n'' + params.alert_message,
                format: ''text''
            }
        };
    },

    request: function (data) {
        [''url'', ''user'', ''password'', ''organization_id'', ''class'', ''api_version'', ''id''].forEach(function (field) {
            if (typeof Itop.params !== ''object'' || typeof Itop.params[field] === ''undefined''
                    || Itop.params[field] === '''' ) {
                throw ''Required Itop param is not set: "itop_'' + field + ''".'';
            }
        });

        var response,
            url = Itop.params.url,
            request = new HttpRequest(),
            object;

        request.addHeader(''Content-Type: multipart/form-data'');
        request.addHeader(''Authorization: Basic '' + btoa(Itop.params.user + '':'' + Itop.params.password));

        if (Itop.HTTPProxy) {
            request.setProxy(Itop.HTTPProxy);
        }

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        Zabbix.log(4, ''[ iTop Webhook ] Sending request: '' + url + ''&json_data='' + data);

        response = request.post(url + ''&json_data='' + encodeURIComponent(data));

        Zabbix.log(4, ''[ iTop Webhook ] Received response with status code '' + request.getStatus() + ''\\n'' + response);

        try {
            response = JSON.parse(response);
        }
        catch (error) {
            Zabbix.log(4, ''[ iTop Webhook ] Failed to parse response received from iTop'');
            throw ''Failed to parse response received from iTop.\\nRequest status code '' +
                    request.getStatus() + ''. Check debug log for more information.'';
        }

        if (request.getStatus() < 200 || request.getStatus() >= 300) {
            throw ''Request failed with status code '' + request.getStatus() + ''. Check debug log for more information.'';
        }
        else if (typeof response.code !== ''undefined'' && response.code !== 0) {
            throw ''Request failed with iTop code '' + response.code + '': '' +
                    JSON.stringify(response.message) + ''. Check debug log for more information.'';
        }
        else {
            Object.keys(response.objects)
                .forEach(function (key) {
                    object = response.objects[key];
                });
    
            return {
                status: request.getStatus(),
                response: object.fields
            };
        }
    }
};

try {
    var params = JSON.parse(value),
        json_data = {},
        itop_params = {},
        result = {tags: {}},
        required_params = [
            ''alert_subject'', ''summary'', ''event_recovery_value'',
            ''event_source'', ''event_value'', ''action_name''
        ];

    Object.keys(params)
        .forEach(function (key) {
            if (key.startsWith(''itop_'')) {
                itop_params[key.substring(5)] = params[key];
            }
            else if (required_params.indexOf(key) !== -1 && params[key] === '''') {
                throw ''Parameter "'' + key + ''" can\\''t be empty.'';
            }
        });

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

    // 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.'';
    }

    if (params.event_source !== ''0'' && params.event_recovery_value === ''0'') {
        throw ''Recovery operations are supported only for trigger-based actions.'';
    }

    Itop.setParams(itop_params);
    Itop.setProxy(params.HTTPProxy);

    json_data.operation = '''';
    json_data.class = Itop.params.class;
    json_data.comment = Itop.params.comment;
    json_data.output_fields = ''id, friendlyname'';
    json_data.fields = {};

    // Create issue for non trigger-based events.
    if (params.event_source !== ''0'' && params.event_recovery_value !== ''0'') {
        Itop.setCreatePayload();
        Itop.request(json_data);
    }
    // Create issue for trigger-based events.
    else if (params.event_value === ''1'' && params.event_update_status === ''0''
            && (Itop.params.id === ''{EVENT.TAGS.__zbx_itop_id}'' || Itop.params.id === ''*UNKNOWN*'')) {
        Itop.setCreatePayload();

        var response = Itop.request(json_data);

        result.tags.__zbx_itop_id = response.response.id;
        result.tags.__zbx_itop_key = response.response.friendlyname;
        result.tags.__zbx_itop_link = params.itop_url + (params.itop_url.endsWith(''/'') ? '''' : ''/'') +
                ''pages/UI.php?operation=details&class='' + encodeURIComponent(Itop.params.class) + ''&id='' +
                encodeURIComponent(response.response.id);
    }
    // Update created issue for trigger-based event.
    else {
        if (Itop.params.id === ''{EVENT.TAGS.__zbx_itop_id}'' || Itop.params.id === ''*UNKNOWN*'') {
            throw ''Incorrect iTop ticket ID given: '' + Itop.params.id;
        }
        Itop.setUpdatePayload();
        Itop.request(json_data);
    }

    return JSON.stringify(result);
}
catch (error) {
    Zabbix.log(3, ''[ iTop Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_itop_link}','iTop: {EVENT.TAGS.__zbx_itop_key}','','0'),
('78','4','Jira','','','','','','','','25','0','0','0','0','1','3','10s','1',E'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 CWebhookHelper = {

	createProblemURL: function(event_source, zabbix_url, trigger_id, event_id) {
		if (event_source === ''0'') {
			return zabbix_url + ''/tr_events.php?triggerid='' + trigger_id + ''&eventid='' + event_id;
		} else if (event_source === ''4'') {
			return zabbix_url + ''/zabbix.php?action=service.list'';
		}

		return zabbix_url;
	},

};

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 ZABBIX_SEVERITY_MAP = ["not_classified", "information", "warning", "average", "high", "disaster"];

var serviceLogName = ''Jira Webhook'',
	Logger = new CLogger(serviceLogName),
	Jira = CWebhook;

Jira.prototype.onCheckParams = function () {
	CParamValidator.validate({
		jira_url: { type: ''string'', url: true},
		jira_user: { type: ''string'' },
		jira_password: { type: ''string'' },
		jira_issue_type: { type: ''string'' },
		jira_project_key: { type: ''string'' },
		event_source: { type: ''string'' },
		alert_subject: { type: ''string'' },
		alert_message: { type: ''string'' },
		event_nseverity: { type: ''integer'', default: 0 }
	}, this.params);

	this.priority = this.params.event_source == 3 ? this.params.jira_priority_internal : this.params[''severity_'' + ZABBIX_SEVERITY_MAP[this.params.event_nseverity]];
	this.params.event_tags_json = parseTags(this.params.event_tags_json, false);

	if (this.params.event_tags_json.length > 0) {
		this.labels = setLabels(this.params.event_tags_json);
	}

	parseOptionalFields();

	this.result = { tags: {} };
}

function escapeMarkup(str) {
	var length = str.length,
		result = '''',
		markup = [''{'', ''|'', ''}'', ''~'', ''_'', ''\\\\'', ''['', '']'', ''^'', ''<'', ''>'', ''?'', ''!'', ''#'', ''+'', ''*'', ''&''];

	for (var i = 0; i < length; i++) {
		var char = str[i];
		result += (markup.indexOf(char) !== -1) ? (''&#'' + str[i].charCodeAt() + '';'') : char;
	}

	return result;
}

function parseOptionalFields() {
	this.params.customfields = {};
	this.params.components = [];

	Object.keys(this.params).forEach(function (key) {
		if (key.startsWith(''customfield_'')) {
			this.params.customfields[key] = this.params[key];
		}

		if (key.startsWith(''component_'')) {
			this.params.components.push({"name": this.params[key]})
		}
	});
}

Jira.prototype.addCustomFields = function (data) {
	if (typeof this.params.customfields === ''object'' && Object.keys(this.params.customfields).length) {
		var schemaData = this.sendRequest(''GET'', ''/rest/api/latest/field''),
			schema = {};

		schemaData.forEach(function (item) {
			schema[item.id] = item.schema;
		});

		Object.keys(this.params.customfields).forEach(function (field) {
			if (typeof schema[field] === ''object'' && schema[field].type) {				
				const type = schema[field].type;

				if (type === ''number'') {
					data.fields[field] = parseInt(this.params.customfields[field]);
				}
				else if (type === ''string'' && schema[field][''custom''].split('':'')[1] === ''url'') {
					if (this.params.customfields[field] === ''zabbix_url'') {
						data.fields[field] = CWebhookHelper.createProblemURL(this.params.event_source, this.params.zabbix_url, this.params.trigger_id, this.params.event_id);
					} else {
						data.fields[field] = this.params.customfields[field];
					}
				}
				else if (type === ''date'') {
					if (this.params.customfields[field].match(/\\d+[.-]\\d+[.-]\\d+/) !== null) {
						data.fields[field] = this.params.customfields[field].replace(/\\./g, ''-'');
					}
				}
				else if (type === ''datetime'') {
					if (this.params.customfields[field].match(/\\d+[.-]\\d+[.-]\\d+T\\d+:\\d+:\\d+/) !== null) {
						data.fields[field] = this.params.customfields[field].replace(/\\./g, ''-'');
					}
				}
				else if (type === ''option'') {
					data.fields[field] = { ''value'': this.params.customfields[field] };
				}
				else if (type === ''array'') {
					if (schema[field].items === ''option'') {
						const valuesList = this.params.customfields[field].split('','');
						data.fields[field] = [];
						valuesList.forEach(function (val) {
							data.fields[field].push({''value'': val})
						});
					}
					else {
						data.fields[field] = [this.params.customfields[field]];
					}
				}
				else {
					data.fields[field] = this.params.customfields[field];
				}
			}
		});
	}
	else {
		Logger.log(Logger.WARN, ''No customfields found.'');
	}

	return data;
}

function parseTags(event_tags) {
	try {
		return JSON.parse(event_tags);
	}
	catch (error) {
		if (this.params.event_source == 0 || this.params.event_source > 2) {
			Logger.log(Logger.WARN, ''No event tags were found. Should be an object. Received event tags: "'' + event_tags + ''".'');
		}
		return [];
	}
}

function getTagValue(tags, key, throw_on_fail) {
	for (i in tags) {
		if (tags[i].tag === key) {
			return tags[i].value
		}
	}

	if (!throw_on_fail) {
		return false;
	} else {
		throw ''Tag "'' + key + ''" is not in the event tag list.''
	}
};

function setLabels(event_tags_json) {
	var buffer = [];

	event_tags_json.forEach(function (tag) {
		if (typeof tag.tag !== ''undefined'' && typeof tag.value !== ''undefined'' && !tag.tag.startsWith(''__zbx'')) {
			label = (tag.tag + (tag.value ? ('':'' + tag.value) : '''')).replace(/\\s/g, ''_'');
			if (label.length < 256) {
				buffer.push(label);
			}
		}
	});

	if (buffer.length === 0) {
		Logger.log(Logger.WARN, ''No labels were set.'');
	}

	return buffer;
}

Jira.prototype.commentIssue = function (issue_key) {
	var data = {
		body: this.params.alert_message,
	};
	this.sendRequest(''POST'', ''/rest/api/latest/issue/'' + encodeURIComponent(issue_key) + ''/comment'', data);
}

Jira.prototype.sendRequest = function (method, path, data) {
	this.request.clearHeader();
	this.request.addHeaders(''Authorization: Basic '' + btoa(this.params.jira_user + '':'' + this.params.jira_password));

	var response = this.request.jsonRequest(method, this.params.jira_url + path, data);

	if ((this.request.getStatus() !== 200 && this.request.getStatus() !== 201)) {
		Logger.log(Logger.WARN, ''HTTP code: '' + this.request.getStatus());
		if (CParamValidator.isType(response.description, ''string'')) {
			throw response.description;
		}
		else {
			Logger.log(Logger.WARN, ''Request not successful. Received response: '' + JSON.stringify(response));
			throw ''Unknown error. Check debug log for more information.'';
		}
	}

	return response;
}

Jira.prototype.onProblem = function (alert) {
	if (this.params.event_tags_json.length > 0 && getTagValue(this.params.event_tags_json, ''__zbx_jira_issuekey'', false) !== false) {
		return this.onUpdate(alert, true);
	}

	var data = {
		fields: {
			project: {
				key: this.params.jira_project_key
			},
			issuetype: {
				name: this.params.jira_issue_type
			},
			summary: this.params.alert_subject,
			description: this.params.alert_message,
			priority: {
				"name": this.priority
			}
		}
	};

	if (this.labels && this.labels.length > 0) {
		data.fields.labels = this.labels;
	}

	if (this.params.components && this.params.components.length > 0) {
		data.fields.components = this.params.components;
	}

	var response = this.sendRequest(''POST'', ''/rest/api/latest/issue'', this.addCustomFields(data));
	this.result.tags = {
		__zbx_jira_issuekey: response.key,
		__zbx_jira_issuelink: this.params.jira_url + ''/browse/'' + response.key
	}
	return this.result;
}

Jira.prototype.onUpdate = function (alert, dontUpdatePriority) {
	var data = {
		fields: {
			summary: this.params.alert_subject,
			priority: {
				"name": this.priority
			}
		}
	};

	if (dontUpdatePriority && this.params.event_source != 0) {
		delete data[''fields''][''priority''];
	}

	var jira_issue_key = getTagValue(this.params.event_tags_json, ''__zbx_jira_issuekey'', true);
	this.sendRequest(''PUT'', ''/rest/api/latest/issue/'' + encodeURIComponent(jira_issue_key) + ''?returnIssue=true'', this.addCustomFields(data));
	this.commentIssue(jira_issue_key);

	return this.result;
}

Jira.prototype.onResolve = function (alert) {
	return this.onUpdate(alert, true);
}

Jira.prototype.onDiscovery = function (alert) {
	this.priority = this.params.jira_priority_discovery;
	return this.onProblem(alert);
}

Jira.prototype.onAutoreg = function (alert) {
	this.priority = this.params.jira_priority_autoregistration;
	return this.onProblem(alert);
}

try {
	var hook = new Jira(value);
	hook.request = new CHttpRequest(Logger);
	return hook.run();
}
catch (error) {
	Logger.log(Logger.WARN, ''notification failed: '' + error);
	throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_jira_issuelink}','Jira: {EVENT.TAGS.__zbx_jira_issuekey}','https://git.zabbix.com/projects/ZBX/repos/zabbix/browse/templates/media/jira

1. To make this integration work, you will need the following from Jira:
  * Jira instance URL;
  * Jira project key and issue type that the issues created by Zabbix will use;
  * Jira username and password or API token. API token is strongly recommended and can be obtained at https://id.atlassian.com/manage/api-tokens.
2. In the Zabbix web interface, go to Administration → Macros section. Set up the global macro "{$ZABBIX.URL}" which will contain the URL to the Zabbix frontend.
3. On this page replace the placeholder ''<...>'' values with the ones from the step #1.
4. In Zabbix, you need to have a Zabbix user and add Media with the Jira media type. Make sure this user has access to all hosts for which you would like problem issues to be created in Jira.','0'),
('79','4','Jira Service Management','','','','','','','','25','0','0','0','0','1','3','10s','1',E'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 CWebhookHelper = {

	createProblemURL: function(event_source, zabbix_url, trigger_id, event_id) {
		if (event_source === ''0'') {
			return zabbix_url + ''/tr_events.php?triggerid='' + trigger_id + ''&eventid='' + event_id;
		} else if (event_source === ''4'') {
			return zabbix_url + ''/zabbix.php?action=service.list'';
		}

		return zabbix_url;
	},

};

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 ZABBIX_SEVERITY_MAP = ["not_classified", "information", "warning", "average", "high", "disaster"];

var serviceLogName = ''Jira Service Management Webhook'',
	Logger = new CLogger(serviceLogName),
	Jira = CWebhook;

Jira.prototype.onCheckParams = function () {
	CParamValidator.validate({
		jira_url: { type: ''string'', url: true},
		jira_user: { type: ''string'' },
		jira_password: { type: ''string'' },
		jira_request_type_id: { type: ''string'' },
		jira_servicedesk_id: { type: ''string'' },
		issue_comments_public: {type: ''boolean'', default: false},
		event_source: { type: ''string'' },
		alert_subject: { type: ''string'' },
		alert_message: { type: ''string'' },
		event_nseverity: { type: ''integer'', default: 0 }
	}, this.params);

	this.priority = this.params.event_source == 3 ? this.params.jira_priority_internal : this.params[''severity_'' + ZABBIX_SEVERITY_MAP[this.params.event_nseverity]];
	this.params.event_tags_json = parseTags(this.params.event_tags_json, false);

	if (this.params.event_tags_json.length > 0) {
		this.labels = setLabels(this.params.event_tags_json);
	}

	parseOptionalFields();

	this.result = { tags: {} };
}

function escapeMarkup(str) {
	var length = str.length,
		result = '''',
		markup = [''{'', ''|'', ''}'', ''~'', ''_'', ''\\\\'', ''['', '']'', ''^'', ''<'', ''>'', ''?'', ''!'', ''#'', ''+'', ''*'', ''&''];

	for (var i = 0; i < length; i++) {
		var char = str[i];
		result += (markup.indexOf(char) !== -1) ? (''&#'' + str[i].charCodeAt() + '';'') : char;
	}

	return result;
}

function parseOptionalFields() {
	this.params.customfields = {};
	this.params.components = [];

	Object.keys(this.params).forEach(function (key) {
		if (key.startsWith(''customfield_'')) {
			this.params.customfields[key] = this.params[key];
		}

		if (key.startsWith(''component_'')) {
			this.params.components.push({"name": this.params[key]})
		}
	});
}

Jira.prototype.addCustomFields = function (data, requestFieldValues) {
	if (typeof this.params.customfields === ''object'' && Object.keys(this.params.customfields).length) {
		var schemaData = this.sendRequest(''GET'', ''/rest/api/latest/field''),
			schema = {};

		schemaData.forEach(function (item) {
			schema[item.id] = item.schema;
		});

		var placeholder = (requestFieldValues ? ''requestFieldValues'' : ''fields'');

		Object.keys(this.params.customfields).forEach(function (field) {
			if (typeof schema[field] === ''object'' && schema[field].type) {
				const type = schema[field].type;

				if (type === ''number'') {
					data[placeholder][field] = parseInt(this.params.customfields[field]);
				}
				else if (type === ''string'' && schema[field][''custom''].split('':'')[1] === ''url'') {
					if (this.params.customfields[field] === ''zabbix_url'') {
						data[placeholder][field] = CWebhookHelper.createProblemURL(this.params.event_source, this.params.zabbix_url, this.params.trigger_id, this.params.event_id);
					} else {
						data[placeholder][field] = this.params.customfields[field];
					}
				}
				else if (type === ''date'') {
					if (this.params.customfields[field].match(/\\d+[.-]\\d+[.-]\\d+/) !== null) {
						data[placeholder][field] = this.params.customfields[field].replace(/\\./g, ''-'');
					}
				}
				else if (type === ''datetime'') {
					if (this.params.customfields[field].match(/\\d+[.-]\\d+[.-]\\d+T\\d+:\\d+:\\d+/) !== null) {
						data[placeholder][field] = this.params.customfields[field].replace(/\\./g, ''-'');
					}
				}
				else if (type === ''option'') {
					data[placeholder][field] = { ''value'': this.params.customfields[field] };
				}
				else if (type === ''array'') {
					if (schema[field].items === ''option'') {
						const valuesList = this.params.customfields[field].split('','');
						data[placeholder][field] = [];
						valuesList.forEach(function (val) {
							data[placeholder][field].push({''value'': val})
						});
					}
					else {
						data[placeholder][field] = [this.params.customfields[field]];
					}
				}
				else {
					data[placeholder][field] = this.params.customfields[field];
				}
			}
		});
	}
	else {
		Logger.log(Logger.WARN, ''No customfields found.'');
	}

	return data;
}

function parseTags(event_tags) {
	try {
		return JSON.parse(event_tags);
	}
	catch (error) {
		if (this.params.event_source == 0 || this.params.event_source > 2) {
			Logger.log(Logger.WARN, ''No event tags were found. Should be an object. Received event tags: "'' + event_tags + ''".'');
		}
		return [];
	}
}

function getTagValue(tags, key, throw_on_fail) {
	for (i in tags) {
		if (tags[i].tag === key) {
			return tags[i].value
		}
	}

	if (!throw_on_fail) {
		return false;
	} else {
		throw ''Tag "'' + key + ''" is not in the event tag list.''
	}
};

function setLabels(event_tags_json) {
	var buffer = [];

	event_tags_json.forEach(function (tag) {
		if (typeof tag.tag !== ''undefined'' && typeof tag.value !== ''undefined'' && !tag.tag.startsWith(''__zbx'')) {
			label = (tag.tag + (tag.value ? ('':'' + tag.value) : '''')).replace(/\\s/g, ''_'');
			if (label.length < 256) {
				buffer.push(label);
			}
		}
	});

	if (buffer.length === 0) {
		Logger.log(Logger.WARN, ''No labels were set.'');
	}

	return buffer;
}

Jira.prototype.commentIssue = function (issue_key) {
	var data = {
		body: this.params.alert_message,
		public: this.params.issue_comments_public
	};
	this.sendRequest(''POST'', ''/rest/servicedeskapi/request/'' + encodeURIComponent(issue_key) + ''/comment'', data);
}

Jira.prototype.sendRequest = function (method, path, data) {
	this.request.clearHeader();
	this.request.addHeaders(''Authorization: Basic '' + btoa(this.params.jira_user + '':'' + this.params.jira_password));

	var response = this.request.jsonRequest(method, this.params.jira_url + path, data);

	if ((this.request.getStatus() !== 200 && this.request.getStatus() !== 201)) {
		Logger.log(Logger.WARN, ''HTTP code: '' + this.request.getStatus());
		if (CParamValidator.isType(response.description, ''string'')) {
			throw response.description;
		}
		else {
			Logger.log(Logger.WARN, ''Request not successful. Received response: '' + JSON.stringify(response));
			throw ''Unknown error. Check debug log for more information.'';
		}
	}

	return response;
}

Jira.prototype.onProblem = function (alert) {
	if (this.params.event_tags_json.length > 0 && getTagValue(this.params.event_tags_json, ''__zbx_jira_requestkey'', false) !== false) {
		return this.onUpdate(alert, true);
	}

	var data = {
		serviceDeskId: this.params.jira_servicedesk_id,
		requestTypeId: this.params.jira_request_type_id,
		requestFieldValues: {
			summary: this.params.alert_subject,
			description: this.params.alert_message,
			priority: {
				"name": this.priority
			}
		}
	};

	if (this.labels && this.labels.length > 0) {
		data.requestFieldValues.labels = this.labels;
	}

	if (this.params.components && this.params.components.length > 0) {
		data.requestFieldValues.components = this.params.components;
	}

	var response = this.sendRequest(''POST'', ''/rest/servicedeskapi/request'', this.addCustomFields(data, true));
	this.result.tags = {
		__zbx_jira_requestkey: response.issueKey,
		__zbx_jira_requestlink: this.params.jira_url + ''/browse/'' + response.issueKey
	}
	return this.result;
}

Jira.prototype.onUpdate = function (alert, dontUpdatePriority) {
	var data = {
		fields: {
			summary: this.params.alert_subject,
			priority: {
				"name": this.priority
			}
		}
	};

	if (dontUpdatePriority && this.params.event_source != 0) {
		delete data[''fields''][''priority''];
	}

	var jira_issue_key = getTagValue(this.params.event_tags_json, ''__zbx_jira_requestkey'', true);
	this.sendRequest(''PUT'', ''/rest/api/latest/issue/'' + encodeURIComponent(jira_issue_key) + ''?returnIssue=true'', this.addCustomFields(data));
	this.commentIssue(jira_issue_key);

	return this.result;
}

Jira.prototype.onResolve = function (alert) {
	return this.onUpdate(alert, true);
}

Jira.prototype.onDiscovery = function (alert) {
	this.priority = this.params.jira_priority_discovery;
	return this.onProblem(alert);
}

Jira.prototype.onAutoreg = function (alert) {
	this.priority = this.params.jira_priority_autoregistration;
	return this.onProblem(alert);
}

try {
	var hook = new Jira(value);
	hook.request = new CHttpRequest(Logger);
	return hook.run();
}
catch (error) {
	Logger.log(Logger.WARN, ''notification failed: '' + error);
	throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_jira_requestlink}','Jira Service Management: {EVENT.TAGS.__zbx_jira_requestkey}','https://git.zabbix.com/projects/ZBX/repos/zabbix/browse/templates/media/jira_service_management

1. To make this integration work, you will need the following from Jira:
  * Jira instance URL;
  * Jira service desk id and request type id that the issues created by Zabbix will use;
  * Jira username and password or API token. API token is strongly recommended and can be obtained at https://id.atlassian.com/manage/api-tokens.
2. In the Zabbix web interface, go to Administration → Macros section. Set up the global macro "{$ZABBIX.URL}" which will contain the URL to the Zabbix frontend.
3. On this page replace the placeholder ''<...>'' values with the ones from the step #1.
4. In Zabbix, you need to have a Zabbix user and add Media with the Jira Service Management media type. Make sure this user has access to all hosts for which you would like problem issues to be created in Jira.','0'),
('80','4','Line','','','','','','','','25','0','0','0','0','1','3','10s','1',E'function sendMessage(to, messages, params) {
    var response,
        request = new HttpRequest();

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

    request.addHeader(''Content-Type: application/json'');
    request.addHeader(''Authorization: Bearer '' + params.bot_token);

    response = request.post(''https://api.line.me/v2/bot/message/push'', JSON.stringify({
        "to": to,
        "messages": messages
    }));

    if (request.getStatus() !== 200) {
        throw response;
    }
}

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

    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'';
    }

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

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

    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 (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'';
    }
}

function getZabbixProblemLink(params) {
    return params.zabbix_url + (params.zabbix_url.endsWith(''/'') ? '''' : ''/'') + ''tr_events.php?triggerid='' + params.trigger_id + ''&eventid='' + params.event_id;    
}

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

    validateParams(params);

    severity_emoji = [
        "\\u26AA",
        "\\uD83D\\uDD35",
        "\\uD83D\\uDFE4",
        "\\uD83D\\uDFE1",
        "\\uD83D\\uDFE0",
        "\\uD83D\\uDD34",
    ];

    // If its a trigger and a recovery operation and not an update operation.
    if (params.event_source === ''0'' && params.event_value === ''0'' && params.event_update_status === ''0'') {
        var line_message = [
            {
                "type": "text",
                "text": ''\\u2705 '' + params.alert_subject + ''\\n\\n'' + params.alert_message + ''\\n'' + params.trigger_description + ''\\n\\n'' + getZabbixProblemLink(params)
            }
        ];
    }
    // If its a trigger and its a problem.
    else if (params.event_source === ''0'' && params.event_value === ''1'') {
        var line_message = [
            {
                "type": "text",
                "text": severity_emoji[params.event_nseverity] + '' '' + params.alert_subject + ''\\n\\n'' + params.alert_message + ''\\n'' + params.trigger_description + ''\\n\\n'' + getZabbixProblemLink(params)
            }
        ];
    }
    // If its a trigger and its an update operation.
    else if (params.event_source === ''0'' && params.event_update_status === ''1'') {
        var line_message = [
            {
                "type": "text",
                "text": ''\\u2733 '' + severity_emoji[params.event_nseverity] + '' '' + params.alert_subject + ''\\n\\n'' + params.alert_message + ''\\n'' + params.trigger_description + ''\\n\\n'' + getZabbixProblemLink(params)
            }
        ];
    }
    else {
        var line_message = [
            {
                "type": "text",
                "text": params.alert_subject + ''\\n\\n'' + params.alert_message + ''\\n''
            }
        ];
    }

    sendMessage(params.send_to, line_message, params);

    return ''OK'';
}
catch (err) {
    Zabbix.log(4, ''[ Line Webhook ] Line notification failed : '' + err);
    throw ''Line notification failed : '' + err;
}','30s','0','0','','','Please refer to setup guide here: https://git.zabbix.com/projects/ZBX/repos/zabbix/browse/templates/media/line

Set bot_token parameter to your Line bot token.
When assigning Line media to the Zabbix user - add the ID of the target recipient. Use a userId, groupId, or roomId value.','0'),
('81','4','ManageEngine ServiceDesk','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var MEngine = {
    params: {},

    setParams: function (params) {
        if (typeof params !== ''object'') {
            return;
        }

        MEngine.params = params;
        if (typeof MEngine.params.url === ''string'') {
            if (!MEngine.params.url.endsWith(''/'')) {
                MEngine.params.url += ''/'';
            }

            MEngine.params.url += ''api/v3/'';
        }

        if (MEngine.params.on_premise.toLowerCase() !== ''true''
                && typeof MEngine.params.on_demand_url_auth === ''string'') {
            if (!MEngine.params.on_demand_url_auth.endsWith(''/'')) {
                MEngine.params.on_demand_url_auth += ''/'';
            }

            MEngine.params.on_demand_url_auth += ''oauth/v2/token?'';
        }
    },

    setProxy: function (HTTPProxy) {
        MEngine.HTTPProxy = HTTPProxy;
    },

    createLink: function (id, url) {
        return url + (url.endsWith(''/'') ? '''' : ''/'') +
            ((MEngine.params.on_premise.toLowerCase() === ''true'')
                ? (''WorkOrder.do?woMode=viewWO&woID='' + id)
                : (''app/itdesk/ui/requests/'' + id + ''/details'')
            );
    },

    refreshAccessToken: function () {
        [
            ''on_demand_url_auth'',
            ''on_demand_refresh_token'',
            ''on_demand_client_id'',
            ''on_demand_client_secret''
        ].forEach(function (field) {
            if (typeof MEngine.params !== ''object'' || typeof MEngine.params[field] === ''undefined''
                    || MEngine.params[field].trim() === '''' ) {
                throw ''Required MEngine param is not set: "sd_'' + field + ''".'';
            }
        });

        var response,
            request = new HttpRequest(),
            url = MEngine.params.on_demand_url_auth +
                ''refresh_token='' + encodeURIComponent(MEngine.params.on_demand_refresh_token) +
                ''&grant_type=refresh_token&client_id='' + encodeURIComponent(MEngine.params.on_demand_client_id) +
                ''&client_secret='' + encodeURIComponent(MEngine.params.on_demand_client_secret) +
                ''&redirect_uri=https://www.zoho.com&scope=SDPOnDemand.requests.ALL'';

        if (MEngine.HTTPProxy) {
            request.setProxy(MEngine.HTTPProxy);
        }

        Zabbix.log(4, ''[ ManageEngine Webhook ] Refreshing access token. Request: '' + url);

        response = request.post(url);

        Zabbix.log(4, ''[ ManageEngine Webhook ] Received response with status code '' +
        request.getStatus() + ''\\n'' + response);

        try {
            response = JSON.parse(response);
        }
        catch (error) {
            Zabbix.log(4, ''[ ManageEngine Webhook ] Failed to parse response received from Zoho Accounts'');
        }

        if ((request.getStatus() < 200 || request.getStatus() >= 300) && !response.access_token) {
            throw ''Access token refresh failed with HTTP status code '' + request.getStatus() +
                ''. Check debug log for more information.'';
        }
        else {
            MEngine.params.on_demand_auth_token = response.access_token;
        }
    },

    request: function (method, query, data) {
        var response,
            url = MEngine.params.url + query,
            input,
            request = new HttpRequest(),
            message;

        if (MEngine.params.on_premise.toLowerCase() === ''true'') {
            request.addHeader(''TECHNICIAN_KEY: '' + MEngine.params.on_premise_auth_token);
        }
        else {
            request.addHeader(''Authorization: Zoho-oauthtoken '' + MEngine.params.on_demand_auth_token);
            request.addHeader(''Accept: application/v3+json'');
        }

        if (MEngine.HTTPProxy) {
            request.setProxy(MEngine.HTTPProxy);
        }

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        input = ''input_data='' + encodeURIComponent(data);
        Zabbix.log(4, ''[ ManageEngine Webhook ] Sending request: '' + url + ''?'' + input);

        switch (method) {
            case ''post'':
                response = request.post(url, input);
                break;

            case ''put'':
                response = request.put(url, input);
                break;

            default:
                throw ''Unsupported HTTP request method: '' + method;
        }

        Zabbix.log(4, ''[ ManageEngine Webhook ] Received response with status code '' +
            request.getStatus() + ''\\n'' + response);

        try {
            response = JSON.parse(response);
        }
        catch (error) {
            Zabbix.log(4, ''[ ManageEngine Webhook ] Failed to parse response received from ManageEngine'');
        }

        if ((request.getStatus() < 200 || request.getStatus() >= 300)
                && typeof response.response_status !== ''object'') {
            throw ''Request failed with HTTP status code '' + request.getStatus() +
                ''. Check debug log for more information.'';
        }
        else if (typeof response.response_status === ''object'' && response.response_status.status === ''failed'') {
            message = ''Request failed with status_code '';

            if (typeof response.response_status.messages === ''object''
                    && response.response_status.messages[0]
                    && response.response_status.messages[0].message) {
                message += response.response_status.messages[0].status_code +
                    ''. Message: '' + response.response_status.messages[0].message;
            }
            else {
                message += response.response_status.status_code;
            }

            message += ''. Check debug log for more information.'';
            throw message;
        }
        else if (response.request) {
            return response.request.id;
        }
    },

    createPaylaod: function (fields, isNote) {
        var data = {},
            result;

        if (isNote) {
            data.description = fields[''field_string:description''].replace(/(?:\\r\\n|\\r|\\n)/g, ''<br>'');
            result = {request_note: data};
        }
        else {
            Object.keys(fields)
                .forEach(function(field) {
                    if (fields[field].trim() === '''') {
                        Zabbix.log(4, ''[ ManageEngine Webhook ] Field "'' + field +
                            ''" can\\''t be empty. The field ignored.'');
                    }
                    else {
                        try {
                            var prefix = field.split('':'')[0],
                                root;

                            if  (prefix.startsWith(''udf_'') && !data.udf_fields) {
                                data.udf_fields = {};
                                root = data.udf_fields;
                            }
                            else if (prefix.startsWith(''udf_'')) {
                                root = data.udf_fields;
                            }
                            else {
                                root = data;
                            }

                            if (prefix.endsWith(''string'')) {
                                root[field.substring(field.indexOf('':'') + 1)
                                    .toLowerCase()] = fields[field];
                            }
                            else {
                                root[field.substring(field.indexOf('':'') + 1)
                                    .toLowerCase()] = {
                                    name: fields[field]
                                };
                            }
                        }
                        catch (error) {
                            Zabbix.log(4, ''[ ManageEngine Webhook ] Can\\''t parse field "'' + field +
                                ''". The field ignored.'');
                        }
                    }
                });
            if (data.description) {
                data.description = data.description.replace(/(?:\\r\\n|\\r|\\n)/g, ''<br>'');
            }

            result = {request: data};
        }

        return result;
    }
};

try {
    var params = JSON.parse(value),
        fields = {},
        sd = {},
        result = {tags: {}},
        required_params = [
            ''sd_on_premise'', ''field_string:subject'', ''field_string:description'',
            ''event_recovery_value'', ''event_source'', ''event_value'', ''event_update_status''
        ],
        severities = [
            {name: ''not_classified'', color: ''#97AAB3''},
            {name: ''information'', color: ''#7499FF''},
            {name: ''warning'', color: ''#FFC859''},
            {name: ''average'', color: ''#FFA059''},
            {name: ''high'', color: ''#E97659''},
            {name: ''disaster'', color: ''#E45959''},
            {name: ''default'', color: ''#000000''}
        ];

    Object.keys(params)
        .forEach(function (key) {
            if (key.startsWith(''sd_'')) {
                sd[key.substring(3)] = params[key];
            }
            else if (key.startsWith(''field_'') || key.startsWith(''udf_field_'')) {
                fields[key] = params[key];
            }

            if (required_params.indexOf(key) !== -1 && params[key].trim() === '''') {
                throw ''Parameter "'' + key + ''" can\\''t be empty.'';
            }
        });

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

    // 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.'';
    }

    if (params.event_source !== ''0'' && params.event_recovery_value === ''0'') {
        throw ''Recovery operations are supported only for trigger-based actions.'';
    }

    if ([0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity)) === -1) {
        params.event_nseverity = ''6'';
    }

    if (params.event_update_status === ''1'' && (typeof params.sd_request_id === ''undefined''
            || params.sd_request_id.trim() === ''''
            || params.sd_request_id === ''{EVENT.TAGS.__zbx_sd_request_id}''
            || params.sd_request_id === ''*UNKNOWN*'')) {
        throw ''Parameter "sd_request_id" can\\''t be empty for update operation.'';
    }

    MEngine.setParams(sd);
    MEngine.setProxy(params.HTTPProxy);

    if (MEngine.params.on_premise.toLowerCase() !== ''true'') {
        MEngine.refreshAccessToken();
    }

    // Create issue for non trigger-based events.
    if (params.event_source !== ''0'' && params.event_recovery_value !== ''0'') {
        fields[''field_object:priority''] = params[''priority_'' + severities[params.event_nseverity].name]
        || ''Normal'';

        MEngine.request(''post'', ''requests'', MEngine.createPaylaod(fields));
    }
    // Create issue for trigger-based events.
    else if (params.event_value === ''1'' && params.event_update_status === ''0'') {
        fields[''field_object:priority''] = params[''priority_'' + severities[params.event_nseverity].name]
        || ''Normal'';

        var id = MEngine.request(''post'', ''requests'', MEngine.createPaylaod(fields));

        result.tags.__zbx_sd_request_id = id;
        result.tags.__zbx_sd_request_link = MEngine.createLink(id, params.sd_url);
    }
    // Update created issue for trigger-based event.
    else {
        if (params.event_update_status === ''1'') {
            MEngine.request(''post'', ''requests/'' + params.sd_request_id + ''/notes'',
                MEngine.createPaylaod(fields, true)
            );
        }
        delete fields[''field_string:description''];
        MEngine.request(''put'', ''requests/'' + params.sd_request_id, MEngine.createPaylaod(fields));
    }

    return JSON.stringify(result);
}
catch (error) {
    Zabbix.log(3, ''[ ManageEngine Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_sd_request_link}','ManageEngine: {EVENT.TAGS.__zbx_sd_request_id}','','0'),
('82','4','MantisBT','','','','','','','','25','0','0','0','0','1','1','10s','1',E'var Mantisbt = {
    params: {},

    setParams: function (params) {
        if (typeof params !== ''object'') {
            return;
        }
        Mantisbt.params = params;
    },

    setProxy: function (HTTPProxy) {
        Mantisbt.HTTPProxy = HTTPProxy;
    },

    checkUrlFormat: function (url) {
        if (typeof url === ''string'' && !url.endsWith(''/'')) {
            url += ''/'';
        }

        if (url.indexOf(''http://'') === -1 && url.indexOf(''https://'') === -1) {
            url = ''https://'' + url;
        }

        return url;
    },

    getProblemURL: function (zabbix_url, triggerid, eventid, event_source) {
        var problem_url = zabbix_url;

        if (event_source === ''0'') {
            problem_url += ''tr_events.php?triggerid='' + triggerid + ''&eventid='' + eventid;
        }

        return problem_url;
    },

    request: function (method, url, data) {
        [''token'', ''url'', ''category''].forEach(function (field) {
            if (typeof Mantisbt.params !== ''object'' || typeof Mantisbt.params[field] === ''undefined'' || Mantisbt.params[field] === '''') {
                throw ''Required MantisBT param is not set: "'' + field + ''".'';
            }
        });

        var response,
            request = new HttpRequest();

        request.addHeader(''Content-Type: application/json'');
        request.addHeader(''Authorization: '' + Mantisbt.params.token);

        if (typeof Mantisbt.HTTPProxy !== ''undefined'' && Mantisbt.HTTPProxy !== '''') {
            request.setProxy(Mantisbt.HTTPProxy);
        }

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        Zabbix.log(4, ''[ MantisBT Webhook ] Sending request: '' + url + ((typeof data === ''string'') ? (''\\n'' + data) : ''''));

        switch (method) {
            case ''post'':
                response = request.post(url, data);
                break;

            case ''patch'':
                response = request.patch(url, data);
                break;

            default:
                throw ''Unsupported HTTP request method: '' + method;
        }

        Zabbix.log(4, ''[ MantisBT Webhook ] Received response with status code '' + request.getStatus() + ''\\n'' + response);

        if (response !== null) {
            try {
                response = JSON.parse(response);
            }
            catch (error) {
                Zabbix.log(4, ''[ MantisBT Webhook ] Failed to parse the response received from MantisBT'');
                response = null;
            }
        }

        if (typeof response !== ''object'') {
            throw ''Failed to process the response received from MantisBT. Check debug log for more information.'';
        }

        if (request.getStatus() < 200 || request.getStatus() >= 300) {
            var message = ''Request failed with status code '' + request.getStatus();

            if (typeof response.message !== ''undefined'') {
                message += '': '' + response.message;
            }

            throw message;
        }

        return response;
    }
};

try {
    var params = JSON.parse(value),
        mantisbt = {},
        url = '''',
        data = {},
        result = { tags: {} },
        required_params = [
            ''alert_subject'', ''alert_message'', ''event_source'', ''event_value'', ''event_update_action'',
            ''event_update_status'', ''event_recovery_value'', ''event_nseverity'', ''event_tagsjson'',
            ''event_id'', ''trigger_id'', ''zabbix_url'', ''alert_sendto'',
            ''mantisbt_token'', ''mantisbt_url'', ''mantisbt_category'', ''mantisbt_issue_number'', ''mantisbt_use_zabbix_tags''
        ],
        method,
        severities = [''none'', ''low'', ''normal'', ''high'', ''urgent'', ''immediate''];


    Object.keys(params)
        .forEach(function (key) {
            if (key.startsWith(''mantisbt_'')) {
                mantisbt[key.substring(9)] = params[key];
            }
            else if (required_params.indexOf(key) !== -1 && params[key] === '''') {
                throw ''Parameter "'' + key + ''" cannot be empty.'';
            }
        });

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

    // 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.'';
    }

    if (params.event_source !== ''0'' && params.event_recovery_value === ''0'') {
        throw ''Recovery operations are supported for trigger-based actions only.'';
    }

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

    // Check for backslash in the end of url and schema.
    mantisbt.url = Mantisbt.checkUrlFormat(mantisbt.url);
    params.zabbix_url = Mantisbt.checkUrlFormat(params.zabbix_url);

    // In case of resolve event.
    if (params.event_source === ''0'' && params.event_value === ''0'') {
        method = "patch";
        url = mantisbt.url + ''api/rest/issues/'' + mantisbt.issue_number;
        data = {
            summary: params.alert_subject,
            status: {
                name: "resolved"
            }
        };
        if (/commented/.test(params.event_update_action)) {
            data.additional_information = params.event_update_message;
        }
        process_tags = false;
    }
    // In case of update event.
    else if (params.event_source === ''0'' && params.event_update_status === ''1'') {
        method = "patch";
        url = mantisbt.url + ''api/rest/issues/'' + mantisbt.issue_number;
        data = {
            status: {},
            priority: {
                name: severities[parseInt(params.event_nseverity, 10)]
            }
        };
        if (/commented/.test(params.event_update_action)) {
            data.additional_information = params.event_update_message;
        }
        if (/acknowledged/.test(params.event_update_action)) {
            data.status.name = "acknowledged";
        }
        if (/unacknowledged/.test(params.event_update_action)) {
            data.status.name = "new";
        }
        process_tags = false;
    }
    else {
        method = ''post'';
        url = mantisbt.url + ''api/rest/issues'';

        data = {
            summary: params.alert_subject,
            description: params.alert_message,
            project: { name: params.alert_sendto },
            category: { name: mantisbt.category },
            priority: {
                name: parseInt(params.event_nseverity, 10) ? severities[parseInt(params.event_nseverity, 10)] : "none"
            }
        };
        if (params.event_source === ''0'') {
            problem_url = Mantisbt.getProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source);
            data.description += ''\\n'' + problem_url;

            if (mantisbt.use_zabbix_tags === "true") {
                var alert_tags = JSON.parse(params.event_tagsjson);
                data.tags = alert_tags.map(function (t) { return { name: t.value ? (t.tag + '': '' + t.value) : t.tag }; });
            }
        }
        process_tags = true;
    }

    Mantisbt.setParams(mantisbt);
    Mantisbt.setProxy(params.HTTPProxy);

    var response = Mantisbt.request(method, url, data);

    if (process_tags) {
        result.tags.__zbx_mantisbt_issue_number = response.issue.id;
        result.tags.__zbx_mantisbt_link = mantisbt.url + ''view.php?id='' + response.issue.id;
    }

    Zabbix.log(4, ''[ MantisBT Webhook ] Result: '' + JSON.stringify(result));
    return JSON.stringify(result);
}
catch (error) {
    Zabbix.log(4, ''[ MantisBT Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_mantisbt_link}','MantisBT: Issue ID {EVENT.TAGS.__zbx_mantisbt_issue_number}','','0'),
('83','4','Mattermost','','','','','','','','25','0','0','0','0','1','1','10s','1',E'var SEVERITY_COLORS = [
    ''#97AAB3'', ''#7499FF'', ''#FFC859'',
    ''#FFA059'', ''#E97659'', ''#E45959''
];

var RESOLVE_COLOR = ''#009900'';

var SEND_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(mattermost_url, team_name, postid) {
    return ''{0}/{1}/pl/{2}''.format(
        mattermost_url.replace(/\\/+$/, ''''),
        team_name,
        postid
    );
}

function getChannel(send_to) {
    switch (true) {
        case /.+\\/#.+/.test(send_to):
            return getChannelByName(send_to);

        case /@.+/.test(send_to):
            return getDirectChannel(send_to);

        default:
            return getChannelByID(send_to);
    }
}

function getChannelByName(send_to) {
    var team_chan = send_to
        .trim()
        .split(''/#'');

    var resp = JSON.parse(req.get(
        Mattermost.channel_byname.format(team_chan[0], team_chan[1]),
        JSON.stringify(fields)
    )
    );

    if (req.getStatus() != 200) {
        throw ''[{0}] {1}''.format(resp.status_code, resp.message);
    }

    return resp;
}

function getDirectChannel(send_to) {
    Zabbix.log(5, ''[ Mattermost Webhook ] Call {0}({1})''.format(
        arguments.callee.name,
        JSON.stringify(arguments)
    ));

    var teamUser = send_to
            .trim()
            .split(''/@''),
        bot = getBotUser(),
        user = getUserByName(teamUser[1]);

    var resp = JSON.parse(req.post(
        Mattermost.direct_channel,
        JSON.stringify([bot.id, user.id])
    )
    );

    Zabbix.log(5, ''[ Mattermost Webhook ] Result {0}: {1}''.format(
        arguments.callee.name,
        JSON.stringify(resp)
    ));

    if (req.getStatus() != 201) {
        throw ''[{0}] {1}''.format(resp.status_code, resp.message);
    }

    resp.team_name = teamUser[0];

    return resp;
}

function getChannelByID(channelID) {
    Zabbix.log(5, ''[ Mattermost Webhook ] Call {0}({1})''.format(
        arguments.callee.name,
        JSON.stringify(arguments)
    ));

    var resp = JSON.parse(req.get(
        Mattermost.get_channel.format(channelID),
        JSON.stringify(fields)
    )
    );

    Zabbix.log(5, ''[ Mattermost Webhook ] Result {0}: {1}''.format(
        arguments.callee.name,
        JSON.stringify(resp)
    ));

    if (req.getStatus() != 200) {
        throw ''[{0}] {1}''.format(resp.status_code, resp.message);
    }

    return resp;
}

function getBotUser() {
    Zabbix.log(5, ''[ Mattermost Webhook ] Call {0}({1})''.format(
        arguments.callee.name,
        JSON.stringify(arguments)
    ));

    var resp = JSON.parse(req.get(
        Mattermost.bot_user,
        JSON.stringify(fields)
    )
    );

    Zabbix.log(5, ''[ Mattermost Webhook ] Result {0}: {1}''.format(
        arguments.callee.name,
        JSON.stringify(resp)
    ));

    if (req.getStatus() != 200) {
        throw ''[{0}] {1}''.format(resp.status_code, resp.message);
    }

    return resp;
}

function getUserByName(userName) {
    Zabbix.log(5, ''[ Mattermost Webhook ] Call {0}({1})''.format(
        arguments.callee.name,
        JSON.stringify(arguments)
    ));

    var resp = JSON.parse(req.get(
        Mattermost.user_byname.format(userName),
        JSON.stringify(fields)
    )
    );

    Zabbix.log(5, ''[ Mattermost Webhook ] Result {0}: {1}''.format(
        arguments.callee.name,
        JSON.stringify(resp)
    ));

    if (req.getStatus() != 200) {
        throw ''[{0}] {1}''.format(resp.status_code, resp.message);
    }

    return resp;
}

function getTeamByID(teamID) {
    Zabbix.log(5, ''[ Mattermost Webhook ] Call {0}({1})''.format(
        arguments.callee.name,
        JSON.stringify(arguments)
    ));

    var resp = JSON.parse(req.get(
        Mattermost.get_team.format(teamID),
        JSON.stringify(fields)
    )
    );

    Zabbix.log(5, ''[ Mattermost Webhook ] Result {0}: {1}''.format(
        arguments.callee.name,
        JSON.stringify(resp)
    ));

    if (req.getStatus() != 200) {
        throw ''[{0}] {1}''.format(resp.status_code, resp.message);
    }

    return resp;
}

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 getTagValue(event_tags, key) {
    var pattern = new RegExp(''('' + key + '':.+)'');
    var tagValue = event_tags
        .split('','')
        .filter(function (v) {
            return v.match(pattern);
        })
        .map(function (v) {
            return v.split('':'')[1];
        })[0]
        || 0;

    return tagValue;
}

function handlerAlarm(req, params) {
    var channel = getChannel(params.send_to);
    var fields = {
        channel_id: channel.id,
        props: {}
    };

    if (isEventProblem(params)) {
        var team_name = channel.team_name
            ? channel.team_name
            : getTeamByID(channel.team_id).name;

        fields.props.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(
            Mattermost.post_message,
            JSON.stringify(fields)
        )
        );

        if (req.getStatus() != 201) {
            throw ''[{0}] {1}''.format(resp.status_code, resp.message);
        }

        result.tags.__mattermost_post_id = resp.id;
        result.tags.__mattermost_channel_id = channel.id;
        result.tags.__mattermost_channel_name = channel.name;
        result.tags.__mattermost_message_link = getPermalink(
            params.mattermost_url,
            team_name,
            resp.id
        );

    }
    else if (isEventUpdate(params)) {
        fields.root_id = getTagValue(params.event_tags, ''mattermost_post_id'');

        if (params.event_source === ''0'') {}
        fields.props.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(
            Mattermost.post_message, JSON.stringify(fields)
        )
        );

        if (req.getStatus() != 201) {
            throw ''[{0}] {1}''.format(resp.status_code, resp.message);
        }

    }
    else if (isEventResolve(params)) {
        fields.channel_id = getTagValue(params.event_tags, ''mattermost_channel_id'');
        fields.id = getTagValue(params.event_tags, ''mattermost_post_id'');
        fields.props.attachments = [
            createMessage(
                RESOLVE_COLOR,
                params.event_date,
                params.event_time,
                createProblemURL(params.zabbix_url, params.trigger_id, params.event_id, params.event_source)
            )
        ];

        var post_id = getTagValue(params.event_tags, ''mattermost_post_id'');

        resp = JSON.parse(req.put(
            Mattermost.chat_update.format(post_id),
            JSON.stringify(fields)
        )
        );

        if (req.getStatus() != 200) {
            throw ''[{0}] {1}''.format(resp.status_code, resp.message);
        }
    }
}

function handlerEvent(req, params) {
    var channel = getChannel(params.send_to);
    var fields = {
        channel_id: channel.id,
        props: {}
    };

    if (isEventProblem(params)) {
        var team_name = channel.team_name
            ? channel.team_name
            : getTeamByID(channel.team_id).name;

        fields.props.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(Mattermost.post_message, JSON.stringify(fields)));

        if (req.getStatus() != 201) {
            throw ''[{0}] {1}''.format(resp.status_code, resp.message);
        }

        result.tags.__mattermost_channel_name = channel.name;
        result.tags.__mattermost_message_link = getPermalink(
            params.mattermost_url,
            team_name,
            resp.id
        );

    }
    else if (isEventUpdate(params)) {
        fields.props.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(Mattermost.post_message, JSON.stringify(fields)));

        if (req.getStatus() != 201) {
            throw ''[{0}] {1}''.format(resp.status_code, resp.message);
        }

    }
    else if (isEventResolve(params)) {
        fields.props.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(Mattermost.post_message, JSON.stringify(fields)));

        if (req.getStatus() != 201) {
            throw ''[{0}] {1}''.format(resp.status_code, resp.message);
        }
    }
}

function createMessage(
    event_severity_color,
    event_date,
    event_time,
    problem_url,
    isShort
) {
    var message = {
        fallbac: params.alert_subject,
        title: params.alert_subject,
        color: event_severity_color,
        title_link: problem_url,
        footer: problem_url,

        fields: [
            {
                title: ''Host'',
                value: ''{0} [{1}]''.format(params.host_name, params.host_ip),
                short: true
            },
            {
                title: ''Event time'',
                value: ''{0} {1}''.format(event_date, event_time),
                short: true
            }
        ],
    };

    
    if (params.event_source === ''0'') {
        message.fields.push(
            {
                title: ''Severity'',
                value: params.event_severity,
                short: true
            },
            {
                title: ''Opdata'',
                value: params.event_opdata,
                short: true
            }
        );
    }

    if (!isShort && params.event_source === ''0'') {
        message.fields.push(
            {
                title: ''Event tags'',
                value: ''`{0}`''.format(params.event_tags.replace(/__.+?:(.+?,|.+)/g, '''') || ''None''),
                short: true
            },
            {
                title: ''Trigger description'',
                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
            }
        );
    }

    return message;
}

function validateParams(params) {
    if (typeof params.bot_token !== ''string'' || params.bot_token.trim() === '''') {
        throw ''Field "bot_token" 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.send_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)) === -1) {
        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_ip !== ''string'' || params.host_ip.trim() === '''') {
        throw ''Field "host_ip" cannot be empty'';
    }

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

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

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

    if ([''alarm'', ''event''].indexOf(params.send_mode) === -1) {
        throw ''Incorrect "send_mode" parameter given: '' + params.send_mode + ''\\nMust be "alarm" or "event".'';
    }

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

    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 HttpRequest(),
        fields = {},
        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);

    params.mattermost_url = params.mattermost_url.replace(/\\/+$/, '''');
    params.zabbix_url = params.zabbix_url.replace(/\\/+$/, '''');

    var APIEndpoint = params.mattermost_url + ''/api/v4/'';

    var Mattermost = {
        post_message: APIEndpoint + ''posts'',
        get_channel: APIEndpoint + ''channels/{0}'',
        get_team: APIEndpoint + ''teams/{0}'',
        chat_update: APIEndpoint + ''posts/{0}'',
        direct_channel: APIEndpoint + ''channels/direct'',
        channel_byname: APIEndpoint + ''teams/name/{0}/channels/name/{1}'',
        user_byname: APIEndpoint + ''users/username/{0}'',
        bot_user: APIEndpoint + ''users/me''

    };

    params.send_mode = params.send_mode.toLowerCase();
    params.send_mode = params.send_mode in SEND_MODE_HANDLERS
        ? params.send_mode
        : ''alarm'';

    SEND_MODE_HANDLERS[params.send_mode](req, params);

    if (params.event_source === ''0'') {
        return JSON.stringify(result);
    }
    else {
        return ''OK'';
    }
}
catch (error) {
    Zabbix.log(4, ''[ Mattermost Webhook ] Mattermost notification failed: '' + error);
    throw ''Mattermost notification failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__mattermost_message_link}','Open in Mattermost: {EVENT.TAGS.__mattermost_channel_name}','','0'),
('84','4','MS Teams','','','','','','','','25','0','0','0','0','1','3','10s','1',E'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 CWebhookHelper = {

	createProblemURL: function(event_source, zabbix_url, trigger_id, event_id) {
		if (event_source === ''0'') {
			return zabbix_url + ''/tr_events.php?triggerid='' + trigger_id + ''&eventid='' + event_id;
		} else if (event_source === ''4'') {
			return zabbix_url + ''/zabbix.php?action=service.list'';
		}

		return zabbix_url;
	},

};

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 SEVERITY_COLORS = [
	''#97AAB3'',
	''#7499FF'',
	''#FFC859'',
	''#FFA059'',
	''#E97659'',
	''#E45959'',
	''#009900'',
	''#1F1F1F''
],
	serviceLogName = ''MS Teams Webhook'',
	Logger = new CLogger(serviceLogName),
	MSTeams = CWebhook;

MSTeams.prototype.addBodyFact = function (name, value) {
	if (!CParamValidator.isDefined(this.data.sections[0].facts)) {
		this.data.sections[0].facts = [];
	}
	this.data.sections[0].facts.push({name: name, value: value});
}

MSTeams.prototype.onCheckParams = function () {
	CParamValidator.validate({teams_endpoint: {type: ''string''}, event_source: {type: ''string''}, alert_subject: {type: ''string''},
		alert_message: {type: ''string''}, host_ip: {type: ''string'', default: ''''}, zabbix_url: {type: ''string'', url: true},
		use_default_message: {type: ''boolean'', default: false}, event_nseverity: {type: ''integer'', default: 7}}, this.params);
	var actionName = ''Zabbix Home'';
	if (!CParamValidator.inArray(this.params.event_source, [''0'',''3'',''4''])
		|| this.params.alert_message.startsWith("NOTE: Escalation canceled:")) {
		this.params[''use_default_message''] = true;
	}
	if (this.params.event_source === ''0'') {
		CParamValidator.validate({event_id: {type: ''integer''}, trigger_id: {type: ''integer''}}, this.params);
		actionName = ''Event Info'';
	}
	var actionURL = CWebhookHelper.createProblemURL(this.params.event_source, this.params.zabbix_url, this.params.trigger_id, this.params.event_id);
	if (this.params.event_value === ''0'') {
		this.params.event_nseverity = 6;
	}
	this.data = {
		themeColor: SEVERITY_COLORS[this.params.event_nseverity].replace(''#'', ''''),
		summary: this.params.alert_subject,
		sections: [{
			markdown: ''false'',
			activityTitle: this.params.alert_subject,
			text: (this.params.event_source === ''0'' && !this.params.use_default_message) ? this.params.trigger_description : this.params.alert_message
		}],
		potentialAction: [{
			''@type'': ''OpenUri'',
			name: actionName,
			targets: [{
				os: ''default'',
				uri: actionURL
			}]
		}]
	};
};

MSTeams.prototype.makeUpDefaultMessage = function () {
	if (!this.params.use_default_message) {
		if (!CParamValidator.isEmpty(this.params.host_name) && CParamValidator.isMacroSet(this.params.host_name, ''HOST.NAME'')) {
			this.addBodyFact(''Host'', this.params.host_name + ((!CParamValidator.isEmpty(this.params.host_ip)) ? '' ['' + this.params.host_ip + '']'' : ''''));
		}
		if (!CParamValidator.isEmpty(this.params.event_severity) && CParamValidator.isMacroSet(this.params.event_severity, ''EVENT.SEVERITY'')) {
			this.addBodyFact(''Severity'', this.params.event_severity);
		}
		if (!CParamValidator.isEmpty(this.params.event_opdata) && CParamValidator.isMacroSet(this.params.event_opdata, ''EVENT.OPDATA'')) {
			this.addBodyFact(''Operational data'', this.params.event_opdata);
		}
		if (!CParamValidator.isEmpty(this.params.event_tags) && CParamValidator.isMacroSet(this.params.event_tags, ''EVENT.TAGS'')) {
			this.addBodyFact(''Event tags'', this.params.event_tags);
		}

		Object.keys(this.params).forEach(function (key) {
			if (key.startsWith(''fact_'') && this.params[key] !== '''') {
				this.addBodyFact(key.substring(5), this.params[key]);
			}
			else if (key.startsWith(''openUri_'') && this.params[key] !== '''' && !this.params[key].startsWith(''{'')) {
				this.data.potentialAction.push({
					''@type'': ''OpenUri'',
					name: key.substring(8),
					targets: [{
						os: ''default'',
						uri: this.params[key]
					}]
				});
			}
		});
	}
}

const htmlMultiline = function (text) {
	return text.replace(/(?:\\r\\n|\\r|\\n)/g, ''<br>'');
}

MSTeams.prototype.sendRequest = function () {
	this.request.addHeaders({"Content-Type": "application/json"});
	var response = this.request.plainRequest(''POST'', this.params.teams_endpoint, JSON.stringify(this.data));

	if (response !== ''1'') {
		Logger.log(Logger.WARN, ''FAILED with response: '' + response);
		throw response;
	}
	return ''OK'';
};

MSTeams.prototype.onProblem = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	if (!this.params.use_default_message) {
		this.addBodyFact(''Event time'', this.params.event_time + '' '' + this.params.event_date);
		this.makeUpDefaultMessage();
	}
	this.data.sections[0].text = htmlMultiline(this.data.sections[0].text);

	return this.sendRequest();
}

MSTeams.prototype.onUpdate = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	if (!this.params.use_default_message) {
		this.data.sections[0].text = this.params.event_update_user + '' '' + this.params.event_update_action + ''.'';
		if (this.params.event_update_message) {
			this.data.sections[0].text += ''<br>Message:<br>'' + this.params.event_update_message;
		}
		this.addBodyFact(''Event update time'', this.params.event_update_time + '' '' + this.params.event_update_date);
		this.makeUpDefaultMessage();
	}
	this.data.sections[0].text = htmlMultiline(this.data.sections[0].text);

	return this.sendRequest();
}

MSTeams.prototype.onResolve = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	if (!this.params.use_default_message) {
		this.addBodyFact(''Recovery time'', this.params.event_recovery_time + '' '' + this.params.event_recovery_date);
		this.makeUpDefaultMessage();
	}
	this.data.sections[0].text = htmlMultiline(this.data.sections[0].text);

	return this.sendRequest();
}

MSTeams.prototype.onDiscovery = function (alert) {
	return this.onProblem(alert);
}

MSTeams.prototype.onAutoreg = function (alert) {
	return this.onProblem(alert);
}

try {
	var hook = new MSTeams(value);
	hook.request = new CHttpRequest(Logger);
	return hook.run();
}
catch (error) {
	Logger.log(Logger.WARN, ''notification failed: '' + error);
	throw ''Sending failed: '' + error;
}','30s','0','0','','','https://git.zabbix.com/projects/ZBX/repos/zabbix/browse/templates/media/msteams

1. Add official Zabbix webhook connector from MS Teams apps for the channel, where you want to receive notifications.
2. Create Incoming webhook for your channel.
3. In the Zabbix web interface go to Administration → Macros section. Setup the global macro "{$ZABBIX.URL}" which will contain the URL to the Zabbix frontend. 
4. On this page replace placeholder <PLACE WEBHOOK URL HERE> with the incoming webhook URL, created during the webhook setup in MS Teams.
5. To receive Zabbix notifications in MS Teams, you need to create a Zabbix user and add Media with the MS Teams media type. Make sure this user has access to all hosts for which you would like problem notifications to be sent to MS Teams.','0'),
('85','4','MS Teams Workflow','','','','','','','','25','0','0','0','0','1','3','10s','1',E'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();
	}
}

const CWebhookHelper = {

	createProblemURL: function(event_source, zabbix_url, trigger_id, event_id) {
		if (event_source === ''0'') {
			return zabbix_url + ''/tr_events.php?triggerid='' + trigger_id + ''&eventid='' + event_id;
		} else if (event_source === ''4'') {
			return zabbix_url + ''/zabbix.php?action=service.list'';
		}

		return zabbix_url;
	},

};


var serviceLogName = ''MS Teams Webhook'',
	Logger = new CLogger(serviceLogName),
	MSTeams = CWebhook;

MSTeams.prototype.onCheckParams = function () {
	CParamValidator.validate({
		alert_subject: { type: ''string'' }, alert_message: { type: ''string'' },
		zabbix_url: { type: ''string'', url: true }, teams_endpoint: { type: ''string'', url: true }
	}, this.params);

	if (this.params.event_source === ''0'') {
		CParamValidator.validate({ event_id: { type: ''integer'' }, trigger_id: { type: ''integer'' } }, this.params);
	}

	this.body = {
		type: "message",
		attachments: [
			{
				contentType: "application/vnd.microsoft.card.adaptive",
				contentUrl: null,
				content: {
					$schema: "http://adaptivecards.io/schemas/adaptive-card.json",
					type: "AdaptiveCard",
					version: "1.4",
					body: [
						{
							type: "Container",
							items: [
								{
									type: "TextBlock",
									size: "Medium",
									wrap: "true",
									weight: "Bolder",
									text: this.params.alert_subject
								}
							],
							style: "",
							bleed: true
						}
					],
					actions: [
						{
							type: "Action.OpenUrl",
							title: "Event info",
							url: CWebhookHelper.createProblemURL(this.params.event_source, this.params.zabbix_url, this.params.trigger_id, this.params.event_id)
						}
					]
				}
			}
		]
	};

	this.params.alert_message = this.params.alert_message.split(''\\n'');

	for (line in this.params.alert_message) {
		this.body.attachments[0].content.body.push({
			type: "TextBlock",
			wrap: "true",
			text: this.params.alert_message[line]
		});
	}

};

MSTeams.prototype.sendRequest = function (color) {
	this.body.attachments[0].content.body[0].style = color;

	this.request.addHeaders({ "Content-Type": "application/json" });
	var response = this.request.plainRequest(''POST'', this.params.teams_endpoint, JSON.stringify(this.body));

	var status = this.request.getStatus();
	if (status < 200 || status >= 300) {
		Logger.log(Logger.INFO, ''HTTP code: '' + status + ''. Endpoint response: '' + response);
		throw ''HTTP code: '' + status + ''. Endpoint response: '' + response;
	}

	return ''OK'';
};

MSTeams.prototype.onProblem = function () {
	return this.sendRequest("attention");
};

MSTeams.prototype.onResolve = function () {
	return this.sendRequest("good");
};

MSTeams.prototype.onUpdate = function () {
	return this.sendRequest("emphasis");
};

MSTeams.prototype.onDiscovery = function () {
	return this.sendRequest("emphasis");
};

MSTeams.prototype.onAutoreg = function () {
	return this.sendRequest("emphasis");
};

try {
	var hook = new MSTeams(value);
	hook.request = new CHttpRequest(Logger);
	return hook.run();
}
catch (error) {
	Logger.log(Logger.WARN, ''notification failed: '' + error);
	throw ''Sending failed: '' + error;
};','30s','0','0','','','To set up a webhook, please follow these steps:

- 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.
- Create a Zabbix user and add the MS Teams Workflow media to it.

For more detailed instructions, please visit https://git.zabbix.com/projects/ZBX/repos/zabbix/browse/templates/media/.','0'),
('86','4','Opsgenie','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var method,
    Media = {
    params: {},
    name: '''',
    labels: [],
    HTTPProxy: '''',

    setParams: function (params) {
        if (typeof params !== ''object'') {
            return;
        }

        Media.params = params;
        Media.params.api += Media.params.api.endsWith(''/'') ? '''' : ''/'';
        Media.params.web += Media.params.web.endsWith(''/'') ? '''' : ''/'';
    },

    setProxy: function (HTTPProxy) {
        if (typeof HTTPProxy !== ''undefined'' && HTTPProxy.trim() !== '''') {
            Media.HTTPProxy = HTTPProxy;
        }
    },

    setTags: function(event_tags_json) {
        if (typeof event_tags_json !== ''undefined'' && event_tags_json !== ''''
                && event_tags_json !== ''{EVENT.TAGSJSON}'') {

            try {
                var tags = JSON.parse(event_tags_json),
                    label;

                tags.forEach(function (tag) {
                    if (typeof tag.tag === ''string'') {
                        label = (tag.tag + (typeof tag.value !== ''undefined''
                                && tag.value !== '''' ? ('':'' + tag.value) : '''')).replace(/\\s/g, ''_'');
                        Media.labels.push(label);
                    }
                });
            }
            catch (error) {
                Zabbix.log(4, ''[ '' + Media.name + '' Webhook ] Failed to parse "event_tags_json" param'');
            }
        }
    },

    request: function (method, query, data, allow_404) {
        if (typeof(allow_404) === ''undefined'') {
            allow_404 = false;
        }

        [''api'', ''token''].forEach(function (field) {
            if (typeof Media.params !== ''object'' || typeof Media.params[field] === ''undefined''
                    || Media.params[field] === '''') {
                throw ''Required '' + Media.name + '' param is not set: "'' + field + ''".'';
            }
        });

        var response,
            url = Media.params.api + query,
            request = new HttpRequest();

        request.addHeader(''Content-Type: application/json'');
        request.addHeader(''Authorization: '' + Media.params.token);
        request.setProxy(Media.HTTPProxy);

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        Zabbix.log(4, ''[ '' + Media.name + '' Webhook ] Sending request: '' +
            url + ((typeof data === ''string'') ? (''\\n'' + data) : ''''));

        switch (method) {
            case ''get'':
                response = request.get(url, data);
                break;

            case ''post'':
                response = request.post(url, data);
                break;

            case ''put'':
                response = request.put(url, data);
                break;

            default:
                throw ''Unsupported HTTP request method: '' + method;
        }

        Zabbix.log(4, ''[ '' + Media.name + '' Webhook ] Received response with status code '' +
            request.getStatus() + ''\\n'' + response);

        if (response !== null) {
            try {
                response = JSON.parse(response);
            }
            catch (error) {
                Zabbix.log(4, ''[ '' + Media.name + '' Webhook ] Failed to parse response.'');
                response = null;
            }
        }

        if ((request.getStatus() < 200 || request.getStatus() >= 300)
                && (!allow_404 || request.getStatus() !== 404)) {
            var message = ''Request failed with status code '' + request.getStatus();

            if (response !== null) {
                if (typeof response.errors === ''object'' && Object.keys(response.errors).length > 0) {
                    message += '': '' + JSON.stringify(response.errors);
                }
                else if (typeof response.errorMessages === ''object'' && Object.keys(response.errorMessages).length > 0) {
                    message += '': '' + JSON.stringify(response.errorMessages);
                }
                else if (typeof response.message === ''string'') {
                    message += '': '' + response.message;
                }
            }

            throw message + '' Check debug log for more information.'';
        }

        return {
            status: request.getStatus(),
            response: response
        };
    },

    getAlertId: function (requestId) {
        status_counter = params.status_counter || 25; 
        do {
            resp = Media.request(''get'', ''requests/'' + requestId, undefined, true);
        status_counter -= 1;            
        }
        while ( status_counter > 0 && 
            ( 
            typeof resp.response !== ''object'' || 
            typeof resp.response.data === ''undefined'' ||
            resp.response.data.success === false &&
                !resp.response.data.status.includes("There is no open alert") &&
                !resp.response.data.status.includes("Alert is already")
            ) 
        );

        if (typeof resp.response !== ''object'' || typeof resp.response.data === ''undefined'') {
            throw ''Cannot get '' + Media.name + '' issue ID. Check debug log for more information.'';
        }
        else if (resp.response.data.success === false ) {
            throw Media.name + '': Operation status ('' + resp.response.data.status + '')'';
        }

        return resp;
    }
};

try {
    var result = {tags: {}},
        params = JSON.parse(value),
        media = {},
        fields = {},
        resp = {},
        responders = [],
        tags = [],
        required_params = [
            ''alert_subject'',
            ''alert_message'',
            ''event_id'',
            ''event_source'',
            ''event_value'',
            ''event_update_status'',
            ''opsgenie_api'',
            ''opsgenie_web'',
            ''opsgenie_token''
        ],
        severities = [
            ''not_classified'',
            ''information'',
            ''warning'',
            ''average'',
            ''high'',
            ''disaster'',
            ''resolved'',
            ''default''
        ],
        priority;

    Object.keys(params)
        .forEach(function (key) {
            if (required_params.indexOf(key) !== -1 && params[key].trim() === '''') {
                throw ''Parameter "'' + key + ''" cannot be empty.'';
            }
            if (key.startsWith(''opsgenie_'')) {
                media[key.substring(9)] = params[key];
            }
        });

    // Possible values of event_source:
    // 0 - Trigger, 1 - Discovery, 2 - Autoregistration, 3 - Internal.
    if ([0, 1, 2, 3].indexOf(parseInt(params.event_source)) === -1) {
        throw ''Incorrect "event_source" parameter given: "'' + params.event_source + ''".\\nMust be 0-3.'';
    }

    // Check event_value for trigger-based and internal events.
    // Possible values: 1 for problem, 0 for recovering
    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.
    // Possible values: 0 - Webhook was called because of problem/recovery event, 1 - Update operation.
    if (params.event_source === ''0'' && 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.'';
    }

    // Check event_id for a numeric value.
    if (isNaN(parseInt(params.event_id)) || params.event_id < 1) {
        throw ''Incorrect "event_id" parameter given: '' + params.event_id + ''\\nMust be a positive number.'';
    }

    if ((params.event_source === ''1'' || params.event_source === ''2'')  && params.event_value === ''0'') {
        throw ''Recovery operations are supported only for Trigger and Internal actions.'';
    }

    if ([0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity)) === -1) {
        params.event_nseverity = ''7'';
    }

    if (params.event_value === ''0'') {
        params.event_nseverity = ''6'';
    }

    priority = params[''severity_'' + severities[params.event_nseverity]];
    params.zbxurl = params.zbxurl + (params.zbxurl.endsWith(''/'') ? '''' : ''/'');

    Media.name = ''Opsgenie'';
    Media.setParams(media);
    Media.params.token = ''GenieKey '' + Media.params.token;
    Media.setProxy(params.HTTPProxy);
    Media.setTags(params.event_tags_json); // Set Media.labels

    // Create an issue.
    // Numeric value of the event that triggered an action (1 for problem, 0 for recovering).
    // Numeric value of the problem update status. Possible values:
    // 0 - Webhook was called because of problem/recovery event, 1 - Update operation.
    if ((params.event_source == 0 && params.event_value == 1 && params.event_update_status == 0)
            || (params.event_source == 3 && params.event_value == 1)
            || params.event_source == 1 || params.event_source == 2) {
        fields.message = params.alert_subject;
        fields.alias = params.event_id;
        fields.description = params.alert_message;
        fields.priority = priority;
        fields.source = ''Zabbix'';

        if (params.event_source === ''0'') {
            fields.details = {
                ''Zabbix server'': params.zbxurl,
                Problem: params.zbxurl + ''tr_events.php?triggerid='' + params.trigger_id + ''&eventid='' + params.event_id
            };
        }
        else {
            fields.details = {''Zabbix server'': params.zbxurl};
        }

        if (typeof params.opsgenie_teams === ''string'') {
            responders = params.opsgenie_teams.split('','');
            fields.responders = responders.map(function(team) {
                return {type: ''team'', name: team.trim()};
            });
        }

        fields.tags = Media.labels;
        if (typeof params.opsgenie_tags === ''string'') {
            tags = params.opsgenie_tags.split('','');
            tags.forEach(function(item) {
                fields.tags.push(item.trim());
            });
        }

        resp = Media.request(''post'', '''', fields);
        if (typeof resp.response !== ''object'' || typeof resp.response.result === ''undefined'') {
            throw ''Cannot create '' + Media.name + '' issue. Check debug log for more information.'';
        }

        if (resp.status === 202) {
            resp = Media.getAlertId(resp.response.requestId);
            if (params.event_source == 0 && params.event_value == 1 && params.event_update_status == 0) {
                result.tags.__zbx_ops_issuekey = resp.response.data.alertId;
                result.tags.__zbx_ops_issuelink = Media.params.web + ''alert/detail/'' + resp.response.data.alertId;
            }
        }
        else {
            throw Media.name + '' response code is unexpected. Check debug log for more information.'';
        }
    }
    // Update or close the created issue.
    else {
        fields.user = (params.event_value != 0) ? params.zbxuser : '''';
        fields.note = params.alert_message;
        if ( [0, 3].indexOf(parseInt(params.event_source)) > -1  && params.event_value == 0 ) {
            // skip sending of close request from update operation(mandatory when both update & recovery operations are defined in action)  
            method = params.event_update_status == 0 ? "close" : "skip";
        }
        else if ( params.event_source == 0 && params.event_value == 1 && params.event_update_status == 1 && params.event_update_action.includes(''acknowledged'')) {
            method = params.event_update_action.includes(''unacknowledged'') ? "unacknowledge" : "acknowledge";
        }
        else {
            method = "notes";
        }

        if (method !== "skip") {
        resp = Media.request(''post'', params.event_id + ''/'' + method +''?identifierType=alias'', fields);

        if (typeof resp.response !== ''object'' || typeof resp.response.result === ''undefined'') {
            throw ''Cannot update '' + Media.name + '' issue. Check debug log for more information.'';
        }

        if (resp.status === 202) {
            resp = Media.getAlertId(resp.response.requestId);
        }
        else {
            throw Media.name + '' response code is unexpected. Check debug log for more information.'';
        }
    }
    }
    return JSON.stringify(result);
}
catch (error) {
    Zabbix.log(3, ''[ '' + Media.name + '' Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_ops_issuelink}','Opsgenie: {EVENT.TAGS.__zbx_ops_issuekey}','Please refer to https://docs.opsgenie.com/docs/alert-api and https://www.zabbix.com/documentation/7.4/manual/config/notifications/media/webhook#example_scripts.
  
Set global macro {$ZABBIX.URL} with your Zabbix server URL.
Add dedicated user with media type "Opsgenie".
Change the values of the variables opsgenie_api (https://api.opsgenie.com/v2/alerts or https://api.eu.opsgenie.com/v2/alerts),
opsgenie_web (for example, https://myzabbix.app.opsgenie.com), opsgenie_token.','0'),
('87','4','OTRS CE','','','','','','','','25','0','0','0','0','1','3','10s','1',E'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 CWebhookHelper = {

	createProblemURL: function(event_source, zabbix_url, trigger_id, event_id) {
		if (event_source === ''0'') {
			return zabbix_url + ''/tr_events.php?triggerid='' + trigger_id + ''&eventid='' + event_id;
		} else if (event_source === ''4'') {
			return zabbix_url + ''/zabbix.php?action=service.list'';
		}

		return zabbix_url;
	},

};

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();
	}
}

const SEVERITIES = ["not_classified", "information", "warning", "average", "high", "disaster"],
	serviceLogName = '' ((OTRS)) CE Webhook '',
	Logger = new CLogger(serviceLogName),
	OTRS = CWebhook;

OTRS.prototype.onCheckParams = function () {
	CParamValidator.validate({alert_subject: {type: ''string''}, alert_message: {type: ''string''},
		event_nseverity: {type: ''integer'', default: -1}, otrs_url: {type: ''string'', url: true}, otrs_auth_user: {type: ''string''},
		otrs_auth_password: {type: ''string''}, otrs_customer: {type: ''string''}, otrs_default_priority_id: {type: ''integer'', min: 1, max: 5},
		otrs_queue: {type: ''string''}, otrs_ticket_type: {type: ''string''}, otrs_ticket_state: {type: ''string''}, otrs_time_unit: {type: ''integer''},
		otrs_closed_state_id: {type: ''integer'', default: 0}, zabbix_url: {type: ''string'', url: true}}, this.params);
	this.params.entrypoint = ''/nph-genericinterface.pl/Webservice/ZabbixTicketConnector/Ticket'';

	var priority;
	if (this.params.event_nseverity >= 0 && this.params.event_nseverity < SEVERITIES.length) {
		priority = this.params[''severity_'' + SEVERITIES[this.params.event_nseverity]];
	}
	this.priority = (CParamValidator.isDefined(priority)) ? priority.trim() : this.params.otrs_default_priority_id;

	if (this.params.event_source === ''0'') {
		CParamValidator.validate({trigger_id: {type: ''integer''}, event_id: {type: ''integer''}}, this.params);
		this.params.zabbix_url = CWebhookHelper.createProblemURL(this.params.event_source, this.params.zabbix_url, this.params.trigger_id, this.params.event_id);
		this.params.alert_message = this.params.alert_subject + ''\\n'' + this.params.alert_message + ''\\n'' +
			this.params.zabbix_url + ''\\n'';
	}
	if (this.params.event_value != ''0'' && CParamValidator.isMacroSet(this.params.otrs_ticket_id)) {
		this.params.event_update_status = ''1'';
	}
	this.dynamicFields = {}
	Object.keys(this.params).forEach(function (key) {
		if (key.startsWith(''dynamicfield_'')) {
			this.dynamicFields[key.substring(13)] = this.params[key];
		}
	});

	this.data = {
		Article: {
			Subject: this.params.alert_subject,
			Body: (CParamValidator.isDefined(this.params.alert_message)) ? this.params.alert_message : '''',
			TimeUnit: this.params.otrs_time_unit.toString(),
			ContentType: ''text/plain; charset=utf8''
		}
	};

	this.result = {tags: {}};
};

OTRS.prototype.sendRequest = function (method) {
	var url = this.params.otrs_url + this.params.entrypoint +
		''?UserLogin='' + encodeURIComponent(this.params.otrs_auth_user) +
		''&Password='' + encodeURIComponent(this.params.otrs_auth_password);

	var response = this.request.jsonRequest(method, url, this.data);

	if (!CParamValidator.isType(response, ''object'')) {
		Logger.log(Logger.INFO, ''API response ERROR: '' + response);
		throw ''Unknown error. Check debug log for more information.'';
	}
	if (this.request.getStatus() < 200 || this.request.getStatus() >= 300) {
		var message = ''status code '' + this.request.getStatus();
		Logger.log(Logger.INFO, ''API response ERROR with '' + message + '': '' + response);
		throw ''Request failed with '' + message + ''. Check debug log for more information.'';
	}
	if (CParamValidator.isDefined(response.Error) && Object.keys(response.Error).length > 0) {
		Logger.log(Logger.INFO, ''API response ERROR: '' + JSON.stringify(response.Error));
		throw ''Request failed: '' + JSON.stringify(response.Error);
	}

	return {
		status: this.request.getStatus(),
		response: response
	};
};

OTRS.prototype.createTicket = function () {
	this.data[''Ticket''] = {
		Title: this.params.alert_subject,
		Queue: this.params.otrs_queue,
		Type: this.params.otrs_ticket_type,
		State: this.params.otrs_ticket_state,
		PriorityID: this.priority.toString(),
		CustomerUser: this.params.otrs_customer
	}

	var result = this.sendRequest(''post'');

	if (!CParamValidator.isDefined(result.response.TicketID) || result.status != 200) {
		throw ''Cannot create ((OTRS)) CE ticket. Check debug log for more information.'';
	}

	return result.response.TicketID;
}

OTRS.prototype.updateTicket = function () {
	CParamValidator.validate({otrs_ticket_id: {type: ''string''}, entrypoint: {type: ''string''}}, this.params);
	this.params.entrypoint += ''/'' + encodeURIComponent(this.params.otrs_ticket_id);

	var result = this.sendRequest(''put'');

	if (!CParamValidator.isDefined(result.response.TicketID) || result.status != 200) {
		throw ''Cannot update ((OTRS)) CE ticket. Check debug log for more information.'';
	}

	return result.response.TicketID;
}

OTRS.prototype.onProblem = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	if (CParamValidator.isDefined(alert.source) && CParamValidator.inArray(alert.source, [''trigger'', ''service'', ''internal''])) {
		if (Object.keys(this.dynamicFields).length > 0) {
			this.data.DynamicField = [];
			Object.keys(this.dynamicFields).forEach(function(field) {
				if (field !== undefined) {
					if (this.dynamicFields[field].match(/^\\d{4}[.-]\\d{2}[.-]\\d{2}$/)) {
						this.dynamicFields[field] = this.dynamicFields[field].replace(/\\./g, ''-'');
					}
	
					this.data.DynamicField.push({Name: field, Value: this.dynamicFields[field]});
				}
			});
		}
		const ticket_id = this.createTicket(alert);
		this.result.tags.__zbx_otrs_ticket_id = ticket_id;
		this.result.tags.__zbx_otrs_ticketlink = this.params.otrs_url + ''index.pl?Action=AgentTicketZoom;TicketID='' + ticket_id;

		return this.result;
	}
	return this.createTicket();
}

OTRS.prototype.onUpdate = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	this.updateTicket();

	return this.result;
}

OTRS.prototype.onResolve = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	if (this.params.otrs_closed_state_id > 0) {
		this.data[''Ticket''] = {
			StateID: this.params.otrs_closed_state_id
		}
	}
	this.updateTicket();

	return this.result;
}

OTRS.prototype.onDiscovery = function (alert) {
	return this.onProblem(alert);
}

OTRS.prototype.onAutoreg = function (alert) {
	return this.onProblem(alert);
}

try {
	var hook = new OTRS(value);
	hook.request = new CHttpRequest(Logger);
	return hook.run();
}
catch (error) {
	Logger.log(Logger.WARN, ''notification failed: '' + error);
	throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_otrs_ticketlink}','((OTRS)) CE: ticket #{EVENT.TAGS.__zbx_otrs_ticket_id}','This media type integrates your Zabbix installation with your ((OTRS)) CE installation using the Zabbix webhook feature.

((OTRS)) CE configuration:

1. Create a new web service. To do so, navigate to "Admin" → "Web services" and import the "ZabbixTicketConnector.yml" file (it can be found in the official Zabbix repository next to the media type file).

2. Create a new customer.

3. Create a new customer user. Select the ID of the customer that you created in the previous step.

4. Create a new agent. Depending on the ticket queue you want to use for tickets created by the webhook, set the "RW" permission for the group that this ticket queue belongs to. In the example below, if you want to use the "Misc" queue, you must set the "RW" permission for the group "users".

Zabbix configuration:

1. Before you can start using the ((OTRS)) CE webhook, set the global macro "{$ZABBIX.URL}":
- In the Zabbix web interface, go to "Administration" → "Macros" in the top-left dropdown menu.
- Set the global macro "{$ZABBIX.URL}" to the URL of the Zabbix frontend. The URL should be either an IP address, a fully qualified domain name, or localhost.
- Specifying a protocol is mandatory, whereas the port is optional. Depending on the web server configuration, you might also need to append "/zabbix" to the end of URL. Good examples:
  - http://zabbix.com
  - https://zabbix.lan/zabbix
  - http://server.zabbix.lan/
  - http://localhost
  - http://127.0.0.1:8080
- Bad examples:
  - zabbix.com
  - http://zabbix/

2. Set the following webhook parameters:
- otrs_auth_user - the username of the agent
- otrs_auth_password - the password of the agent
- otrs_customer - the email of the customer user
- otrs_queue - the queue that will be used for tickets created by the webhook
- otrs_url - the frontend URL of your ((OTRS)) CE installation (for example, "https://otrs.example.com/otrs")

3. If you want to prioritize issues according to the severity values in Zabbix, you can define mapping parameters (create them as additional webhook parameters):
- severity_<name> - the ((OTRS)) CE priority ID (<name> in the parameter name can be one of the following values: "not_classified", "information", "warning", "average", "high", "disaster")

4. If you have dynamic fields in ((OTRS)) CE and want them to be filled with values from Zabbix, add webhook parameters in the format "dynamicfield_<((OTRS)) CE dynamic field name>", similarly to the previous step. Dynamic fields can only be of the types "text", "textarea", "checkbox", or "date".

5. If you want the webhook to close tickets related to **resolved** problems in Zabbix, you can change the following parameter value:
- otrs_closed_state_id - ((OTRS)) CE state ID for closed tasks (possible values: 0 - Disable tickets closing, >0 - State ID from the State Management page).

6. If you use the ticket type feature, you can change the type of the created tickets:
- otrs_ticket_type - ((OTRS)) CE ticket type (set to "Unclassified" by default; present on fresh installations).

7. Click the "Enabled" checkbox to enable the mediatype and click the "Update" button to save the webhook settings.

8. 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).
- In the "Media" tab, click "Add" and select the type "OTRS CE" from the drop-down list. Add any value in the "Send to" field: it is not used in the webhook, but is required.
- Make sure this user has access to all hosts for which you would like problem notifications to be sent to ((OTRS)) CE.

9. 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/otrs_ce','0'),
('88','4','PagerDuty','','','','','','','','25','0','0','0','0','1','3','10s','1',E'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 CWebhookHelper = {

	createProblemURL: function(event_source, zabbix_url, trigger_id, event_id) {
		if (event_source === ''0'') {
			return zabbix_url + ''/tr_events.php?triggerid='' + trigger_id + ''&eventid='' + event_id;
		} else if (event_source === ''4'') {
			return zabbix_url + ''/zabbix.php?action=service.list'';
		}

		return zabbix_url;
	},

};

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 severityMapping = [
	''info'',
	''info'',
	''warning'',
	''warning'',
	''error'',
	''critical''
],
	serviceLogName = ''PagerDuty Webhook'',
	Logger = new CLogger(serviceLogName),
	PagerDuty = CWebhook;

function getDefaultEventData(data, params) {
	data.event_action = ''trigger'';
	data.payload.custom_details = {
		''Alert message'': params.alert_message
	};
	Object.keys(params).forEach(function (key) {
		if (key.startsWith(''customdetails_'') && !CParamValidator.isEmpty(params[key])) {
			data.payload.custom_details[key.substring(14)] = params[key];
		}
	});
	data.client = ''Zabbix'';
	data.client_url = params.zabbix_url;

	return data;
};

PagerDuty.prototype.onCheckParams = function () {
	this.params.url = ''https://events.pagerduty.com/v2/enqueue'';
	CParamValidator.validate({api_token: {type: ''string''}, event_id: {type: ''integer''},
		alert_subject: {type: ''string''}, host_ip: {type: ''string'', default: ''''}, zabbix_url: {type: ''string'', url: true},
		event_nseverity: {type: ''integer'', min: 0, max: 5, default: 0}}, this.params);
	if (CParamValidator.inArray(this.params.event_source, [''0'',''3'',''4''])) {
		CParamValidator.validate({host_name: {type: ''string'', default: ''''}}, this.params);
		if (this.params.event_source === ''0'') {
			CParamValidator.validate({trigger_id: {type: ''integer''}, event_update_status: {type: ''string'', array: [''0'', ''1'']},
				event_ack: {type: ''boolean''}}, this.params);
		}
		else {
			CParamValidator.validate({alert_message: {type: ''string''}, event_ack: {type: ''boolean'', default: false}}, this.params);
		}
	}
	this.data = {
		routing_key: this.params.api_token,
		dedup_key: String(this.params.event_id),
		payload: {
			summary: this.params.alert_subject,
			source: (!CParamValidator.isEmpty(this.params.host_name)) ? (this.params.host_name +
				((!CParamValidator.isEmpty(this.params.host_ip)) ? '' : '' + this.params.host_ip : '''')) : ''Zabbix'',
			severity: severityMapping[this.params.event_nseverity],
		}
	}
}

PagerDuty.prototype.onProblem = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	this.data = getDefaultEventData(this.data, this.params);
	if (CParamValidator.isDefined(alert.source) && alert.source === ''Trigger'') {
		this.data.links = [{
			href: CWebhookHelper.createProblemURL(this.params.event_source, this.params.zabbix_url, this.params.trigger_id, this.params.event_id),
			text: ''Event link''
		}];
	}

	return this.sendRequest();
}

PagerDuty.prototype.onUpdate = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	if (this.params.event_ack === true) {
		this.data.event_action = ''acknowledge'';
		return this.sendRequest();
	}
	this.data = getDefaultEventData(this.data, this.params);

	return this.sendRequest();
}

PagerDuty.prototype.onResolve = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	this.data = getDefaultEventData(this.data, this.params);
	this.data.event_action = ''resolve'';
	
	return this.sendRequest();
}

PagerDuty.prototype.onDiscovery = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);
	this.data = getDefaultEventData(this.data, this.params);
	this.data.payload.source = ''Discovery'';

	return this.sendRequest();
}

PagerDuty.prototype.onAutoreg = function (alert) {
	return this.onProblem(alert);
}

PagerDuty.prototype.sendRequest = function () {
	var response = this.request.jsonRequest(''POST'', this.params.url, this.data);
	if (!CParamValidator.isType(response, ''object'')) {
		Logger.log(Logger.INFO, ''API response ERROR: '' + response);
		throw ''Unknown error. Check debug log for more information.'';
	}
	if (this.request.getStatus() != 202) {
		if (CParamValidator.isType(response.errors, ''array'') && CParamValidator.isType(response.errors[0], ''string'')) {
			throw response.errors[0];
		}
		else {
			throw ''Unknown error. HTTP status: '' + this.request.getStatus();
		}
	}
	if (response.status != ''success'') {
		Logger.log(Logger.INFO, ''API response ERROR: '' + response);
		throw ''Unknown error. Check debug log for more information.'';
	}

	return ''OK'';
};

try {
	var hook = new PagerDuty(value);
	hook.request = new CHttpRequest(Logger);
	return hook.run();
}
catch (error) {
	Logger.log(Logger.WARN, ''notification failed: '' + error);
	throw ''Sending failed: '' + error;
}','30s','0','0','','','Please refer to https://v2.developer.pagerduty.com/docs/send-an-event-events-api-v2 and https://www.zabbix.com/documentation/7.4/manual/config/notifications/media/webhook#example_scripts.

Set global macro {$ZABBIX.URL} with your Zabbix server URL.
Add a dedicated user with the media type "PagerDuty" and place the integration key in the user''s "Send to" parameter to integrate into the service.','0'),
('89','4','Pushover','','','','','','','','25','0','0','0','0','0','3','10s','1',E'try {
    var params = JSON.parse(value),
        request = new HttpRequest(),
        data,
        response,
        severities = [
            {name: ''not_classified'', color: ''#97AAB3''},
            {name: ''information'', color: ''#7499FF''},
            {name: ''warning'', color: ''#FFC859''},
            {name: ''average'', color: ''#FFA059''},
            {name: ''high'', color: ''#E97659''},
            {name: ''disaster'', color: ''#E45959''},
            {name: ''resolved'', color: ''#009900''},
            {name: ''default'', color: ''#000000''}
        ],
        priority;

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

    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_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.'';
    }

    if ([0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity)) === -1) {
        params.event_nseverity = ''7'';
    }

    if (params.event_value === ''0'') {
        params.event_nseverity = ''6'';
    }

    priority = params[''priority_'' + severities[params.event_nseverity].name] || params.priority_default;

    if (isNaN(priority) || priority < -2 || priority > 2) {
        throw ''"priority" should be -2..2'';
    }

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

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

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

    data = {
        token: params.token,
        user: params.user,
        title: params.title,
        message: params.message,
        url: (params.event_source === ''0'') 
            ? params.url + ''/tr_events.php?triggerid='' + params.triggerid + ''&eventid='' + params.eventid
            : params.url,
        url_title: params.url_title,
        priority: priority
    };

    if (priority == 2) {
        if (isNaN(params.retry) || params.retry < 30) {
            throw ''field "retry" should be a number with value of at least 30 if "priority" is set to 2'';
        }

        if (isNaN(params.expire) || params.expire > 10800) {
            throw ''field "expire" should be a number with value of at most 10800 if "priority" is set to 2'';
        }

        data.retry = params.retry;
        data.expire = params.expire;
    }

    data = JSON.stringify(data);
    Zabbix.log(4, ''[ Pushover Webhook ] Sending request: '' + params.endpoint + ''\\n'' + data);

    request.addHeader(''Content-Type: application/json'');
    response = request.post(params.endpoint, data);

    Zabbix.log(4, ''[ Pushover Webhook ] Received response with status code '' + request.getStatus() + ''\\n'' + response);

    if (response !== null) {
        try {
            response = JSON.parse(response);
        }
        catch (error) {
            Zabbix.log(4, ''[ Pushover Webhook ] Failed to parse response received from Pushover'');
            response = null;
        }
    }

    if (request.getStatus() != 200 || response === null || typeof response !== ''object'' || response.status !== 1) {
        if (response !== null && typeof response === ''object'' && typeof response.errors === ''object''
                && typeof response.errors[0] === ''string'') {
            throw response.errors[0];
        }
        else {
            throw ''Unknown error. Check debug log for more information.'';
        }
    }

    return ''OK'';
}
catch (error) {
    Zabbix.log(4, ''[ Pushover Webhook ] Pushover notification failed: '' + error);
    throw ''Pushover notification failed: '' + error;
}','30s','0','0','','','Please refer to setup guide here: https://git.zabbix.com/projects/ZBX/repos/zabbix/browse/templates/media/pushover

Set token parameter with to your Pushover application key.
When assigning Pushover media to the Zabbix user - add user key into send to field.','0'),
('90','4','Redmine','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var Redmine = {
    params: {},

    setParams: function (params) {
        if (typeof params !== ''object'') {
            return;
        }

        Redmine.params = params;
        if (typeof Redmine.params.url === ''string'') {
            if (!Redmine.params.url.endsWith(''/'')) {
                Redmine.params.url += ''/'';
            }
        }
    },

    addCustomFields: function (data, fields) {
        if (typeof fields === ''object'' && Object.keys(fields).length) {

            data.issue.custom_fields = [];
            Object.keys(fields)
                .forEach(function (field) {
                    var field_value = fields[field];

                    if (field_value !== undefined) {
                        data.issue.custom_fields.push({ id: field, value: field_value });
                    }
                });

        }
        return data;
    },

    request: function (method, query, data) {
        [''url'', ''access_key''].forEach(function (field) {
            if (typeof Redmine.params !== ''object'' || typeof Redmine.params[field] === ''undefined''
                || Redmine.params[field] === '''' ) {
                throw ''Required param is not set: "'' + field + ''".'';
            }
        });

        var response,
            url = Redmine.params.url + query,
            request = new HttpRequest();

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

        request.addHeader(''Content-Type: application/json'');
        request.addHeader(''X-Redmine-API-Key: '' + Redmine.params.access_key);

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        Zabbix.log(4, ''[ Redmine Webhook ] Sending request: '' +
            url + ((typeof data === ''string'') ? ('' '' + data) : ''''));

        switch (method) {
            case ''get'':
                response = request.get(url, data);
                break;

            case ''post'':
                response = request.post(url, data);
                break;

            case ''put'':
                response = request.put(url, data);
                break;

            default:
                throw ''Unsupported HTTP request method: '' + method;
        }

        Zabbix.log(4, ''[ Redmine Webhook ] Received response with status code '' + request.getStatus() + '': '' + response);

        if (response !== null) {
            try {
                response = JSON.parse(response);
            }
            catch (error) {
                Zabbix.log(4, ''[ Redmine Webhook ] Failed to parse response received from Redmine'');
                response = null;
            }
        }

        if (request.getStatus() < 200 || request.getStatus() >= 300) {
            var message = ''Request failed with status code '' + request.getStatus();

            if (response !== null && typeof response.errors !== ''undefined''
                && Object.keys(response.errors).length > 0) {
                message += '': '' + JSON.stringify(response.errors);
            }
            else if (response !== null && typeof response.errorMessages !== ''undefined''
                && Object.keys(response.errorMessages).length > 0) {
                message += '': '' + JSON.stringify(response.errorMessages);
            }

            throw message + '' Check debug log for more information.'';
        }

        return {
            status: request.getStatus(),
            response: response
        };
    },

    getProjectID: function(name) {
        var result = Redmine.request(''get'', ''projects.json''),
            project_id;

        if (result.response) {
            var projects = result.response.projects || [];

            for (var i in projects) {
                if (projects[i].name === name) {
                    project_id = projects[i].id;
                    break;
                }
            }
        }
        else {
            Zabbix.log(4, ''[ Redmine Webhook ] Failed to retrieve project data.'');
        }

        if (typeof project_id === ''undefined'') {
            throw ''Cannot find project with name: '' + name;
        }

        return project_id;
    },

    createIssue: function(subject, description, priority, fields) {
        var project_id = /^\\d+$/.test(Redmine.params.project)
                ? Redmine.params.project
                : Redmine.getProjectID(Redmine.params.project),
            data = {
                issue: {
                    project_id: project_id,
                    tracker_id: Redmine.params.tracker_id,
                    subject: subject,
                    description: description
                }
            },
            result;

        if (priority) {
            data.issue.priority_id = priority;
        }

        result = Redmine.request(''post'', ''issues.json'', Redmine.addCustomFields(data, fields));

        if (typeof result.response !== ''object''
            || typeof result.response.issue.id === ''undefined''
            || result.status != 201) {
            throw ''Cannot create Redmine issue. Check debug log for more information.'';
        }

        return result.response.issue.id;
    },

    updateIssue: function (note, fields, status) {
        var data = {
            issue: {
                notes: note || ''''
            }
        };

        if (status) {
            data.issue.status_id = status;
        }

        Redmine.request(''put'', ''issues/'' + Redmine.params.issue_key + ''.json'', Redmine.addCustomFields(data, fields));
    }

};

try {
    var params = JSON.parse(value),
        params_redmine = {},
        params_fields = {},
        params_update = {},
        result = {tags: {}},
        required_params = [
            ''alert_subject'', ''tracker_id'', ''project'',
            ''event_source'', ''event_value'',  ''event_update_status''
        ],
        severities = [
            {name: ''not_classified'', color: ''#97AAB3''},
            {name: ''information'', color: ''#7499FF''},
            {name: ''warning'', color: ''#FFC859''},
            {name: ''average'', color: ''#FFA059''},
            {name: ''high'', color: ''#E97659''},
            {name: ''disaster'', color: ''#E45959''},
            {name: ''resolved'', color: ''#009900''},
            {name: null, color: ''#000000''}
        ],
        priority;

    Object.keys(params)
        .forEach(function (key) {
            if (key.startsWith(''redmine_'')) {
                params_redmine[key.substring(8)] = params[key];
            }
            else if (key.startsWith(''customfield_'')) {
                params_fields[key.substring(12)] = params[key];
            }
            else if (key.startsWith(''event_update_'')) {
                params_update[key.substring(13)] = params[key];
            }
            else if (required_params.indexOf(key) !== -1 && params[key].trim() === '''') {
                throw ''Parameter "'' + key + ''" cannot be empty.'';
            }
        });

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

    // 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_source === ''0'' && 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 (typeof params_redmine.close_status_id === ''string'' && params_redmine.close_status_id.trim() !== '''' && !parseInt(params_redmine.close_status_id, 10)) {
        throw ''Incorrect "redmine_close_status_id" parameter given! Must be an integer.'';
    }

    if (params.event_source !== ''0'' && params.event_value === ''0'') {
        throw ''Recovery operations are supported only for trigger-based actions.'';
    }

    if (params.event_source === ''0''
        && ((params.event_value === ''1'' && params.event_update_status === ''1'')
            || (params.event_value === ''0''
                && (params.event_update_status === ''0'' || params.event_update_status === ''1'')))
        && (isNaN(parseInt(params.redmine_issue_key)) || parseInt(params.redmine_issue_key) < 1 )) {
        throw ''Incorrect "redmine_issue_key" parameter given: '' + params.redmine_issue_key +
            ''\\nMust be positive integer.'';
    }

    if ([0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity)) === -1) {
        params.event_nseverity = ''7'';
    }

    if (params.event_value === ''0'') {
        params.event_nseverity = ''6'';
    }

    priority = params[''severity_'' + severities[params.event_nseverity].name];
    priority = priority && priority.trim() || severities[7].name;

    Redmine.setParams(params_redmine);
    Redmine.HTTPProxy = params.HTTPProxy;

    // Create issue for non trigger-based events.
    if (params.event_source !== ''0''
        && params.event_value !== ''0'') {
        Redmine.createIssue(params.alert_subject, params.alert_message, priority);
    }
    // Create issue for trigger-based events.
    else if (params.event_value === ''1'' && params_update.status === ''0'') {
        var issue_id = Redmine.createIssue(params.alert_subject,
            params.alert_subject + ''\\n'' + params.alert_message + ''\\n'' +
            params.zabbix_url + (params.zabbix_url.endsWith(''/'') ? '''' : ''/'') +
            ''tr_events.php?triggerid='' + params.trigger_id + ''&eventid='' + params.event_id + ''\\n'',
            priority,
            params_fields);

        result.tags.__zbx_redmine_issue_id = issue_id;
        result.tags.__zbx_redmine_issuelink = params.redmine_url +
            (params.redmine_url.endsWith(''/'') ? '''' : ''/'') + ''issues/'' + issue_id;
    }
    // Close issue if parameter close_status_id is set and it is a recovery operation
    else if (params.event_value === ''0'' && typeof params_redmine.close_status_id === ''string'' && params_redmine.close_status_id.trim() !== '''') {
        Redmine.updateIssue(params.alert_subject + ''\\n'' + params.alert_message, params_fields, params_redmine.close_status_id);
    }
    // Update created issue for trigger-based event.
    else {
        Redmine.updateIssue(params.alert_subject + ''\\n'' + params.alert_message, params_fields);
    }

    return JSON.stringify(result);
}
catch (error) {
    Zabbix.log(3, ''[ Redmine Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_redmine_issuelink}','Redmine: issue #{EVENT.TAGS.__zbx_redmine_issue_id}','','0'),
('91','4','Rocket.Chat','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var RocketChat = {
    params: {},

    setParams: function (params) {
        if (typeof params !== ''object'') {
            return;
        }

        RocketChat.params = params;
        if (RocketChat.params.url && RocketChat.params.api_url) {
            if (!RocketChat.params.url.endsWith(''/'')) {
                RocketChat.params.url += ''/'';
            }
            if (!RocketChat.params.api_url.endsWith(''/'')) {
                RocketChat.params.api_url += ''/'';
            }
            if (RocketChat.params.api_url.startsWith(''/'')) {
                RocketChat.params.api_url = RocketChat.params.api_url.substring(1);
            }

            RocketChat.params.url += RocketChat.params.api_url;
        }
    },

    setProxy: function (HTTPProxy) {
        RocketChat.HTTPProxy = HTTPProxy;
    },

    addFields: function (fields) {
        var data = [];

        if (typeof fields === ''object'' && Object.keys(fields).length) {
            Object.keys(fields)
                .forEach(function(field) {
                    if (fields[field] === '''') {
                        Zabbix.log(4, ''[ RocketChat Webhook ] Field "'' + field +
                            ''" can\\''t be empty. The field ignored.'');
                    }
                    else {
                        try {
                            var parts = field.split('':''),
                                prefix = parts[0].split(''_'');

                            if (typeof prefix[2] === ''undefined''
                                    || (prefix[2] === ''p'' && params.event_value === ''1'')
                                    || (prefix[2] === ''r'' && params.event_value === ''0'')) {
                                data.push({
                                    title: field.substring(field.indexOf('':'') + 1),
                                    value: fields[field],
                                    short: prefix[1] === ''short''
                                });
                            }
                        }
                        catch (error) {
                            Zabbix.log(4, ''[ RocketChat Webhook ] Can\\''t parse field "'' + field +
                                ''". The field ignored.'');
                        }
                    }
                });
        }

        return data;
    },

    request: function (method, query, data) {
        [''url'', ''api_url'', ''user_id'', ''user_token'', ''send_to''].forEach(function (field) {
            if (typeof RocketChat.params !== ''object'' || typeof RocketChat.params[field] === ''undefined''
                    || RocketChat.params[field] === '''' ) {
                throw ''Required parameter is not set: "'' + field + ''".'';
            }
        });

        var response,
            url = RocketChat.params.url + query,
            request = new HttpRequest();

        request.addHeader(''Content-Type: application/json'');
        request.addHeader(''X-Auth-Token:'' + RocketChat.params.user_token);
        request.addHeader(''X-User-Id:'' + RocketChat.params.user_id);

        if (typeof RocketChat.HTTPProxy !== ''undefined'' && RocketChat.HTTPProxy !== '''') {
            request.setProxy(RocketChat.HTTPProxy);
        }

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        Zabbix.log(4, ''[ RocketChat Webhook ] Sending request: '' + url +
            ((typeof data === ''string'') ? (''\\n'' + data) : ''''));

        switch (method) {
            case ''get'':
                response = request.get(url, data);
                break;

            case ''post'':
                response = request.post(url, data);
                break;

            default:
                throw ''Unsupported HTTP request method: '' + method;
        }

        Zabbix.log(4, ''[ RocketChat Webhook ] Received response with status code '' +
            request.getStatus() + ''\\n'' + response);

        if (response !== null) {
            try {
                response = JSON.parse(response);
            }
            catch (error) {
                Zabbix.log(4, ''[ RocketChat Webhook ] Failed to parse response received from RocketChat'');
                response = null;
            }
        }

        if (request.getStatus() < 200 || request.getStatus() >= 300) {
            var message = ''Request failed with status code '' + request.getStatus();

            if (response !== null && typeof response.message !== ''undefined'') {
                message += '': '' + JSON.stringify(response.message);
            }
            else if (response !== null && typeof response.error !== ''undefined'') {
                message += '': '' + JSON.stringify(response.error);
            }

            throw message + ''. Check debug log for more information.'';
        }

        return {
            status: request.getStatus(),
            response: response
        };
    },

    postMessage: function(use_default_message, message, fields) {
        var data = {
            channel: RocketChat.params.send_to,
            attachments: [{
                collapsed: false,
                color: RocketChat.params.color,
                title: params.alert_subject
            }]
        };

        if (RocketChat.params.title_link) {
            data.attachments[0].title_link = RocketChat.params.title_link;
        }

        if (use_default_message) {
            data.attachments[0].text = message;
        }
        else {
            data.attachments[0].fields = RocketChat.addFields(fields);
        }

        var result = RocketChat.request(''post'', ''chat.postMessage'', data);

        if (typeof result.response !== ''object'' || typeof result.response.message._id === ''undefined'') {
            throw ''Cannot send RocketChat message. Check debug log for more information.'';
        }

        return {
            id: result.response.message._id,
            rid: result.response.message.rid,
            channel: result.response.channel
        };
    },

    sendMessage: function(update, fields) {

        var data = {
            message: {
                rid: RocketChat.params.room_id,
                tmid: RocketChat.params.msg_id,
                tshow: true
            }
        };

        if (update.status === ''0'') {
            data.message.attachments = [{
                collapsed: false,
                color: RocketChat.params.color,
                title: params.alert_subject,
                title_link: RocketChat.params.title_link,
                fields: RocketChat.addFields(fields)
            }];
        }
        else {
            data.message.alias = update.user;
            data.message.msg = update.action;
            if (update.message) {
                data.message.attachments = [{
                    color: RocketChat.params.color,
                    text: update.message
                }];
            }
        }

        RocketChat.request(''post'', ''chat.sendMessage'', data);
    },

    getMessageLink: function(rid, id) {
        var room = RocketChat.request(''get'', ''rooms.info?roomId='' + encodeURIComponent(rid)),
            link = params.rc_url +
                (params.rc_url.endsWith(''/'') ? '''' : ''/'');

        switch (room.response.room.t) {
            case ''c'':
                link += ''channel/'' + room.response.room.name + ''?msg='' + id;
                break;

            case ''p'':
                link += ''group/'' + room.response.room.name + ''?msg='' + id;
                break;

            case ''d'':
                link += ''direct/'' + rid + ''?msg='' + id;
                break;

            default:
                Zabbix.log(4, ''[ RocketChat Webhook ] Can\\''t get room type. Link to message will not be added.'');
        }

        return link;
    }
};

try {
    var params = JSON.parse(value),
        response,
        fields = {},
        rc = {},
        update = {},
        result = {tags: {}},
        required_params = [''alert_subject'', ''alert_message'', ''event_source'', ''event_value''],
        severities = [
            {name: ''not_classified'', color: ''#97AAB3''},
            {name: ''information'', color: ''#7499FF''},
            {name: ''warning'', color: ''#FFC859''},
            {name: ''average'', color: ''#FFA059''},
            {name: ''high'', color: ''#E97659''},
            {name: ''disaster'', color: ''#E45959''},
            {name: ''resolved'', color: ''#009900''},
            {name: ''default'', color: ''#000000''}
        ];

    Object.keys(params)
        .forEach(function (key) {
            if (key.startsWith(''rc_'')) {
                rc[key.substring(3)] = params[key];
            }
            else if (key.startsWith(''field_'')) {
                fields[key.substring(6)] = params[key];
            }
            else if (key.startsWith(''event_update_'')) {
                update[key.substring(13)] = params[key];
            }
            else if (required_params.indexOf(key) !== -1 && params[key] === '''') {
                throw ''Parameter "'' + key + ''" can\\''t be empty.'';
            }
        });

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

    // Forcing parameters for non trigger-based events.
    if (params.event_source !== ''0'') {
        params.use_default_message = ''true'';
        params.event_nseverity = ''0'';
        params.rc_title_link = false;
    }

    // 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.'';
    }

    if ([0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity)) === -1) {
        params.event_nseverity = ''7'';
    }

    if (params.event_value === ''0'') {
        params.event_nseverity = ''6'';
    }

    RocketChat.setParams(rc);
    RocketChat.setProxy(params.HTTPProxy);
    RocketChat.params.color = severities[params.event_nseverity].color;

    // Send default message if use_default_message === true.
    if (params.use_default_message.toLowerCase() === ''true'') {
        response = RocketChat.postMessage(true, params.alert_message);
        result.tags.__zbx_rc_id = response.id;
        result.tags.__zbx_rc_rid = response.rid;
        result.tags.__zbx_rc_msg_url = RocketChat.getMessageLink(response.rid, response.id);
    }
    // Send message for trigger-based events.
    else if (params.event_value === ''1'' && update.status === ''0'') {
        response = RocketChat.postMessage(false, params.alert_message, fields);
        result.tags.__zbx_rc_id = response.id;
        result.tags.__zbx_rc_rid = response.rid;
        result.tags.__zbx_rc_msg_url = RocketChat.getMessageLink(response.rid, response.id);
    }
    // Send thread message for trigger-based event.
    else {
        RocketChat.sendMessage(update, fields);
    }

    return JSON.stringify(result);
}
catch (error) {
    Zabbix.log(3, ''[ RocketChat Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_rc_msg_url}','Rocket.Chat','','0'),
('92','4','ServiceNow','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var ServiceNow = {
    params: {},

    setParams: function (params) {
        if (typeof params !== ''object'') {
            return;
        }

        ServiceNow.params = params;
        if (typeof ServiceNow.params.url === ''string'') {
            if (!ServiceNow.params.url.endsWith(''/'')) {
                ServiceNow.params.url += ''/'';
            }

            ServiceNow.params.url += ''api/now/table/incident'';
        }
    },

    setProxy: function (HTTPProxy) {
        ServiceNow.HTTPProxy = HTTPProxy;
    },

    setFields: function (data, fields) {
        if (typeof fields === ''object'' && Object.keys(fields).length) {
            Object.keys(fields)
                .forEach(function(field) {
                    data[field] = (fields[field].match(/^\\d{4}\\.\\d{2}\\.\\d{2}$/) !== null)
                        ? fields[field].replace(/\\./g, ''-'')
                        : fields[field];
                });
        }
    },

    request: function (method, data) {
        [''url'', ''user'', ''password''].forEach(function (field) {
            if (typeof ServiceNow.params !== ''object'' || typeof ServiceNow.params[field] === ''undefined''
                || ServiceNow.params[field] === '''' ) {
                throw ''Required ServiceNow param is not set: "'' + field + ''".'';
            }
        });

        var response,
            url = ServiceNow.params.url,
            request = new HttpRequest();

        request.addHeader(''Content-Type: application/json'');
        request.addHeader(''Authorization: Basic '' + btoa(ServiceNow.params.user + '':'' + ServiceNow.params.password));

        if (typeof ServiceNow.HTTPProxy !== ''undefined'' && ServiceNow.HTTPProxy !== '''') {
            request.setProxy(ServiceNow.HTTPProxy);
        }

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        Zabbix.log(4, ''[ ServiceNow Webhook ] Sending request: '' + url + ((typeof data === ''string'')
            ? (''\\n'' + data)
            : ''''));

        switch (method) {
            case ''get'':
                response = request.get(url, data);
                break;

            case ''post'':
                response = request.post(url, data);
                break;

            case ''put'':
                response = request.put(url, data);
                break;

            default:
                throw ''Unsupported HTTP request method: '' + method;
        }

        Zabbix.log(4, ''[ ServiceNow Webhook ] Received response with status code '' +
            request.getStatus() + ''\\n'' + response);

        if (response !== null) {
            try {
                response = JSON.parse(response);
            }
            catch (error) {
                Zabbix.log(4, ''[ ServiceNow Webhook ] Failed to parse response received from ServiceNow'');
                response = null;
            }
        }

        if (request.getStatus() < 200 || request.getStatus() >= 300) {
            var message = ''Request failed with status code '' + request.getStatus();

            if (response !== null && typeof response.error.message !== ''undefined''
                && Object.keys(response.error).length > 0) {
                message += '': '' + JSON.stringify(response.error.message);
            }

            throw message + '' Check debug log for more information.'';
        }
        else if (typeof response.result !== ''object'' || typeof response.result.sys_id === ''undefined'') {
            throw ''Cannot create ServiceNow incident. Check debug log for more information.'';
        }

        return response.result;
    }
};

try {
    var params = JSON.parse(value),
        fields = {},
        servicenow = {},
        data = {},
        result = {tags: {}},
        required_params = [
            ''alert_subject'', ''alert_message'', ''event_source'', ''event_value'',
            ''event_update_status'', ''event_recovery_value'', ''event_nseverity''
        ],
        severities = [
            {name: ''not_classified'', color: ''#97AAB3''},
            {name: ''information'', color: ''#7499FF''},
            {name: ''warning'', color: ''#FFC859''},
            {name: ''average'', color: ''#FFA059''},
            {name: ''high'', color: ''#E97659''},
            {name: ''disaster'', color: ''#E45959''},
            {name: ''resolved'', color: ''#009900''},
            {name: ''default'', color: ''#000000''}
        ],
        method = ''post'',
        process_tags = true;

    Object.keys(params)
        .forEach(function (key) {
            if (key.startsWith(''servicenow_'')) {
                servicenow[key.substring(11)] = params[key];
            }
            else if (key.startsWith(''u_'')) {
                fields[key] = params[key];
            }
            else if (required_params.indexOf(key) !== -1 && params[key] === '''') {
                throw ''Parameter "'' + key + ''" can\\''t be empty.'';
            }
        });

    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 ([0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity)) === -1) {
        params.event_nseverity = ''7'';
    }

    // 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.'';
    }

    if (params.event_source !== ''0'' && params.event_recovery_value === ''0'') {
        throw ''Recovery operations are supported only for trigger-based actions.'';
    }

    data.short_description = params.alert_subject;
    data.description = params.alert_message;
    data.comments = params.alert_message;

    if (typeof params[''urgency_for_'' + severities[params.event_nseverity].name] !== ''undefined'') {
        data.urgency = params[''urgency_for_'' + severities[params.event_nseverity].name];
    }

    ServiceNow.setParams(servicenow);
    ServiceNow.setProxy(params.HTTPProxy);
    ServiceNow.setFields(data, fields);

    if (params.event_source === ''0'' && (params.event_value === ''0'' || params.event_update_status === ''1'')) {
        process_tags = false;
        method = ''put'';
        delete data.description;
        delete data.urgency;
        ServiceNow.params.url += ''/'' + params.servicenow_sys_id;
    }

    var response = ServiceNow.request(method, data);

    if (process_tags) {
        result.tags.__zbx_servicenow_sys_id = response.sys_id;
        result.tags.__zbx_servicenow_link = params.servicenow_url +
            (params.servicenow_url.endsWith(''/'') ? '''' : ''/'') + ''incident.do?sys_id='' + response.sys_id;
        result.tags.__zbx_servicenow_number = response.number;
    }

    return JSON.stringify(result);
}
catch (error) {
    Zabbix.log(3, ''[ ServiceNow Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_servicenow_link}','ServiceNow: {EVENT.TAGS.__zbx_servicenow_number}','','0'),
('93','4','SIGNL4','','','','','','','','25','0','0','0','0','1','3','10s','1',E'// SIGNL4 Webhook
try {
    var response,
        payload,
        params = JSON.parse(value),
        endpoint = ''https://connect.signl4.com/webhook/'',
        request = new HttpRequest();

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

    if (typeof params.teamsecret === ''string'' && params.teamsecret.trim() !== '''') {
        endpoint += params.teamsecret;
        delete params.teamsecret;
    }
    else {
        throw ''The team secret of your SIGNL4 team cannot be empty.'';
    }

    if (typeof params.Severity === ''string'' && params.Severity === ''{EVENT.SEVERITY}'') {
        params.Severity = ''Not classified'';
    }

	if (typeof params.User === ''string'' && params.User === ''{USER.FULLNAME}'') {
        params.User = '''';
    }

	if (typeof params.Event_Update_Action === ''string'' && params.Event_Update_Action === ''{EVENT.UPDATE.ACTION}'') {
        params.Event_Update_Action = '''';
    }

	// Assemble X-S4-ExternalID for two-way integration
	// Format: "ZabbixEventID: 222 ZabbixURL: https://your-zabbix-server/zabbix/"
	params[''X-S4-ExternalID''] = ''ZabbixEventID: '' + params.Event_ID;
	if (typeof params.Zabbix_URL === ''string'' && params.Zabbix_URL.indexOf(''http'') == 0) {
		// Make sure the URL ends with ''/''
		if (params.Zabbix_URL.charAt(params.Zabbix_URL.length - 1) != ''/'') {
			params.Zabbix_URL = params.Zabbix_URL + ''/'';
		}

		params[''X-S4-ExternalID''] = params[''X-S4-ExternalID''] + '' ZabbixURL: '' +  params.Zabbix_URL;

		// Add Link parameter
		params[''Link''] = params.Zabbix_URL + "tr_events.php?triggerid="+params.Trigger_ID + "&eventid=" + params.Event_ID;
	}

	// Check if this is a new problem or a recovery
	if (params.Trigger_Status == ''OK'') {
		params[''X-S4-Status''] = ''resolved'';
	}
	else {
		params[''X-S4-Status''] = ''new'';
		params[''X-S4-SourceSystem''] = ''Zabbix'';
	}

    payload = JSON.stringify(params);
    Zabbix.log(4, ''[ SIGNL4 Webhook ] Sending request: '' + payload);

    request.addHeader(''Content-Type: application/json'');
    response = request.post(endpoint, ''payload='' + payload);

    Zabbix.log(4, ''[ SIGNL4 Webhook ] Received response with status code '' +
            request.getStatus() + ''\\n'' + response
    );

    if (request.getStatus() !== 201) {
        throw ''Request failed with status code '' + request.getStatus() +
                ''. Check debug log for more information.'';
    }

    return ''OK'';
}
catch (error) {
    Zabbix.log(4, ''[ SIGNL4 Webhook ] ERROR: '' + error);

    throw ''Sending failed: '' + error;
}','30s','0','0','','','SIGNL4 is a mobile alert notification app for powerful alerting, alert management and mobile assignment of work items. It offers alerting via app push, SMS and voice calls including escalations, tracking, and duty scheduling.

Get the app at https://www.signl4.com.

Find out more including an integration video here: https://www.signl4.com/blog/portfolio_item/zabbix-mobile-alert-notification-duty-schedule-escalation/','0'),
('94','4','Slack','','','','','','','','25','0','0','0','0','1','1','10s','1',E'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();
	}
}

const CWebhookHelper = {

	createProblemURL: function(event_source, zabbix_url, trigger_id, event_id) {
		if (event_source === ''0'') {
			return zabbix_url + ''/tr_events.php?triggerid='' + trigger_id + ''&eventid='' + event_id;
		} else if (event_source === ''4'') {
			return zabbix_url + ''/zabbix.php?action=service.list'';
		}

		return zabbix_url;
	},

};

var serviceLogName = ''Slack Webhook'',
	Logger = new CLogger(serviceLogName),
	Slack = CWebhook;

Slack.prototype.onCheckParams = function () {
	CParamValidator.validate({
		alert_subject: { type: ''string'' },
		alert_message: { type: ''string'' },
		bot_token: { type: ''string'' },
		zabbix_url: { type: ''string'', url: true },
		channel: { type: ''string'', macro: ''ALERT.SENDTO'' },
		slack_mode: { type: ''string'', array: [''alarm'', ''event''], }
	}, this.params);

	if (this.params.event_source === ''0'') {
		CParamValidator.validate({
			event_id: { type: ''integer'' },
			trigger_id: { type: ''integer'' }
		}, this.params);
	}

	if (CParamValidator.inArray(this.params.event_source, [''0'', ''3'', ''4''])) {
		CParamValidator.validate({
			event_tags: { type: ''array'', macro: ''EVENT.TAGSJSON'', tags: true, default: {} }
		}, this.params);
	}

	if (this.params.event_value != ''0'' && CParamValidator.isDefined(this.params.event_tags[''__channel_id_'' + this.params.channel])) {
		this.params.event_update_status = ''1'';
	}

	this.severity_colors = [
		''#97AAB3'',
		''#7499FF'',
		''#FFC859'',
		''#FFA059'',
		''#E97659'',
		''#E45959''
	];

	this.resolve_color = ''#009900'';
	this.slack_endpoint = ''https://slack.com/api/'';

	this.problem_url = CWebhookHelper.createProblemURL(this.params.event_source, this.params.zabbix_url, this.params.trigger_id, this.params.event_id);

	this.data = {
		channel: this.params.channel,
		attachments: [
			{
				fallback: this.params.alert_subject,
				title: this.params.alert_subject,
				color: this.severity_colors[this.params.event_nseverity],
				title_link: this.problem_url,
				text: this.params.alert_message,
				actions: [
					{
						type: ''button'',
						text: ''Open in Zabbix'',
						url: this.problem_url
					}
				]
			}
		]
	};

	this.reply = {
		channel: this.params.channel,
		thread_ts: '''',
		blocks: [
			{
				type: ''context'',
				elements: [
					{
						type: ''plain_text'',
						text: ''Event update message''
					}
				]
			},
			{
				type: ''rich_text'',
				elements: [
					{
						type: ''rich_text_section'',
						elements: [
							{
								type: ''text'',
								text: '''',
								style: {
									italic: true
								}
							}
						]
					}
				]
			}
		]
	};
};

Slack.prototype.sendRequest = function (route, data, tags) {
	this.request.clearHeader();
	this.request.addHeaders({
		''Content-Type'': ''application/json; charset=utf-8;'',
		''Authorization'': ''Bearer '' + this.params.bot_token
	});

	var response = this.request.jsonRequest(''POST'', this.slack_endpoint + route, 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.error, ''string'')) {
			throw ''Endpoint response:'' + response.error;
		}
		else {
			throw ''Unknown error. Check debug log for more information.'';
		}
	}

	if (tags) {
		return {
			tags: {
				[''__message_ts_'' + this.params.channel]: response.ts,
				[''__channel_id_'' + this.params.channel]: response.channel,
				[''__message_link_'' + this.params.channel]: this.getPermalink(response.channel, response.ts),
			}
		};

	}
	else {
		return { tags: {} };
	}
};

Slack.prototype.getPermalink = function (channel, message_ts) {
	var response = this.request.jsonRequest(''GET'', this.slack_endpoint + ''chat.getPermalink'' + ''?channel='' + channel + ''&message_ts='' + message_ts);

	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.error, ''string'')) {
			throw ''Endpoint response:'' + response.error;
		}
		else {
			throw ''Unknown error. Check debug log for more information.'';
		}
	}

	if (!CParamValidator.isDefined(response.permalink)) {
		throw ''Permalink is missed from the JSON response'';
	}

	return response.permalink;
};

Slack.prototype.onProblem = function (properties) {
	Logger.log(Logger.INFO, ''Source: '' + properties.source + ''; Event: '' + properties.event);

	if (this.params.slack_mode === "alarm") {
		return this.sendRequest(''chat.postMessage'', this.data, true);
	} else {
		return this.sendRequest(''chat.postMessage'', this.data, false);
	}
};

Slack.prototype.onUpdate = function (properties) {
	Logger.log(Logger.INFO, ''Source: '' + properties.source + ''; Event: '' + properties.event);

	if (this.params.slack_mode === "alarm") {
		this.data.channel = this.params.event_tags[''__channel_id_'' + this.params.channel];
		this.data.ts = this.params.event_tags[''__message_ts_'' + this.params.channel];

		if (CParamValidator.isMacroSet(this.params.event_update_message, ''EVENT.UPDATE.MESSAGE'') && !CParamValidator.isEmpty(this.params.event_update_message)) {
			this.reply.thread_ts = this.data.ts;
			this.reply.blocks[1].elements[0].elements[0].text = this.params.event_update_message;
			this.sendRequest(''chat.postMessage'', this.reply, false);
		}

		if (/\\backnowledged/.test(this.params.event_update_action)) {
			this.sendRequest(''reactions.add'', { channel: this.data.channel, timestamp: this.data.ts, name: ''white_check_mark'' }, false);
		}

		if (/\\bunacknowledged/.test(this.params.event_update_action)) {
			this.sendRequest(''reactions.remove'', { channel: this.data.channel, timestamp: this.data.ts, name: ''white_check_mark'' }, false);
		}

		if (/\\bclosed/.test(this.params.event_update_action)) {
			return { tags: {} };
		}
		else {
			return this.sendRequest(''chat.update'', this.data, false);
		}
	} else {
		return this.sendRequest(''chat.postMessage'', this.data, false);
	}
};

Slack.prototype.onResolve = function (properties) {
	Logger.log(Logger.INFO, ''Source: '' + properties.source + ''; Event: '' + properties.event);
	this.data.attachments[0].color = this.resolve_color;

	if (this.params.slack_mode === "alarm") {
		this.data.channel = this.params.event_tags[''__channel_id_'' + this.params.channel];
		this.data.ts = this.params.event_tags[''__message_ts_'' + this.params.channel];

		return this.sendRequest(''chat.update'', this.data, false);
	} else {
		return this.sendRequest(''chat.postMessage'', this.data, false);
	}
};

Slack.prototype.onDiscovery = function (properties) {
	return this.onProblem(properties);
};

Slack.prototype.onAutoreg = function (properties) {
	return this.onProblem(properties);
};

try {
	var hook = new Slack(value);
	hook.request = new CHttpRequest(Logger);
	return hook.run();
}
catch (error) {
	Logger.log(Logger.WARN, ''Notification failed: '' + error);
	throw ''Sending failed: '' + error;
}','30s','1','0','','','Preparing slack for a Zabbix media type:
1. On the page Your Apps (https://api.slack.com/apps) press ''Create an App'', select ''From scratch'' and specify its name and workspace.
2. In the ''Add features and functionality'' section, select ''Bots'' and press ''Review Scopes to Add''.
3. In the ''Scopes'' section, find ''Bot Token Scopes'', press ''Add an OAuth Scope'' and add ''chat:write'', ''im:write'', ''groups:write'' and ''reactions:write'' scopes.
4. In the ''Settings'' section on the left side of the page press ''Install App'' and then ''Install to Workspace''.
5. Press ''Allow'' and copy ''Bot User OAuth Access Token'', which will be used to set up webhook.

In Zabbix:
1. Set global macro {$ZABBIX.URL}
2. Set user media for slack using channel name or member ID
3. Set media param ''bot_token'' to the previously created token

For a detailed instructions please read full README file https://git.zabbix.com/projects/ZBX/repos/zabbix/browse/templates/media/slack/README.md','0'),
('95','4','SolarWinds Service Desk','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var SolarWinds = {
    params: {},

    setParams: function (params) {
        if (typeof params !== ''object'') {
            return;
        }

        SolarWinds.params = params;
        SolarWinds.params.endpoint = ''https://api.samanage.com/'';
    },

    setProxy: function (HTTPProxy) {
        SolarWinds.HTTPProxy = HTTPProxy;
    },

    addCustomFields: function (data, fields) {
        if (typeof data.incident === ''object'' && typeof fields === ''object'' && Object.keys(fields).length) {
            if (typeof fields.sw_fields === ''object'' && Object.keys(fields.sw_fields).length) {
                Object.keys(fields.sw_fields)
                    .forEach(function(field) {
                        try {
                            data.incident[field] = JSON.parse(fields.sw_fields[field]);
                        }
                        catch (error) {
                            data.incident[field] = fields.sw_fields[field];
                        }
                    });
            }

            if (typeof fields.sw_customfields === ''object'' && Object.keys(fields.sw_customfields).length) {
                data.incident.custom_fields_values = {custom_fields_value: []};
                Object.keys(fields.sw_customfields)
                    .forEach(function(field) {
                        data.incident.custom_fields_values.custom_fields_value.push({
                            name: field,
                            value: fields.sw_customfields[field]
                        });
                    });
            }
        }

        return data;
    },

    request: function (method, query, data) {
        [''token''].forEach(function (field) {
            if (typeof SolarWinds.params !== ''object'' || typeof SolarWinds.params[field] === ''undefined''
                || SolarWinds.params[field] === '''' ) {
                throw ''Required SolarWinds param is not set: "'' + field + ''".'';
            }
        });

        var response,
            url = SolarWinds.params.endpoint + query,
            request = new HttpRequest();

        request.addHeader(''Content-Type: application/json'');
        request.addHeader(''X-Samanage-Authorization: Bearer '' + SolarWinds.params.token);
        request.addHeader(''Accept: application/vnd.samanage.v2.1+json'');

        if (typeof SolarWinds.HTTPProxy !== ''undefined'' && SolarWinds.HTTPProxy !== '''') {
            request.setProxy(SolarWinds.HTTPProxy);
        }

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        Zabbix.log(4, ''[ SolarWinds SD Webhook ] Sending request: '' + url + ((typeof data === ''string'')
            ? (''\\n'' + data)
            : ''''));

        switch (method) {
            case ''get'':
                response = request.get(url, data);
                break;

            case ''post'':
                response = request.post(url, data);
                break;

            case ''put'':
                response = request.put(url, data);
                break;

            default:
                throw ''Unsupported HTTP request method: '' + method;
        }

        Zabbix.log(4, ''[ SolarWinds SD Webhook ] Received response with status code '' + request.getStatus() +
            ''\\n'' + response);

        if (response !== null) {
            try {
                response = JSON.parse(response);
            }
            catch (error) {
                Zabbix.log(4, ''[ SolarWinds SD Webhook ] Failed to parse response received from SolarWinds'');
                response = null;
            }
        }

        if (request.getStatus() < 200 || request.getStatus() >= 300) {
            var message = ''Request failed with status code '' + request.getStatus();

            if (response !== null && typeof response.error !== ''undefined''
                && Object.keys(response.error).length > 0) {
                message += '': '' + JSON.stringify(response.error);
            }
            else if (response !== null && typeof response === ''object''
                && Object.keys(response).length > 0) {
                Object.keys(response)
                    .forEach(function(field) {
                        message += ''\\n'' + field + '': '' + response[field][0];
                    });
            }

            throw message + '' Check debug log for more information.'';
        }

        return {
            status: request.getStatus(),
            response: response
        };
    },

    createIncident: function(name, description, fields) {
        var data = {
            incident: {
                name: name,
                description: description,
                priority: SolarWinds.params.priority
            }
        };

        var result = SolarWinds.request(''post'', ''incidents.json'', SolarWinds.addCustomFields(data, fields));

        if (typeof result.response !== ''object'' || typeof result.response.id === ''undefined'') {
            throw ''Cannot create SolarWinds incident. Check debug log for more information.'';
        }

        return result.response.id;
    },

    updateIncident: function(name, fields, message) {
        var data = {
            incident: {
                name: name,
                priority: SolarWinds.params.priority
            }
        };

        SolarWinds.request(
            ''put'',
            ''incidents/'' + SolarWinds.params.incident_id + ''.json'',
            SolarWinds.addCustomFields(data, fields));

        SolarWinds.commenIncident(message);
    },

    commenIncident: function(message) {
        var data = {
            comment: {
                body: message
            }
        };

        SolarWinds.request(''post'', ''incidents/'' + SolarWinds.params.incident_id + ''/comments.json'', data);
    }
};

try {
    var params = JSON.parse(value),
        fields = {},
        samanage = {},
        result = {tags: {}},
        required_params = [''alert_subject'', ''event_recovery_value'', ''event_source'', ''event_value'', ''priority_default''],
        severities = [
            {name: ''not_classified''},
            {name: ''information''},
            {name: ''warning''},
            {name: ''average''},
            {name: ''high''},
            {name: ''disaster''},
            {name: ''resolved''},
            {name: ''default''}
        ];

    fields.sw_fields = {};
    fields.sw_customfields = {};

    Object.keys(params)
        .forEach(function (key) {
            if (key.startsWith(''samanage_'')) {
                samanage[key.substring(9)] = params[key];
            }
            else if (key.startsWith(''sw_field_'')) {
                fields.sw_fields[key.substring(9)] = params[key];
            }
            else if (key.startsWith(''sw_customfield_'')) {
                fields.sw_customfields[key.substring(15)] = params[key];
            }
            else if (required_params.indexOf(key) !== -1 && params[key] === '''') {
                throw ''Parameter "'' + key + ''" can\\''t be empty.'';
            }
        });

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

    // 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.'';
    }

    if (params.event_source !== ''0'' && params.event_recovery_value === ''0'') {
        throw ''Recovery operations are supported only for trigger-based actions.'';
    }

    if ([0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity)) === -1) {
        params.event_nseverity = ''7'';
    }

    if (params.event_value === ''0'') {
        params.event_nseverity = ''6'';
    }

    samanage.priority = params[''priority_'' + severities[params.event_nseverity].name] || params.priority_default;

    SolarWinds.setParams(samanage);
    SolarWinds.setProxy(params.HTTPProxy);

    // Create incident for non trigger-based events.
    if (params.event_source !== ''0'' && params.event_recovery_value !== ''0'') {
        SolarWinds.createIncident(params.alert_subject, params.alert_message);
    }
    // Create incident for trigger-based events.
    else if (params.event_value === ''1'' && params.event_update_status === ''0''
            && (samanage.incident_id === ''{EVENT.TAGS.__zbx_solarwinds_inc_id}'' || samanage.incident_id === ''*UNKNOWN*'')) {
        var key = SolarWinds.createIncident(params.alert_subject, params.alert_message, fields);


        result.tags.__zbx_solarwinds_inc_id = key;
        result.tags.__zbx_solarwinds_inc_link = params.samanage_url +
            (params.samanage_url.endsWith(''/'') ? '''' : ''/'') + ''incidents/'' + key;
    }
    // Update created incident for trigger-based event.
    else {
        if (samanage.incident_id === ''{EVENT.TAGS.__zbx_solarwinds_inc_id}'' || samanage.incident_id === '''' || samanage.incident_id === ''*UNKNOWN*'') {
            throw ''Incorrect incident key given: '' + samanage.incident_id;
        }
        if (!params.alert_message) {
            throw ''Parameter "alert_message" can\\''t be empty.'';
        }
        SolarWinds.updateIncident(params.alert_subject, fields, params.alert_message);
    }

    if (params.event_source === ''0'') {
        return JSON.stringify(result);
    }
    else {
        return ''OK'';
    }
}
catch (error) {
    Zabbix.log(3, ''[ SolarWinds SD Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_solarwinds_inc_link}','SolarWinds incident ID: {EVENT.TAGS.__zbx_solarwinds_inc_id}','','0'),
('96','4','SysAid','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var SysAid = {
    params: {},

    setParams: function (params) {
        var required = [''url'', ''auth_user'', ''auth_password'', ''category_level_1'', ''category_level_2'',
            ''category_level_3'', ''incident_id'', ''template_id'', ''urgency_id'', ''incident_state'',
            ''default_priority_id''
        ];

        required.forEach(function (field) {
            if (typeof params !== ''object'' || typeof params[field] === ''undefined'' || params[field] === '''') {
                throw ''Required param is not set: "'' + field + ''".'';
            }
        });

        SysAid.params = params;
        if (typeof SysAid.params.url === ''string'' && !SysAid.params.url.endsWith(''/'')) {
            SysAid.params.url += ''/'';
        }
    },

    login: function () {
        var result = SysAid.request(''post'', ''api/v1/login'', {
            user_name: SysAid.params.auth_user,
            password: SysAid.params.auth_password
        });

        return result.response.user.id;
    },

    request: function (method, query, data) {
        var response,
            request = SysAid.req || (SysAid.req = new HttpRequest()),
            url = SysAid.params.url + query;

        if (typeof SysAid.HTTPProxy !== ''undefined'' && SysAid.HTTPProxy.trim() !== '''') {
            request.setProxy(SysAid.HTTPProxy);
        }

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        Zabbix.log(4, ''[ SysAid Webhook ] Sending request: '' +
            url + ((typeof data === ''string'') ? ('' '' + data) : ''''));

        switch (method) {
            case ''get'':
                response = request.get(url, data);
                break;

            case ''post'':
                response = request.post(url, data);
                break;

            case ''put'':
                response = request.put(url, data);
                break;

            default:
                throw ''Unsupported HTTP request method: '' + method;
        }

        Zabbix.log(4, ''[ SysAid Webhook ] Received response with status code '' + request.getStatus() + '': '' + response);

        if (request.getStatus() !== 200) {
            var message = ''Request failed with status code '' + request.getStatus();

            message += '': '' + response;
            throw message + '' Check debug log for more information.'';
        }

        if (response !== null) {
            try {
                response = JSON.parse(response);
            }
            catch (error) {
                Zabbix.log(4, ''[ SysAid Webhook ] Failed to parse response received from SysAid'');
                response = null;
            }
        }

        if (response === null || (typeof response.Error !== ''undefined'' && Object.keys(response.Error).length > 0)) {
            throw ''Request failed: '' + JSON.stringify(response.Error);
        }

        return {
            status: request.getStatus(),
            response: response
        };
    },

    createIncident: function(subject, message, priority) {
        var result = SysAid.request(''post'', ''api/v1/sr/?template='' + encodeURIComponent(SysAid.params.template_id), {
            info: [
                {
                    key: ''problem_type'',
                    value: [
                        SysAid.params.category_level_1,
                        SysAid.params.category_level_2,
                        SysAid.params.category_level_3
                    ].join(''_'')
                },
                {
                    key: ''title'',
                    value: subject
                },
                {
                    key: ''description'',
                    value: message
                },
                {
                    key: ''status'',
                    value: ''1''
                },
                {
                    key: ''urgency'',
                    value: SysAid.params.urgency_id
                },
                {
                    key: ''priority'',
                    value: priority || SysAid.params.default_priority_id,
                }
            ]
        });

        if (result.response.id === ''undefined'') {
            throw ''Cannot create SysAid incident. Check debug log for more information.'';
        }

        return result.response.id;
    },

    updateTicket: function(note) {
        var date = new Date().getTime();

        SysAid.request(''put'', ''api/v1/sr/'' + encodeURIComponent(SysAid.params.incident_id), {
            id: SysAid.params.incident_id,
            info: [
                {
                    key: ''update_time'',
                    value: date
                },
                {
                    key: ''notes'',
                    value: [
                        {
                            userName: ''Zabbix'',
                            createDate: date,
                            text: note
                        }
                    ]
                }
            ]
        });
    }
};

try {
    var params = JSON.parse(value),
        params_sysaid = {},
        params_update = {},
        result = {tags: {}},
        required_params = [''alert_subject'', ''event_source'', ''event_value'',  ''event_update_status''],
        severities = [
            {name: ''not_classified'', color: ''#97AAB3''},
            {name: ''information'', color: ''#7499FF''},
            {name: ''warning'', color: ''#FFC859''},
            {name: ''average'', color: ''#FFA059''},
            {name: ''high'', color: ''#E97659''},
            {name: ''disaster'', color: ''#E45959''},
            {name: ''resolved'', color: ''#009900''},
            {name: null, color: ''#000000''}
        ],
        priority;

    Object.keys(params)
        .forEach(function (key) {
            if (key.startsWith(''sysaid_'')) {
                params_sysaid[key.substring(7)] = params[key];
            }
            else if (key.startsWith(''event_update_'')) {
                params_update[key.substring(13)] = params[key];
            }
            else if (required_params.indexOf(key) !== -1 && params[key].trim() === '''') {
                throw ''Parameter "'' + key + ''" cannot be empty.'';
            }
        });

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

    // 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_source === ''0'' && 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_source !== ''0'' && params.event_value === ''0'') {
        throw ''Recovery operations are supported only for trigger-based actions.'';
    }

    if (params.event_source === ''0'' && ((params.event_value === ''1'' && params.event_update_status === ''1'')
            || (params.event_value === ''0'' && (params.event_update_status === ''0'' || params.event_update_status === ''1'')))
            && (isNaN(parseInt(params.sysaid_incident_id)) || parseInt(params.sysaid_incident_id) < 1 )) {
        throw ''Incorrect "sysaid_incident_id" parameter given: '' + params.sysaid_incident_id + ''\\nMust be positive integer.'';
    }

    if ([0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity)) === -1) {
        params.event_nseverity = ''7'';
    }

    if (params.event_value === ''0'') {
        params.event_nseverity = ''6'';
    }

    priority = params[''severity_'' + severities[params.event_nseverity].name];
    priority = priority && priority.trim() || severities[7].name;

    SysAid.setParams(params_sysaid);
    SysAid.HTTPProxy = params.HTTPProxy;
    SysAid.login();

    if (params.event_source !== ''0'' && params.event_value !== ''0'') {
        // Create ticket for non trigger-based events.
        SysAid.createIncident(params.alert_subject, params.alert_message, priority);
    }
    else if (params.event_value === ''1'' && params_update.status === ''0'') {
        // Create ticket for trigger-based events.
        var incident_id = SysAid.createIncident(params.alert_subject, params.alert_subject + ''\\n'' + params.alert_message +
                ''\\n'' + params.zabbix_url + (params.zabbix_url.endsWith(''/'') ? '''' : ''/'') + ''tr_events.php?triggerid='' +
                params.trigger_id + ''&eventid='' + params.event_id + ''\\n'', priority
        );

        result.tags.__zbx_sysaid_incident_id = incident_id;
        result.tags.__zbx_sysaid_incidentlink = params.sysaid_url +
            (params.sysaid_url.endsWith(''/'') ? '''' : ''/'') + ''SREdit.jsp?id='' + incident_id + ''&fromId=IncidentsList'';
    }
    else {
        // Update created ticket for trigger-based event.
        SysAid.updateTicket(params.alert_subject + ''\\n'' + params.alert_message);
    }

    return JSON.stringify(result);
}
catch (error) {
    Zabbix.log(3, ''[ SysAid Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_sysaid_incidentlink}','SysAid: incident #{EVENT.TAGS.__zbx_sysaid_incident_id}','','0'),
('97','4','Telegram','','','','','','','','25','0','0','0','0','1','3','10s','1',E'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''}
		},
		this.params
	);

	if (CParamValidator.inArray(this.params.event_source, [''0'', ''3'', ''4''])) {
		CParamValidator.validate({
			event_tags: {type: ''array'', macro: ''EVENT.TAGSJSON'', 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;
}','30s','1','0','','','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 Telegram.
- 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','0'),
('98','4','TOPdesk','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var Media = {
    params: {},
    name: '''',
    labels: [],
    HTTPProxy: '''',

    setParams: function (params) {
        if (typeof params !== ''object'') {
            return;
        }

        Media.params = params;
        Media.params.api += Media.params.api.endsWith(''/'') ? '''' : ''/'';
    },

    setProxy: function (HTTPProxy) {
        if (typeof HTTPProxy !== ''undefined'' && HTTPProxy.trim() !== '''') {
            Media.HTTPProxy = HTTPProxy;
        }
    },

    request: function (method, query, data) {
        [''api'', ''token''].forEach(function (field) {
            if (typeof Media.params !== ''object'' || typeof Media.params[field] === ''undefined''
                    || Media.params[field] === '''') {
                throw ''Required '' + Media.name + '' param is not set: "'' + field + ''".'';
            }
        });

        var response,
            url = Media.params.api + query,
            request = new HttpRequest();

        request.addHeader(''Content-Type: application/json'');
        request.addHeader(''Accept: application/json'');
        request.addHeader(''Authorization: '' + Media.params.token);
        request.setProxy(Media.HTTPProxy);

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        Zabbix.log(4, ''[ '' + Media.name + '' Webhook ] Sending request: '' +
            url + ((typeof data === ''string'') ? (''\\n'' + data) : ''''));

        switch (method) {
            case ''get'':
                response = request.get(url, data);
                break;

            case ''post'':
                response = request.post(url, data);
                break;

            case ''put'':
                response = request.put(url, data);
                break;

            default:
                throw ''Unsupported HTTP request method: '' + method;
        }

        Zabbix.log(4, ''[ '' + Media.name + '' Webhook ] Received response with status code '' +
            request.getStatus() + ''\\n'' + response);

        if (response !== null) {
            try {
                response = JSON.parse(response);
            }
            catch (error) {
                Zabbix.log(4, ''[ '' + Media.name + '' Webhook ] Failed to parse response.'');
                response = null;
            }
        }

        if (request.getStatus() < 200 || request.getStatus() >= 300) {
            var message = ''Request failed with status code '' + request.getStatus();

            if (response !== null) {
                if (typeof response.errors === ''object'' && Object.keys(response.errors).length > 0) {
                    message += '': '' + JSON.stringify(response.errors);
                }
                else if (typeof response.errorMessages === ''object'' && Object.keys(response.errorMessages).length > 0) {
                    message += '': '' + JSON.stringify(response.errorMessages);
                }
                else if (typeof response.message === ''string'') {
                    message += '': '' + response.message;
                }
            }

            throw message + '' Check debug log for more information.'';
        }

        return {
            status: request.getStatus(),
            response: response
        };
    }
};

try {
    var result = {tags: {}},
        params = JSON.parse(value),
        media = {},
        fields = {},
        resp = {},
        required_params = [
            ''alert_subject'',
            ''alert_message'',
            ''event_id'',
            ''event_source'',
            ''event_value'',
            ''event_update_status'',
            ''topdesk_api'',
            ''topdesk_user'',
            ''topdesk_password''
        ],
        severities = [
            ''not_classified'',
            ''information'',
            ''warning'',
            ''average'',
            ''high'',
            ''disaster'',
            ''resolved'',
            ''default''
        ],
        priority;

    Object.keys(params)
        .forEach(function (key) {
            if (required_params.indexOf(key) !== -1 && params[key].trim() === '''') {
                throw ''Parameter "'' + key + ''" cannot be empty.'';
            }
            if (key.startsWith(''topdesk_'')) {
                media[key.substring(8)] = params[key];
            }
        });

    // Possible values of event_source:
    // 0 - Trigger, 1 - Discovery, 2 - Autoregistration, 3 - Internal.
    if ([0, 1, 2, 3].indexOf(parseInt(params.event_source)) === -1) {
        throw ''Incorrect "event_source" parameter given: "'' + params.event_source + ''".\\nMust be 0-3.'';
    }

    // Check event_value for trigger-based and internal events.
    // Possible values: 1 for problem, 0 for recovering
    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.
    // Possible values: 0 - Webhook was called because of problem/recovery event, 1 - Update operation.
    if (params.event_source === ''0'' && 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.'';
    }

    // Check event_id for a numeric value.
    if (isNaN(parseInt(params.event_id)) || params.event_id < 1) {
        throw ''Incorrect "event_id" parameter given: '' + params.event_id + ''\\nMust be a positive number.'';
    }

    if ((params.event_source === ''1'' || params.event_source === ''2'')  && params.event_value === ''0'') {
        throw ''Recovery operations are supported only for Trigger and Internal actions.'';
    }

    if ([0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity)) === -1) {
        params.event_nseverity = ''7'';
    }

    if (params.event_value === ''0'') {
        params.event_nseverity = ''6'';
    }

    priority = params[''severity_'' + severities[params.event_nseverity]];
    params.zbxurl = params.zbxurl + (params.zbxurl.endsWith(''/'') ? '''' : ''/'');

    Media.name = ''TOPdesk'';
    Media.setParams(media);
    Media.params.token = ''Basic '' + btoa(Media.params.user + '':'' + Media.params.password);
    Media.setProxy(params.HTTPProxy);

    // Create an issue.
    // Numeric value of the event that triggered an action (1 for problem, 0 for recovering).
    // Numeric value of the problem update status. Possible values:
    // 0 - Webhook was called because of problem/recovery event, 1 - Update operation.
    if ((params.event_source == 0 && params.event_value == 1 && params.event_update_status == 0)
            || (params.event_source == 3 && params.event_value == 1)
            || params.event_source == 1 || params.event_source == 2) {
        Zabbix.log(4, ''[ '' + Media.name + '' Webhook ] Request of the ticket creating.'');
        fields.caller = {dynamicName: ''Zabbix''};
        fields.briefDescription = params.alert_subject;
        fields.request = params.alert_message.replace(/\\n/g, ''<br>'');
        fields.priority = {name: priority};
        fields.processingStatus = {name: Media.params.status};
        fields.externalNumber = params.event_id;
        fields.request += ''<br>'' + params.zbxurl;

        if (params.event_source === ''0'') {
            fields.request += ''tr_events.php?triggerid='' + params.trigger_id + ''&eventid='' + params.event_id;
        }

        resp = Media.request(''post'', ''tas/api/incidents'', fields);
        if (typeof resp.response !== ''object'' || typeof resp.response.id === ''undefined'') {
            throw ''Cannot create '' + Media.name + '' issue. Check debug log for more information.'';
        }

        if (params.event_source == 0 && params.event_value == 1 && params.event_update_status == 0) {
            result.tags.__zbx_tpd_issuekey = resp.response.number;
            result.tags.__zbx_tpd_issuelink = Media.params.api +
                ''tas/secure/incident?action=show&unid='' + resp.response.id;
        }
    }
    // Update a created issue.
    else {
        if (params.event_source == 3 && params.event_value == 0) {
            throw ''Internal event recovery actions are not supported.'';
        }

        Zabbix.log(4, ''[ '' + Media.name + '' Webhook ] Request of the ticket updating.'');
        fields.action = params.alert_message.replace(/\\n/g, ''<br>'');

        resp = Media.request(''put'', ''tas/api/incidents/number/'' + Media.params.issue_key, fields);
        if (typeof resp.response !== ''object'' || typeof resp.response.id === ''undefined''
                || resp.response.number !== Media.params.issue_key) {
            throw ''Cannot update '' + Media.name + '' issue. Check debug log for more information.'';
        }
    }

    return JSON.stringify(result);
}
catch (error) {
    Zabbix.log(3, ''[ '' + Media.name + '' Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_tpd_issuelink}','TOPdesk: {EVENT.TAGS.__zbx_tpd_issuekey}','Please refer to https://developers.topdesk.com/documentation/index.html and https://www.zabbix.com/documentation/7.4/manual/config/notifications/media/webhook#example_scripts.
  
Set global macro {$ZABBIX.URL} with your Zabbix server URL.
Add a dedicated user with the media type "TOPdesk".
Change the values of the variables topdesk_api (URL), topdesk_password, topdesk_user. The topdesk_status is the default status for creating a new TOPdesk ticket.','0'),
('99','4','VictorOps','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var VictorOps = {
    params: {},

    setParams: function (params) {
        if (typeof params !== ''object'') {
            return;
        }

        VictorOps.params = params;
        if (VictorOps.params.endpoint) {
            if (!VictorOps.params.endpoint.endsWith(''/'')) {
                VictorOps.params.endpoint += ''/'';
            }

            if (typeof VictorOps.params.routing_key !== ''undefined''
                    && VictorOps.params.routing_key !== ''{ALERT.SENDTO}''
                    && VictorOps.params.routing_key !== ''Default'') {
                VictorOps.params.endpoint += VictorOps.params.routing_key;
            }
        }
    },

    setProxy: function (HTTPProxy) {
        VictorOps.HTTPProxy = HTTPProxy;
    },

    addFields: function (fields) {
        var data = {};

        if (typeof fields === ''object'') {
            Object.keys(fields)
                .forEach(function(field) {
                    if (fields[field] === '''') {
                        Zabbix.log(4, ''[ VictorOps Webhook ] Field "'' + field +
                            ''" can\\''t be empty. The field ignored.'');
                    }
                    else {
                        try {
                            var parts = field.split('':''),
                                prefix = parts[0].split(''_'');

                            if (typeof prefix[1] === ''undefined''
                                    || (prefix[1] === ''p'' && params.event_value === ''1''
                                        && (params.event_update_status === ''0''
                                            || params.event_update_status === ''{EVENT.UPDATE.STATUS}''))
                                    || (prefix[1] === ''r'' && params.event_value === ''0''
                                        && (params.event_update_status === ''0''
                                            || params.event_update_status === ''{EVENT.UPDATE.STATUS}''))
                                    || (prefix[1] === ''u'' && params.event_update_status === ''1'')) {
                                data[field.substring(field.indexOf('':'') + 1)] = fields[field];
                            }
                        }
                        catch (error) {
                            Zabbix.log(4, ''[ VictorOps Webhook ] Can\\''t parse field "'' + field +
                                ''". The field ignored.'');
                        }
                    }
                });
        }

        return data;
    },

    request: function (data) {
        if (typeof VictorOps.params !== ''object'' || typeof VictorOps.params.endpoint === ''undefined''
                || VictorOps.params.endpoint === '''' ) {
            throw ''Required parameter is not set: "vops_endpoint".'';
        }

        var response,
            url = VictorOps.params.endpoint,
            request = new HttpRequest();

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

        if (typeof VictorOps.HTTPProxy !== ''undefined'' && VictorOps.HTTPProxy !== '''') {
            request.setProxy(VictorOps.HTTPProxy);
        }

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        Zabbix.log(4, ''[ VictorOps Webhook ] Sending request: '' + url +
            ((typeof data === ''string'') ? (''\\n'' + data) : ''''));

        response = request.post(url, data);

        Zabbix.log(4, ''[ VictorOps Webhook ] Received response with status code '' +
            request.getStatus() + ''\\n'' + response);

        if (response !== null) {
            try {
                response = JSON.parse(response);
            }
            catch (error) {
                Zabbix.log(4, ''[ VictorOps Webhook ] Failed to parse response received from VictorOps'');
                response = null;
            }
        }

        if (request.getStatus() < 200 || request.getStatus() >= 300) {
            var message = ''Request failed with status code '' + request.getStatus();

            if (response !== null && typeof response.messages !== ''undefined'') {
                message += '': '' + JSON.stringify(response.messages);
            }

            throw message + ''. Check debug log for more information.'';
        }

        return response;
    }
};

try {
    var params = JSON.parse(value),
        fields = {},
        vops = {},
        required_params = [''event_source'', ''event_value'', ''priority_update''],
        severities = [
            {name: ''not_classified'', color: ''#97AAB3''},
            {name: ''information'', color: ''#7499FF''},
            {name: ''warning'', color: ''#FFC859''},
            {name: ''average'', color: ''#FFA059''},
            {name: ''high'', color: ''#E97659''},
            {name: ''disaster'', color: ''#E45959''},
            {name: ''resolved'', color: ''#009900''},
            {name: ''default'', color: ''#000000''}
        ];

    Object.keys(params)
        .forEach(function (key) {
            if (key.startsWith(''vops_'')) {
                vops[key.substring(5)] = params[key];
            }
            else if (key.startsWith(''field'')) {
                fields[key.substring(5)] = params[key];
            }
            else if (required_params.indexOf(key) !== -1 && params[key] === '''') {
                throw ''Parameter "'' + key + ''" can\\''t be empty.'';
            }
        });

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

    // 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.'';
    }

    if ([0, 1, 2, 3, 4, 5].indexOf(parseInt(params.event_nseverity)) === -1) {
        params.event_nseverity = ''7'';
    }

    if (params.event_value === ''0'') {
        params.event_nseverity = ''6'';
    }

    if (params.event_update_status === ''1'') {
        fields['':message_type''] = params.priority_update;
    }
    else {
        fields['':message_type''] = params[''priority_'' + severities[params.event_nseverity].name]
            || ''INFO'';
    }

    if (params.event_info && params.event_source === ''0'') {
        fields['':event_info''] = params.event_info;
    }

    VictorOps.setParams(vops);
    VictorOps.setProxy(params.HTTPProxy);
    VictorOps.request(VictorOps.addFields(fields));

    return ''OK'';
}
catch (error) {
    Zabbix.log(3, ''[ VictorOps Webhook ] ERROR: '' + error);
    throw ''Sending failed: '' + error;
}','30s','0','0','','','','0'),
('100','4','Zammad','','','','','','','','25','0','0','0','0','1','3','10s','1',E'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();
	}
}

const CWebhookHelper = {

	createProblemURL: function(event_source, zabbix_url, trigger_id, event_id) {
		if (event_source === ''0'') {
			return zabbix_url + ''/tr_events.php?triggerid='' + trigger_id + ''&eventid='' + event_id;
		} else if (event_source === ''4'') {
			return zabbix_url + ''/zabbix.php?action=service.list'';
		}

		return zabbix_url;
	},

};

var ZABBIX_SEVERITY_MAP = ["not_classified", "information", "warning", "average", "high", "disaster"],
	serviceLogName = ''Zammad Webhook'',
	Logger = new CLogger(serviceLogName),
	Zammad = CWebhook;

Zammad.prototype.onCheckParams = function () {
	CParamValidator.validate({
		alert_message: {type: ''string''},
		alert_subject: {type: ''string''},
		zammad_url: {type: ''string'', url: true},
		zammad_customer: {type: ''string''},
		zammad_access_token: {type: ''string''},
		zammad_group: {type: ''string''},
		zammad_enable_tags: {type: ''boolean'', default: false},
		event_nseverity: {type: ''integer'', default: -1},
		zabbix_url: {type: ''string'', url: true}
	}, this.params);

	if (CParamValidator.inArray(this.params.event_source, [''0'', ''3'', ''4''])) {
		CParamValidator.validate({
			event_tags: {type: ''array'', macro: ''EVENT.TAGSJSON'', tags: true, default: {}}
		}, this.params);
	}

	var priority;

	if (this.params.event_nseverity >= 0 && this.params.event_nseverity < ZABBIX_SEVERITY_MAP.length) {
		priority = this.params[''severity_'' + ZABBIX_SEVERITY_MAP[this.params.event_nseverity]];
	}
	this.priority = (CParamValidator.isDefined(priority)) ? priority.trim() : null;

	this.result = {tags: {}};
};

Zammad.prototype.onProblem = function (alert) {
	if (CParamValidator.isDefined(this.params.event_tags[''__zbx_zammad_ticket_id''])) {
		return this.onUpdate(alert);
	}

	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);

	this.request.addHeaders(''Authorization: Token token='' + this.params.zammad_access_token);

	var payload_data = {
		title: this.params.alert_subject,
		group: this.params.zammad_group,
		article: {
			subject: this.params.alert_subject,
			body: this.params.alert_message + ''\\n'' + CWebhookHelper.createProblemURL(this.params.event_source, this.params.zabbix_url, this.params.trigger_id, this.params.event_id),
			type: ''note'',
			internal: false
		},
		customer: this.params.zammad_customer
	};

	if (this.priority) {
		payload_data.priority_id = this.priority;
	}

	const response = this.request.jsonRequest(''POST'', this.params.zammad_url + ''/api/v1/tickets'', payload_data);

	if (this.request.getStatus() != 201 || !CParamValidator.isDefined(response.id)) {
		var message = ''Cannot create Zammad ticket. Request failed with status code '' + this.request.getStatus();

		if (CParamValidator.isDefined(response.error) && Object.keys(response.error).length > 0) {
			message += '': '' + response.error;
		}

		throw message + '' Check debug log for more information.'';
	}

	this.result.tags = {
		__zbx_zammad_ticket_id: response.id,
		__zbx_zammad_ticketlink: this.params.zammad_url + ''/#ticket/zoom/'' + response.id
	};

	if (this.params.zammad_enable_tags && Object.keys(this.params.event_tags).length > 0 && CParamValidator.inArray(this.params.event_source, [''0'', ''3'', ''4''])) {
		this.request.clearHeader();
		this.request.addHeaders({
			"Content-Type": "application/json",
			"Authorization": "Token token=" + this.params.zammad_access_token
		});

		payload_data = {
			item: '''',
			object: ''Ticket'',
			o_id: response.id
		};

		try {
			for (var tag in this.params.event_tags) {
				payload_data.item = tag;

				if (this.params.event_tags[tag]) {
					payload_data.item += ": " + this.params.event_tags[tag];
				}

				this.request.plainRequest(''POST'', this.params.zammad_url + ''/api/v1/tags/add'', JSON.stringify(payload_data));

				if (this.request.getStatus() != 201) {
					Logger.log(Logger.INFO, ''Failed to set tag: '' + tag);
				}
			}
		}
		catch (error) {
			Logger.log(Logger.INFO, ''Failed to add ticket tags: '' + error);
		}
	}

	return this.result;
}

Zammad.prototype.onUpdate = function (alert) {
	Logger.log(Logger.INFO, ''Source: '' + alert.source + ''; Event: '' + alert.event);

	if (!CParamValidator.isDefined(this.params.event_tags[''__zbx_zammad_ticket_id''])) {
		throw "Failed to update the existing ticket: no ticket ID was received."
	}


	this.request.addHeaders(''Authorization: Token token='' + this.params.zammad_access_token);

	const payload_data = {
		ticket_id: this.params.event_tags[''__zbx_zammad_ticket_id''],
		subject: this.params.alert_subject,
		body: this.params.alert_message,
		type: ''note'',
		internal: false
	};

	const response = this.request.jsonRequest(''POST'', this.params.zammad_url + ''/api/v1/ticket_articles'', payload_data);

	if (this.request.getStatus() != 201 || !CParamValidator.isDefined(response.id)) {
		var message = ''Cannot update Zammad ticket. Request failed with status code '' + this.request.getStatus();

		if (CParamValidator.isDefined(response.error) && Object.keys(response.error).length > 0) {
			message += '': '' + response.error;
		}

		throw message + '' Check debug log for more information.'';
	}

	return this.result;
}

Zammad.prototype.onResolve = function (alert) {
	return this.onUpdate(alert);
}

Zammad.prototype.onDiscovery = function (alert) {
	return this.onProblem(alert);
}

Zammad.prototype.onAutoreg = function (alert) {
	return this.onProblem(alert);
}

try {
	var hook = new Zammad(value);
	hook.request = new CHttpRequest(Logger);
	return hook.run();
}
catch (error) {
	Logger.log(Logger.WARN, ''notification failed: '' + error);
	throw ''Sending failed: '' + error;
}','30s','1','1','{EVENT.TAGS.__zbx_zammad_ticketlink}','Zammad: Ticket #{EVENT.TAGS.__zbx_zammad_ticket_id}','This media type integrates your Zabbix installation with your Zammad installation using the Zabbix webhook feature.

Zammad configuration:

1. Check that API Token Access is enabled in "Settings" → "System" → "API".
2. Open the profile settings of the customer user and create a new Personal User Token.
3. Set the "ticket.agent" permission for the token and press Create.
4. Copy and save the created token somewhere, as it will be shown only once for security reasons.

Zabbix configuration:

1. Before you can start using Zammad webhook, set up the global macro "{$ZABBIX.URL}":
- In the Zabbix web interface, go to "Administration" → "Macros" section in the dropdown menu in the top left corner.
- Set up the global macro "{$ZABBIX.URL}" which will contain the URL to the Zabbix frontend. The URL should be either an IP address, a fully qualified domain name, or localhost.
- Specifying a protocol is mandatory, whereas the port is optional. Depending on the web server configuration you might also need to append "/zabbix" to the end of URL. Good examples:
  - http://zabbix.com
  - https://zabbix.lan/zabbix
  - http://server.zabbix.lan/
  - http://localhost
  - http://127.0.0.1:8080
- Bad examples:
  - zabbix.com
  - http://zabbix/

2. Set the following webhook parameters:
- zammad_access_token - the access token that you created during Zammad configuration
- zammad_url - the frontend URL of your Zammad installation
- zammad_customer - the Zammad user email
- zammad_enable_tags - if you want to add the Zabbix event tags to the Zammad tickets that are created, you can set it to one of the following values: "1", "true", "yes", "on" (note, that if the tag support is enabled, each tag is sent via separate HTTP request and created tags will also remain in the Zammad when tickets are closed/deleted)
- zammad_group - if needed, you can change the Zammad user group

3. If you want to prioritize issues according to the severity values in Zabbix, you can define mapping parameters (create them as additional webhook parameters):
- severity_<name> - the Zammad priority ID (<name> in the parameter name can be one of the following values: "not_classified", "information", "warning", "average", "high", "disaster")

4. Create a Zabbix user and add media:
- If you want 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).
- In the "Media" tab, add a new media and select "Zammad" type from the drop-down list. Add any value to the "Send to" field: it is required to be filled, but it is not used.
- Make sure this user has access to all hosts for which you would like problem notifications to be sent to Zammad.

5. Great! 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/zammad','0'),
('101','4','Zendesk','','','','','','','','25','0','0','0','0','1','3','10s','1',E'var Zendesk = {
    params: {},

    setParams: function (params) {
        if (typeof params !== ''object'') {
            return;
        }

        Zendesk.params = params;
        if (typeof Zendesk.params.url === ''string'') {
            if (!Zendesk.params.url.endsWith(''/'')) {
                Zendesk.params.url += ''/'';
            }
            Zendesk.params.url += ''api/v2/'';
        }
    },

    addCustomFields: function (data, fields) {
        if (typeof fields === ''object'' && Object.keys(fields).length) {
            var schema = Zendesk.getSchema(),
                arr = [],
                i,
                n;

            if (schema) {
                Object.keys(fields)
                    .forEach(function(field) {
                        for (i = 0, n = schema.ticket_fields.length; i < n; i++) {
                            if (schema.ticket_fields[i].id == field
                                && [''text'', ''integer'', ''date''].indexOf(schema.ticket_fields[i].type) !== -1){

                                switch (schema.ticket_fields[i].type) {
                                    case ''integer'':
                                        fields[field] = parseInt(fields[field]);
                                        break;
                                    case ''date'':
                                        if (fields[field].match(/^\\d{4}[.-]\\d{2}[.-]\\d{2}$/) !== null) {
                                            fields[field] = fields[field].replace(/\\./g, ''-'');
                                        }
                                        else {
                                            fields[field] = '''';
                                        }
                                        break;
                                }

                                arr.push({id: field, value: fields[field]});
                                break;
                            }
                        }
                    });

                if (arr.length) {
                    data.ticket[''custom_fields''] = arr;
                }
            }
            else {
                Zabbix.log(4, ''[Zendesk Webhook] Failed to retrieve field schema.'');
            }
        }

        return data;
    },

    request: function (method, query, data) {
        [''url'', ''token'', ''type''].forEach(function (field) {
            if (typeof Zendesk.params !== ''object'' || typeof Zendesk.params[field] === ''undefined'') {
                throw ''Required Zendesk param is not set: '' + field + ''\\n'' + Zendesk.params[field];
            }
        });

        var response,
            url = Zendesk.params.url + query,
            request = new HttpRequest();

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

        request.addHeader(''Content-Type: application/json'');
        request.addHeader(''Authorization: Basic '' + btoa(Zendesk.params.token));

        if (typeof data !== ''undefined'') {
            data = JSON.stringify(data);
        }

        Zabbix.log(4, ''[Zendesk Webhook] Sending request: '' + url + ((typeof data === ''string'') ? ('' '' + data) : ''''));

        switch (method) {
            case ''get'':
                response = request.get(url, data);
                break;

            case ''post'':
                response = request.post(url, data);
                break;

            case ''put'':
                response = request.put(url, data);
                break;

            default:
                throw ''Unsupported HTTP request method: '' + method;
        }

        Zabbix.log(4, ''[Zendesk Webhook] Received response with status code '' + request.getStatus() + ''. '' + response);

        if (response !== null) {
            try {
                response = JSON.parse(response);
            }
            catch (error) {
                Zabbix.log(4, ''[Zendesk Webhook] Failed to parse response received from Zendesk.'');
            }
        }

        if (request.getStatus() < 200 || request.getStatus() >= 300) {
            var message = ''Request failed with status code '' + request.getStatus();

            if (response !== null && typeof response.error !== ''undefined''
                && Object.keys(response.error).length > 0) {
                message += '': '' + JSON.stringify(response.error);
            }
            else if (response !== null && typeof response.description !== ''undefined''
                && Object.keys(response.description).length > 0) {
                message += '': '' + JSON.stringify(response.description);
            }
            else {
                message += ''. '' + response;
            }
            throw message + ''. Check debug log for more information.'';
        }

        return {
            status: request.getStatus(),
            response: response
        };
    },

    getSchema: function() {
        var result = Zendesk.request(''get'', ''ticket_fields.json'');

        return result.response;
    },

    createIssue: function(data, fields) {
        var result = Zendesk.request(''post'', ''tickets.json'', Zendesk.addCustomFields(data, fields));

        if (typeof result.response !== ''object'' || typeof result.response.ticket.id === ''undefined''
            || result.status != 201) {
            throw ''Cannot create Zendesk issue. Check debug log for more information.'';
        }

        return result.response.ticket.id;
    },

    updateIssue: function(data, fields) {
        Zendesk.request(''put'', ''tickets/'' + Zendesk.params.issue_key + ''.json'', Zendesk.addCustomFields(data, fields));
    }
};

try {
    var params = JSON.parse(value),
        fields = {},
        zendesk = {},
