Skip to main content

Модуль корректировки опечаток в почтовых адресах

77.png

Автор: Evgeniy GCboost.ru

Большое количество пользователей вводит свои почтовые адреса с опечатками в домене почтового сервиса и соответственно не получают письма из рассылки. При этом находятся в базе getcourse и за них нужно ежемесячно платить.

По моему опыту это 15-20% пользователей известных почтовых сервисов. Или ~10% от всей базы. При базе в 100000 это уже потенциально 10000 тех кого мы привлекли, но они не получают письма и мы за них платим ~20000 рублей в месяц.

В неё входит сам код и информация по установке.

При необходимости, возможна услуга установки скрипта нашим специалистом (оплачивается отдельно).

Метод установки:

1. Загрузите файл в файловое хранилище getcourse.
2. Скопируйте путь к файлу
3. Перейдите в настройки аккаунта getcourse
4. Откройте вкладку «Настройки»
5. В текстовое поле «Счетчики и прочие скрипты для BODY» в самый конец добавьте скрипт и замените путь к файлу

332.jpg

Скрипт - сохранить в формате ".js"

/**
 * Plugin Name: ZygcFixEmailTypos
 * Plugin URI: https://gcboost.ru/shop/zygcfixemailtypos
 * Description: Исправление опечаток в почтовых адресах подписчиков в getcourse
 * Version: 1.1
 * Author: Evgeniy Nayden
 * Author URI: https://gcboost.ru/
 */
;(function(a,i){var l={},o="zy-gc-fix-email-typos",n={init:function(i){let o={debug:!1,access:"admin"};if(l=a.extend(!0,o,i),n.debug("options",i),n.debug("settings",l),!n.checkAccess())return!1;a("head").append(n.getStyles()),n.menuActionInject(),n.modalInject()},debug:function(a="key",i={}){l.debug&&console.log(a,i)},checkAccess:function(){n.debug("checkAccess",window.userInfo);let a=!1;return window.userInfo&&("user"===l.access&&window.userInfo.isInAccount&&(a=!0),"manager"===l.access&&window.userInfo.isManager&&(a=!0),"admin"===l.access&&window.userInfo.isAdmin&&(a=!0)),window.location.pathname.indexOf("pl/user/user")>=0&&a},menuActionInject:function(){n.debug("menuInject",l.menu),window.userInfo&&window.userInfo.isAdmin&&(a(".segment-construct-widget .dropdown-menu.segment-actions-menu").append('\n <li>\n <a href="javascript:void(0)" class="fix-email-typos">Исправить опечатки в адресах</a>\n </li>\n '),a(".dropdown-menu.segment-actions-menu .fix-email-typos").on("click",n.checkEmails))},newEmailInject:function(i,l){i.addClass(`${o}__change`).find("a").addClass(`${o}__notcorrect-email`),"levenshtein"===l.methods?i.append(`\n <div class="${o}__correct-email">\n Возможно: ${l.email}\n </div>\n <button class="btn btn-primary ${o}__complete-change" data-new-email="${l.email}">Заменить</button>\n `):i.append(`\n <div class="${o}__correct-email">\n${l.email}\n </div>\n <button class="btn btn-primary ${o}__complete-change" data-new-email="${l.email}">Заменить</button>\n `),a(`.${o}__change .${o}__complete-change`).on("click",n.modalFixEmailShow)},checkEmails:function(){const i=a('[data-sort="email"]').closest("th").data("col-seq");a(`table tr.gc-user-link[data-user-id] td[data-col-seq="${i}"]`).each(function(){const i=a(this).text().trim(),l=i.slice(i.indexOf("@")+1),o=n.listTypos();let m={};a:for(const a in o)for(const n in o[a])if(o[a][n]===l){m={findtypos:!0,methods:"manual",email:i.slice(0,i.indexOf("@")+1)+a};break a}0==Object.keys(m).length&&(m=n.checkByLevenshtein(i,l)),m.findtypos&&n.newEmailInject(a(this),m)})},checkByLevenshtein:function(a,i){const l=["mail.ru","yandex.ru","gmail.com","rambler.ru","list.ru","bk.ru","inbox.ru","yahoo.com","hotmail.com","inbox.lv","icloud.com","outlook.com","tut.by","ya.ru"];let o,m=-1;for(vd in l){let a=n.levenshteinDistance(i,l[vd]);if(0==a){o=l[vd],m=0;break}(a<=m||m<0)&&(o=l[vd],m=a)}return m>0&&m<4?{findtypos:!0,methods:"levenshtein",email:a.slice(0,a.indexOf("@")+1)+o}:{findtypos:!1,email:a}},levenshteinDistance:function(a,i){if(!a.length)return i.length;if(!i.length)return a.length;const l=[];for(let o=0;o<=i.length;o++){l[o]=[o];for(let n=1;n<=a.length;n++)l[o][n]=0===o?n:Math.min(l[o-1][n]+1,l[o][n-1]+1,l[o-1][n-1]+(a[n-1]===i[o-1]?0:1))}return l[i.length][a.length]},modalInject:function(){let i=`\n <div class="modal zy-gc-modal" id="${o}__modal">\n <div class="modal-dialog" style="width: 50%;">\n <div class="modal-content">\n  <div class="modal-body">\n <div class="gc-modal-content" id="gc-modal-content-1">\n  <div>\n  <input type="text" value="" data-user-id="" class="email-val">\n  <div class="confirmation"></div>\n  </div>\n  <br>\n  <div>\n  <button class="btn btn-primary save-new-email" onclick="return false;">Сохранить</button> \n  или <a href="javascript:void(0);" class="cancel">отменить редактирование</a>\n  </div>\n </div>\n  </div>\n  <div class="close-btn">\n <i class="fa fa-times"></i>\n  </div>\n </div>\n </div>\n </div>\n`;a("body").append(i),a(`#${o}__modal`).find(".save-new-email").on("click",function(){n.fixEmail()}),a(`#${o}__modal`).find(".cancel").on("click",function(){a(`#${o}__modal`).hide()}),a(`#${o}__modal`).find(".close-btn").on("click",function(){a(`#${o}__modal`).hide()})},modalFixEmailShow:function(i){i.preventDefault(),i.stopPropagation();const l=a(i.target).closest("tr.gc-user-link").data("user-id"),n=a(i.target).data("new-email"),m=a(`#${o}__modal`);m.find("input.email-val").val(n).data("user-id",l),m.show()},modalFixEmailHide:function(i){const l=a(`#${o}__modal`);l.hide(),l.find(".confirmation").empty(),l.find(".email-val").val("").data("user-id","")},fixEmail:function(){const i=a(`#${o}__modal`),l=a(`#${o}__modal`).find(".email-val").data("user-id"),m=a(`#${o}__modal`).find(".email-val").val(),e=i.find(".confirmation");ajaxCall("/pl/user/user/set-email",{email:m,user_id:l},{btn:i.find(".save-new-email")},function(i){if(i.data.user_exists){var r=i.data.user;e.html(['Пользователь с таким email уже существует (<a href="'+r.card_link+'" target="_blank">'+r.name+"</a>), он будет удален, продолжить?",'<br /><button class="btn btn-danger save" onclick="return false;">Да</button> <a href="javascript:void(0);" class="fix-email-cancel">Нет</a>'].join(" ")),e.find("button").click(function(){ajaxCall("/pl/user/user/set-email",{email:m,user_id:l,override:1},{btn:e.find("button")},function(i){i.data.finished&&(a.toast("email успешно изменен",{type:"success"}),n.modalFixEmailHide(l),a(`table tr.gc-user-link[data-user-id="${l}"] .${o}__complete-change`).remove())})}),e.find("a").click(function(){n.modalFixEmailHide(l)})}else i.data.finished?(a.toast("email успешно изменен",{type:"success"}),n.modalFixEmailHide(l),a(`table tr.gc-user-link[data-user-id="${l}"] .${o}__complete-change`).remove()):n.modalFixEmailHide(l)})},getStyles:function(){return`\n <style>\n form .form-content {\n position: relative;\n }\n .${o}__change {\n background-color: #f3ec8f;\n }\n .${o}__correct-email {\n color: #0e990e;\n }\n .${o}__notcorrect-email {\n color: #d62c2c;\n }\n .${o}__complete-change {\n margin-top: 5px;\n }\n #${o}__modal {\n background-color: rgba(0,0,0,.6);\n }\n #${o}__modal .modal-dialog {\n height: 100vh;\n display: flex;\n align-items: center;\n }\n #${o}__modal .modal-content {\n width: 100%;\n padding: 10px;\n }\n #${o}__modal .email-val {\n padding: 10px 15px;\n width: 100%;\n }\n #${o}__modal .close-btn {\n position: absolute;\n top: -12px;\n right: -12px;\n font-size: 21px;\n background-color: #ccc;\n width: 30px;\n height: 30px;\n display: flex;\n justify-content: center;\n border-radius: 50%;\n align-items: center;\n cursor: pointer;\n }\n </style>\n`},listTypos:function(){return{"yahoo.com":["ahoo.com","yahoo.com.au","yahoo.come","yaoo.com","yhoo.com","yahoo.ru","hahoo.com","yhaoo.com","yachoo.com","uahoo.com","yahoo.clm","yahoo.mail","yagoo.com","yooho.com","yahoo.con","yahooo.com","yahho.com","yahoo.no","yahoo.ca","yahooc.com","yahnoo.com","yaho.com","yahoo.es","yahoi.com"],"yandex.ru":["ayndex.ru","yabdex.ru","yadex.ru","yandes.ru","yandex.com","yandex.ri","yandex.run","yandex.ruru","yandex.ruu","yandex.ry","yandex.u","yandx.ru","yanex.ru","yndex.com","jandex.ru","yndex.ry","yaandex.ru","yandex.comru","yandex.be","yandex.con","yander.ru","yanfex.ru","ayndex.ua","yandex.yru","yanbex.ru","yandexn.ru","yzndex.ru","yandex.bu","yngex.ru","yandeyx.ru","jandex.ua","yanderx.ru","yndexc.ru","yanlex.ru","yaqndex.ru","yandex.ru.ru","yandez.ru","yandex.rru","yxandex.ru","yand.ru","yandexter.ru","yanked.ru","yunxex.ru","yandex.eu","yardex.ry","yandex.ti","yandexd.ru","yandex.ruas","uandex.ru","yande.ru","yundex.ru","ysndex.ru","yandekx.ru","zandex.ru"],"bk.ru":["bk.com","dk.ru","bk.r","bk.ry","br.ru","bk.u","bk.ruu","bk.tu","bl.ru","bk.ri","bc.ru"],"icloud.com":["incloud.com","icloud.con","cloud.com","icloud.ru","iclod.com","icloud.c","icould.com","icloug.com","84icloud.com","iklood.com"],"gmail.com":["g.mail.com","gail.com","gamil.com","gimail.com","gma.com","gmaik.com","gmail.cim","gmail.cjm","gmail.co.il","gmail.com.il","gmail.comcom","gmail.come","gmail.coming","gmail.con","gmail.coom","gmail.cpm","gmail.kom","gmail.om","gmail.vom","gmail.xom","gmaill.com","gmal.com","gmale.com","gmali.com","gmall.com","gmeil.ru","gnail.com","gnail.con","qmail.com","qmail.ru","ymail.com","google.com","googlemail.ru","gmail.ru","amail.com","qmail.c","gmail.comj","gmail.ckm","inbox.rum","gmall.co","com.gmail","gmail.colm","gmail.hu","gmiail.com","gmail.tu","gmail.ua","gmail.comv","qmail.kom","gmail.cyom","gmail.cvom","gmail.ri","gmail.cov","gmail.col","gnail.cjm","com.gmall","gmajl.com","gjmeil.com","cmail.cjm","g-mail.com","gmi.com","imail.com","gmqil.om","quail.com","email.com","gmail.c","gmail.no","gmil.clm","gmail.ch","gmlil.com","ngmail.com","gmai.cot","gvfil.com","atgmail.com","gmail.rum","gimail.kom","gmail.com.b","emeil.com","gmail.gom","gmail.comm","gmail.c.com","gmail.comnk","gmail.net","gmail.lv","gmali.vom","gmail.gr","g.mail","gmail.it","gmail.com.ru","gmail.co.com","gmail.lt","maul.com","mail.gom","googlmail.com","gmail.comr"],"hotmail.com":["hmail.com","hotmail.be","hotmail.co","hotmal.com","hotmail.ru","hotmail.cim","hotmail.kom","hotmail.ry","hotmail.cov","hotmail.con","htmailm.com","hotmai.com","hotmail.it","hotmail.de"],"inbox.ru":["imbox.ru","inbix.ru","indox.ru","inox.ru","inbox.com","inbox.by","lnbox.ru","inbiox.ru","onbox.lv","inboh.ru","inbux.ru","ibox.pu","inboux.r","inbox.r","inboz.lt","inbvox.lv","inbox.lc","ibbox.lv","in.box.ru","inbx.ru","insbox.ru","unbox.ru","inbo.ru","inbox.ri"],"inbox.lv":["inbo.lv","nbox.lv","indox.lv","inbox.kv","inboc.lv","inbokx.lv","invox.lv","inbox.iv","inboks.lv","inbox.lt","ibox.v"],"list.ru":["ist.ru","lis.ru","list.r","list.ry","list.tu","list.u","lust.ru","lisr.ru","list.rum","iist.ru","llist.ru","listt.ru","liast.ru","list.am"],"mail.ru":["mael.ru","mai.lru","mai.ru","maii.ru","maij.ru","mail.bk","mail.pu","mail.r","mail.rb","mail.ri","mail.rj","mail.rui","mail.ry","mail.rz","mail.th","mail.tu","mail.u","mail.ur","maill.ru","mailr.ru","mal.ru","mall.ru","maul.ru","maul.ry","meil.ru","mil.ru","vail.ru","nail.ru","mail.rup","mailrr.ru","mair.ru","mail.run","mzil.ru","mail.rue","mail.uu","mail.rou","mail.ruf","mail.fu","mapl.ru","mail.rul","mail.rus","maeil.ru","muil.ru","mal.rui","maji.ru","email.ru","maisd.ru","meyl.ru","mail.rua","mail.bg","mail.eru","mailb.ru","maol.ru","mial.ru","mail.fr","maqil.ru","mail.ro","mail.ruy","mqail.ru","maqi.ru","mail.cru","e-mail.ru","mail.rh","mail.ca","mal.ry","lmail.ru","mail.rn","maail.ru","ail.ru","mail.comru","mqil.ru","mfil.ru"],"rambler.ru":["rambler.com","rambler.ry","rambler.u","ramblet.ru","ramdler.ru","ramler.ru","rmbler.ru","rambles.ru","rambler.ri","lrambler.ru","qrambler.ru","rfmbler.ru","ramblerl.ru","rambler.tu","rambjer.ru","rambker.ru","rambler.ruk","raqmbler.ru","rambler.r","ramblr.ru"],"ya.ru":["ya.ry","ya.u","ay.ru","ya.by"],"tut.by":["tut.b","tu.by","tyt.by","tut.bu","tu.by","tut.dy","tut.bv","tut.byby","tuit.by","tut.bt"],"outlook.com":["outlook.ru","outlook.by"]}}};a.zygcfixemailtypos=function(i){return n[i]?n[i].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof i&&i?void a.error("Метод с именем "+i+" не существует для jQuery.zygcfixemailtypos"):n.init.apply(this,arguments)}})(jQuery);

Мини инструкция

1. Загрузите файл zy-gc-fix-email-typos.mi.js к себе на хостинг или в файловое хранилище getcourse.
2. Скопируйте путь к файлу
3. Перейдите в настройки аккаунта getcourse
4. Откройте вкладку "Настройки"
5. В текстовое поле "Счетчики и прочие скрипты для BODY" в самый конец добавьте:
<!-- Модуль помощи в исправлении опечаток -->
<script src="путь/к/файлу/zy-gc-fix-email-typos.js"></script>
<script>$.zygcfixemailtypos();</script>
<!-- / Модуль помощи в исправлении опечаток -->

Проблема

Большое количество пользователей вводит свои почтовые адреса с опечатками в домене почтового сервиса и соответственно не получают письма из рассылки. При этом находятся в базе getcourse и за них нужно ежемесячно платить.

По моему опыту это 15-20% пользователей известных почтовых сервисов. Или ~10% от всей базы. При базе в 100000 это уже потенциально 10000 тех кого мы привлекли, но они не получают письма и мы за них платим ~20000 рублей в месяц.

image.png

Варианты?

Их в целом 2:

  1. Делать валидацию формы регистрации
  2. Ручное исправление уже существующих пользователей

В данном модуле реализуется второй вариант. Первый вариант реализован в другом модуле.

Решение

Поиск пользователей с опечатками в доменах известных почтовых сервисов и их исправление.

Для больших школ это нужно делать ежедневно, для маленьких 1-2 раза в неделю.

Примерная схема работы для технического специалиста такая:

  1. Выбрать тех кто зарегистрировался вчера и не получил письмо
  2. Пройтись взглядом по все адресам
  3. Если увидели опечатку в домене:
    1. открыть профиль пользователя
    2. провести замену почты

Проблемы

Сразу образуется 3 проблемы:

  1. Затраты времени технического специалиста
  2. Пропускаются взглядом некоторые пользователи с опечатками
  3. Технический специалист может не знать большого количества популярных почтовых сервисов

Так же необходимо учитывать, что реализовать полностью автоматическую проверку и корректировку очень сложно, так как существуют варианты при которых пользователя с опечаткой менять не стоит и решать это должен человек.

Реализация

Для того, чтобы сократить временные затраты технического специалиста и помочь ему не пропустить опечатку — был реализован модуль для getcourse.

Схема работы после подключения такая:

  • Делаем выборку (любую)
  • В меню действий добавляется пункт «Исправить опечатки в адресах»
  • Скрипт проходится по каждому пользователю и выделяет тех у кого опечатка
  • Предлагает вариант для замены и кнопку замены
  • При клике по кнопке замены выходит модальное окно с подставленным вариантом, который можно скорректировать вручную
  • После подтверждения почта пользователя заменяется на указанную в модальном окне
  • Если пользователь с такой почтой уже есть то предлагается его удалить или оставить все без изменений

  1. Выбираем действия
  2. Выбираем пункт «Исправить опечатки в адресах»
  3. Видим опечатку
  4. Вариант для замены
  5. Кнопка замены с выводом модального окна
  6. Еще пользователь с опечаткой
  7. Указан вариант возможной замены. Он работает по формуле расчета расстояния Левенштейна и указывает на возможные опечатки до 3 символов. Например: вместо gmail.com → gnaill.con

Модальное окно:


Подставляется почта с предложенным вариантом, который можно тут же вручную отредактировать под другой и вариант сохранения или отмены.

Если пользователь с указанной почтой уже существует то модуль укажет на это и предложит либо удалить пользователя либо отменить замену:


Backlog

  • Добавить больше вариантов явных опечаток.
  • Добавить больше вариантов почтовых сервисов.
  • Реализовать подсвечивание адресов со временными (однодневными) почтовыми адресами.
  • Реализовать схему проверки опечаток в почтовых адресах с собственными доменами через пинг по mx зоне.