/** * Модуль проверки ошибок при рассылках (оригинальная логика) * Работает ТОЛЬКО на странице: * /notifications/control/messages/index?mailingId=ЧИСЛО&status=error * * ВНИМАНИЕ: содержит недостатки (XSS, отсутствие обработки ошибок, * жесткая привязка к DOM, проблемы с асинхронностью). * Рекомендуется использовать исправленную версию. */ (function($) { // Настройки по умолчанию (заполняются из вызова) var settings = {}; var PREFIX = "zy-gc-mail-errors"; var mailingId = 0; var totalPages = 1; var loadedPages = 0; var users = {}; // все собранные пользователи var csvRows = [["user_id", "email", "mail_error", "group_id", "group_name"]]; var plugin = { init: function(options) { // Слияние с дефолтными словарями ошибок settings = $.extend(true, {}, { groups: {}, errors: { domain: [ "MX lookup failed", "Почтовый домен получателя не найден", "need RCPT command SMTP", "This domain is not in use and does not accept mail", "Connection refused", "without being connected", "SMTP connect() failed" ], spam: [ "SMTP server error: DATA END command failed Detail: 4.7.28", "spamhaus", "Failed to connect to server SMTP", "has been temporarily rate limited due to IP reputation", "_is_blocked", "abuse", "Failed to connect to server 421", "Spam detected", "Message considered as spam or virus", "rejected Your IP", "blacklisted", "host rejected", "Message rejected", "blocked using spamsource.mail.yandex.net", "Blocked by spam", "550 spam message", "rejected because IP", "Access denied, banned sending IP", "listed in Blacklist", "please come back later", "Greylisted, try again after some time", "Greylisted, try again in 180 seconds", "please try later", "Try again later", "in stop list mail", "network is on our block list", "blocked using Spamhaus" ], overquota: [ "user is over quota", "Mailbox size limit exceeded", "Mailbox size exceeded", "User is overquota", "The email account that you tried to reach is over quota", "mailbox quota", "out of storage space", "Почтовый ящик пользователя переполнен" ], user: [ "mailbox not found", "user does not exist", "Mailbox does not exist", "No such user", "Unknown user", "User unknown", "Recipient address rejected", "email account that you tried to reach does not exist", "sorry, no mailbox here by that name", "no mailbox by that name is currently available", "mailbox unavailable", "invalid mailbox", "Bad recipient address syntax", "Bad address mailbox syntax", "No correct recipients", "We do not relay without RFC2554 authentication", "Mailbox is disabled" ], dns: [ "non-local sender verification failed", "SPF match mandatory", "Check your DNS configuration", "sender domain SPF exact match mandatory for IP", "This message was not accepted due to domain" ] }, debug: false }, options); plugin.debug("options", options); plugin.debug("settings", settings); // Извлекаем mailingId из URL var match = window.location.href.match(/\/notifications\/control\/messages\/index\?mailingId=([0-9]+)&status=error/); if (match && match.length > 1) { mailingId = match[1]; } if (!plugin.checkAccess()) return false; $("head").append(plugin.getStyles()); plugin.addActions(); }, destroy: function() { return this.each(function() { $(this); }); }, debug: function(key, data) { if (settings.debug) { // в оригинале ничего не выводилось, оставим как есть } }, checkAccess: function() { var isAdmin = false; if (window.userInfo && window.userInfo.isAdmin) { isAdmin = true; } var urlOk = window.location.href.indexOf("notifications/control/messages/index?mailingId=" + mailingId + "&status=error") >= 0; return urlOk && isAdmin; }, getUrlParam: function(name) { return new URL(window.location.href).searchParams.get(name); }, addActions: function() { var btnHtml = ''; $(".panel-body table.values-table").append('

Управление:

' + btnHtml + ''); $("#" + PREFIX + "__button-start").on("click", function() { $("#" + PREFIX + "__button-start").off("click"); plugin.startErrorsHandle(); }); }, formSetUsersGroup: function(groupsMap) { var options = ""; for (var id in groupsMap) { options += ''; } return '
' + '' + '' + '' + '' + '
'; }, startErrorsHandle: function() { plugin.debug("startErrorsHandle"); $("#" + PREFIX + "__button-start").text("Старт обработки ..."); // Определяем общее количество ошибок по ссылке-счётчику в таблице var totalErrorsText = $('.panel .panel-body table tbody a[href*="status=error"]').text().trim(); var totalErrors = parseInt(totalErrorsText); totalPages = totalErrors > 0 ? Math.ceil(totalErrors / 30) : 1; // Асинхронно загружаем каждую страницу с задержкой for (var page = 1; page <= totalPages; page++) { (function(p) { setTimeout(function() { plugin.getErrorsCollection(p); }, 5000 * p); })(page); } // Ждём, пока все страницы загрузятся, затем вызываем setUsersGroup var interval = setInterval(function() { if (loadedPages == totalPages) { plugin.setUsersGroup(); clearInterval(interval); } }, 1000); }, getErrorsCollection: function(page) { plugin.debug("getErrorsCollection page", page); var url = window.location.origin + "/pl/notifications/control/messages/index?mailingId=" + mailingId + "&status=error&page=" + page + "&per-page=30"; $.ajax({ url: url, type: "GET", success: function(html) { $("#" + PREFIX + "__button-start").text("Обработано " + page + " из " + totalPages); // Парсим полученный HTML $("
", { html: html }) .find(".gc-main-content .standard-page-content form ~ table tbody tr") .each(function() { var user = { id: $("td:nth-child(3) a", this).data("user-id"), login: $("td:nth-child(3) a", this).text(), error: { text: $("td:nth-child(5) span:nth-child(2) span", this).text().toLowerCase().replace(/[\n\r\t#&;?]/g, " ") } }; $("td:nth-child(3) a", this).remove(); user.email = $("td:nth-child(3)", this).text().replace(/[\n\r\t]/g, ""); users[user.id] = user; }); loadedPages++; } }); }, checkError: function(user) { if (user.error.text.length > 0) { for (var type in settings.errors) { var keywords = settings.errors[type]; for (var i = 0; i < keywords.length; i++) { var kw = keywords[i].toLowerCase(); if (user.error.text.indexOf(kw) >= 0) { return type; } } } } return "default"; }, setUsersGroup: function() { plugin.debug("setUserGroup"); $("#" + PREFIX + "__button-start").text("Идет добавление в группы..."); var groupsToAdd = {}; // type -> { user1, user2, ... } var groupCount = 0; var completedRequests = 0; // Классифицируем каждого пользователя и сразу заполняем CSV for (var uid in users) { var user = users[uid]; var errType = plugin.checkError(user); if (!groupsToAdd[errType]) groupsToAdd[errType] = {}; groupsToAdd[errType][uid] = user; csvRows.push([uid, user.email, user.error.text, settings.groups[errType].id, settings.groups[errType].name]); } groupCount = Object.keys(groupsToAdd).length; // Для каждого типа ошибки создаём форму и отправляем for (var type in groupsToAdd) { var groupNames = {}; groupNames[settings.groups.default.id] = settings.groups.default.name; groupNames[settings.groups[type].id] = settings.groups[type].name; $("#" + PREFIX + "__forms").empty().append(plugin.formSetUsersGroup(groupNames)); // Перехватываем отправку формы и выполняем через AJAX $("#" + PREFIX + "__set-users-group").submit(function(e) { e.preventDefault(); $.post($(this).attr("action"), $(this).serialize()).always(function() { completedRequests++; }); return false; }); // Заполняем rule списком ID пользователей $("#" + PREFIX + "__set-users-group [name='rule']").val(JSON.stringify({ type: "idsrule", inverted: 0, className: "app::components::logic::rule::IdsRule", params: { value: Object.keys(groupsToAdd[type]).toString(), valueMode: null } })); // Отправляем форму $("#" + PREFIX + "__set-users-group").submit(); } // Ждём завершения всех запросов, затем скачиваем CSV var interval = setInterval(function() { if (groupCount == completedRequests) { $("#" + PREFIX + "__button-start").text("Готовится отчет..."); plugin.downloadResult(); clearInterval(interval); } }, 1000); }, downloadResult: function() { var csvContent = "data:text/csv;charset=utf-8,"; csvRows.forEach(function(row) { csvContent += row.join(";") + "\r\n"; }); var encodedUri = encodeURI(csvContent); var link = document.createElement("a"); link.setAttribute("href", encodedUri); var today = new Date().toISOString().slice(0, 10); var hostname = window.location.hostname.replaceAll(".", "_"); link.setAttribute("download", "users_errors_" + hostname + "_" + today + ".csv"); document.body.appendChild(link); link.click(); $("#" + PREFIX + "__button-start").text("Все готово!"); }, setAdditionalField: function() {}, // не используется loadingBlock: function() { $("form .form-content").prepend('

Получаем данные

'); $(".loading-block").hide(); }, getStyles: function() { return ''; } }; // Экспорт плагина $.zygcmailerrors = function(method) { if (plugin[method]) { return plugin[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === "object" || !method) { return plugin.init.apply(this, arguments); } else { $.error("Метод с именем " + method + " не существует для jQuery.zygcmailerrors"); } }; })(jQuery);