Подсвечиваем манипуляции и пропаганду на любом сайте
Пишем собственный лапшесниматель
Подсвечиваем манипуляции и пропаганду на любом сайте

В про­шлый раз мы зало­жи­ли фун­да­мент для новой бое­вой про­грам­мы: она помо­га­ет рас­кры­вать тек­сто­вые мани­пу­ля­ции, пси­хо­ло­ги­че­ское дав­ле­ние и про­па­ган­ду. Пока что наш про­дукт уме­ет толь­ко нахо­дить опре­де­лён­ные сло­ва на опре­де­лён­ной странице. 

Сле­ду­ю­щий шаг — научить­ся под­све­чи­вать мани­пу­ля­ции на любом сай­те. Мы возь­мём за осно­ву уже напи­сан­ный код и научим­ся делать его инъ­ек­цию в любую страницу. 

Что делаем

Помни­те наш ново­год­ний спец­про­ект про пада­ю­щие сне­жин­ки? Мы там напи­са­ли спе­ци­аль­ный код, кото­рый запус­ка­ет сне­жин­ки на любом сай­те. Логи­ка там была такая:

  1. Мы под­го­то­ви­ли скрипт, кото­рый запус­кал сне­жин­ки на локаль­ной странице.
  2. Доба­ви­ли в этот скрипт встав­ку CSS-стилей в доку­мент, что­бы скрипт сам раз­ме­стил сти­ли в раз­де­ле <head>.
  3. Напи­са­ли спе­ци­аль­ный код, кото­рый мож­но выпол­нить в кон­со­ли бра­у­зе­ра на любом сай­те и полу­чить такие же снежинки. 

Сего­дня сде­ла­ем то же самое с нашим лап­ше­сни­ма­те­лем. Сей­час он рабо­та­ет толь­ко на локаль­ной стра­ни­це, поэто­му нам нуж­но будет собрать скрипт так, что­бы его мож­но было выпол­нять на любой странице.

👉 Если бы мы тогда не сде­ла­ли про­ект со сне­жин­ка­ми, то мы бы сей­час дол­го раз­би­ра­лись с инжек­том JS-кода на про­из­воль­ную стра­ни­цу. Но так как у нас уже есть гото­вый код из того про­ек­та, это силь­но облег­ча­ет нам задачу.

Собираем скрипт

Что­бы всё зара­бо­та­ло, нам нуж­но выне­сти скрипт из HTML-страницы в отдель­ный файл. Возь­мём скрипт из про­шло­го про­ек­та и сохра­ним его как noodle.js.

Исходный код

// массив с регулярными выражениями
var r = [];
// заполняем элементы массива нашими выражениями
r[0] = /\B[бБ]ольш\W{2,4} +деньг\W{1,3}\B/;
r[1] = /\Bработа\W{1,4}где хо\W{2,7}\B/;
r[2] = /\B[пП]рямо сейчас\B/;
r[3] = /\B[рР]абота\W{0,3} ["«]на дядю["»]\B/;
r[4] = /\Bгорба\W{2,6}с[яь]\B/;
r[5] = /\B[вВ] любое время\B/;
r[6] = /\Bвсего +\d{1,3} +(минут|час)\D{0,3} в (день|неделю)\B/;
r[7] = /\B[шШ]кол\W\B/;
r[8] = /\B[пП]ассивн\W{1,3}\B/;
r[9] = /\B[нН]а +Бали\B/;
r[10] = /\BТаиланд\W{0,2}\B/;
r[11] = /\B[сС]истем\W{1,3} выгодно\B/;
r[12] = /\B[рР]абот\W{1,3} в декрете\B/;

// когда документ загрузился — начинаем работу
$(document).ready(function () {

  // функция, которая оборачивает найденное регулярное выражение в тег с подсветкой
  // в качестве аргументов мы передаём регулярное выражение и текст, который нужно обработать
  function highlightText(regular, originalHtml) {
      // переменная, в которую мы поместим итоговое значение
      var newHtml;
      // оборачиваем найденное значение в тег <span>
      // решение подсмотрели здесь: https://www.telerik.com/support/kb/aspnet-ajax/details/highlight-text-inside-html-elements-and-templates
      newHtml = originalHtml.replace(new RegExp(regular, "gi"), function replace(match) {
          return '<span class="highlight">' + match + '</span>';
      })
      // возвращаем результат обработки
      return newHtml;
  }

  // функция, которая подсвечивает текст в содержимом какого-то элемента на странице
  function lst(txt) {

    // перебираем по очереди все абзацы, которые есть на странице, используя возможности jQuery
    $(txt).each(function () {

      // по очереди проходим по массиву с регулярными выражениями
      for ( var z = 0; z < r.length; z++) {
        // на каждом шаге цикла берём содержимое очередного абзаца 
        var par = this.innerHTML;
        // и прогоняем его через подсветку каждого найденного регулярного выражения
        this.innerHTML = highlightText(r[z],par);
      }
    });
  };

  // подсвечиваем все слова внутри абзацев на странице
  lst('p');
});

Сей­час этот скрипт нахо­дит все абза­цы, поме­чен­ные тегом <p>, и под­све­чи­ва­ет най­ден­ный в них текст. Но текст на стра­ни­це может быть не толь­ко внут­ри абза­цев — его мож­но завер­нуть в бло­ки тегом <div> или про­сто напи­сать без тегов. 

Что­бы скрипт искал сов­па­де­ния по тек­сту на всей стра­ни­це, а не толь­ко в абза­цах, заме­ним в вызо­ве функ­ции lst() пара­метр p на body. Теперь мы смо­жем обра­ба­ты­вать содер­жи­мое всей стра­ни­цы, вклю­чая раз­де­лы меню и допол­ни­тель­ные бло­ки с дру­ги­ми материалами.

Вставляем стили

В нашем рас­по­ря­же­нии есть толь­ко этот скрипт, кото­рый дол­жен сам под­го­то­вить стра­ни­цу к под­свет­ке най­ден­ных слов. Для это­го мы идём в про­ект со сне­жин­ка­ми, смот­рим, как добав­ля­лись сти­ли в том коде и дела­ем то же самое в этом:

var style = document.createElement('style'); 
style.innerHTML = ` 
.highlight {
      background: #4CFF00;
    }
`; 
document.head.appendChild(style);

Этот код при запус­ке скрип­та доба­вит на стра­ни­цу стиль под­свет­ки с тем же назва­ни­ем, кото­рое исполь­зу­ет­ся в нашем скрипте.

Добавляем новые регулярные выражения

На стар­те мы сде­ла­ли корот­кий спи­сок регу­ля­рок что­бы быст­ро про­ве­рить код в рабо­те. Сей­час у нас есть вре­мя допол­нить его, что­бы код пре­вра­тил­ся из учеб­но­го в рабо­чий про­ект. Сло­ва для регу­ляр­ных выра­же­ний мы под­ре­за­ли у рос­сий­ских поли­то­ло­гов и инфо­биз­не­сме­нов. Навер­ня­ка в сле­ду­ю­щей вер­сии мы ещё прокачаемся:

Код

// массив с регулярными выражениями про заработок
var r = [];
// заполняем элементы массива нашими выражениями
r[0] = /\B[бБ]ольш\W{2,4} +деньг\W{1,3}\B/;
r[1] = /\Bработа\W{1,4}где хо\W{2,7}\B/;
r[2] = /\B[пП]рямо сейчас\B/;
r[3] = /\B[рР]абота\W{0,3} ["«]на дядю["»]\B/;
r[4] = /\Bгорба\W{2,6}с[яь]\B/;
r[5] = /\B[вВ] любое время\B/;
r[6] = /\Bвсего +\d{1,3} +(минут|час)\D{0,3} в (день|неделю)\B/;
r[7] = /\B[шШ]кол\W\B/;
r[8] = /\B[пП]ассивн\W{1,3}\B/;
r[9] = /\B[нН]а +Бали\B/;
r[10] = /\BТаиланд\W{0,2}\B/;
r[11] = /\B[сС]истем\W{1,3} выгодно\B/;
r[12] = /\B[рР]абот\W{1,3} в декрете\B/;
r[13] = /\Bвсему +науч\W{1,3}\B/;
r[14] = /\Bуста\W{1,2} +\W+ +в +офисе\B/;
r[15] = /\Bофисн\W{2,3} +планктон\W{0,2}\B/;
r[16] = /\Bмой +доход +составил\B/;
r[17] = /\Bменя +часто +спрашивают\B/;
r[18] = /\Bмногие +спрашивают\B/;
r[19] = /\Bработать +н[ие] +дня\B/;
r[20] = /\Bденежн\W{2,3} +поток\W?\B/;
r[21] = /\Bденежн\W{2,3} +энерги\W\B/;
r[22] = /\Bне +соль[ею]\W{3,5}\B/;
r[23] = /\Bна +нов\W{2,3} +уров\W{2,3}\B/;
r[24] = /\Bдоход +не +ограничен\B/;
r[25] = /\Bнеограниченн\W{2,3} +доход\B/;
r[26] = /\Bидти +к +своей +цели\B/;
r[27] = /\Bвс[её], +о +ч[её]м +(ты|вы) +мечтал\W\B/;
r[28] = /\Bсброс\W{1,4} +оковы\B/;
r[29] = /\Bофисн\W{2,4} +рабств\W\B/;

// массив с регулярными выражениями c пропагандой
var pr = [];
// заполняем элементы массива нашими выражениями
pr.push(/\Bвраг\W{0,3}\B/);
pr.push(/\Bвойн\W{1,3}\B/);
pr.push(/\Bбор\W\W?с[ья]\B/);
pr.push(/\Bмраз\W{0,3}\B/);
pr.push(/\Bгад\W{0,3}\B/);
pr.push(/\Bурод\W{0,3}\B/);
pr.push(/\Bпредател\W{0,3}\B/);
pr.push(/\Bиуд\W{0,3}\B/);
pr.push(/\Bпаразит\W{0,3}\B/);
pr.push(/\Bагент\W{0,3}\B/);
pr.push(/\Bметодичк\W{0,3}\B/);
pr.push(/\Bпо +указке\B/);
pr.push(/\Bвероломн\W{0,3}\B/);
pr.push(/\Bопуска\W{1,3}с[яь]\B/);
pr.push(/\Bпровальн\W{0,3}\B/);
pr.push(/\Bунизительн\W{0,3}\B/);
pr.push(/\Bчудовищн\W{0,3}\B/);
pr.push(/\Bварварск\W{0,3}\B/);
pr.push(/\Bзверск\W{0,3}\B/);
pr.push(/\B(об)?нищ\W{2,6}\B/);
pr.push(/\B(на|с) +колен\W{0,3}\B/);
pr.push(/\Bдави\W{2,4}\B/);
pr.push(/\Bподавля\W{1,4}\B/);
pr.push(/\Bвырва\W{1,4}\B/);
pr.push(/\Bуничтожи\W{1,4}\B/);
pr.push(/\Bрастопта\W{1,4}\B/);
pr.push(/\Bпроглоти\W{1,4}\B/);
pr.push(/\Bподстави\W{1,4}\B/);
pr.push(/\Bдоби\W{1,4}с[ья]\B/);
pr.push(/\Bдоби\W{1,4}\B/);
pr.push(/\Bнеудивительно\B/);
pr.push(/\Bв +то +время +как\B/);
pr.push(/\Bразумеетс[ья]\B/);
pr.push(/\B(мы|каждый) +(из +нас)? +долж\W{2,4}\B/);
pr.push(/\B(мы|каждый) +(из +нас)? +обяза\W{1,3}\B/);
pr.push(/\Bостава\W{1,4}с[ья] +в +стороне\B/);
pr.push(/\Bвсе +только +начинаетс[ья]\B/);
pr.push(/\Bсу\W{1,4} +котор\W{2,4}\B/);
pr.push(/\Bединственн\W{2,4}\B/);
pr.push(/\Bв +упор +не +виде\W{1,4}\B/);
pr.push(/\Bзакрыва\W{1,4} +глаза\B/);
pr.push(/\Bжестк\W{1,4}\B/);
pr.push(/\Bбескомпромиссн\W{1,4}\B/);
pr.push(/\Bбесцеремонн\W{1,4}\B/);
pr.push(/\Bмощн\W{1,4}\B/);
pr.push(/\Bрезонанс\W{0,4}\B/);
pr.push(/\Bэпопе\W{1,4}\B/);
pr.push(/\Bгеро\W{1,4}\B/);
pr.push(/\Bгероическ\W{1,4}\B/);
pr.push(/\B(с|на) +колен\W{0,4}\B/);
pr.push(/\Bподвиг\W{0,4}\B/);
pr.push(/\Bдолг\W{0,4}\B/);
pr.push(/\Bсвященн\W{1,4}\B/);
pr.push(/\Bдля +кажд\W{1,4}\B/);
pr.push(/\Bбезучастн\W{1,4}\B/);
pr.push(/\Bскорее +всего\B/);
pr.push(/\Bвероятнее +всего\B/);
pr.push(/\Bперспектив\W{0,3} +развития\B/);
pr.push(/\Bмы +как +страна\B/);
pr.push(/\Bнарод\W{0,2}\B/);
pr.push(/\Bобществ\W{1,4}\B/);
pr.push(/\Bдеградац\W{2,4}\B/);
pr.push(/\Bразвал\W{0,4}\B/);
pr.push(/\Bбез +лишнего +шума\B/);
pr.push(/\Bтак +называем\W{0,4}\B/);
pr.push(/\Bдно \B/);
pr.push(/\Bпросто\B/);
pr.push(/\Bпросто-напросто\B/);
pr.push(/\Bсотни +тысяч\B/);
pr.push(/\Bмиллионы\B/);
pr.push(/\Bдесятки +миллионов\B/);
pr.push(/\Bбудущ\W{2,4}\B/);
pr.push(/\Bпобед\W{1,4}\B/);
pr.push(/\Bсветл\W{2,4}\B/);
pr.push(/\Bскорее +всего\B/);
pr.push(/\Bвсе +стороны +(\W{5,15} )?жизни\B/);
pr.push(/\Bвсеобщ\W{2,4}\B/);
pr.push(/\Bвсе\B/);
pr.push(/\Bкаждый\B/);
pr.push(/\Bразвити\W{1,4} +страны\B/);
pr.push(/\Bнедаром\B/);
pr.push(/\Bмноголетн\W{2,4}\B/);
pr.push(/\Bмы +как +страна\B/);
pr.push(/\Bотсутствие +какого-либо\B/);
pr.push(/\Bкаки\W{1,3}-либо +перспектив\W{0,4}\B/);
pr.push(/\Bдостойн\W{2,4} +рол\W{1,3}\B/);
pr.push(/\Bдостоинств\W{1,4}\B/);
pr.push(/\Bгордос\W{1,4}\B/);
pr.push(/\Bсвобод\W{0,4}\B/);
pr.push(/\Bтотальн\W{1,4}\B/);
pr.push(/\Bмассовое +(\W{5,15} )?сознание\B/);
pr.push(/\Bпо-видимому\B/);
pr.push(/\Bнет +даже +подобия\B/);
pr.push(/\Bочевидно\B/);

Мы доба­ви­ли вто­рой мас­сив с новы­ми сло­ва­ми, кото­рые отве­ча­ют за про­па­ган­ду. Что­бы этот мас­сив тоже участ­во­вал в рабо­те, доба­вим в скрипт после пер­во­го цик­ла с про­вер­кой такой код со вто­рым циклом:

// по очереди проходим по второму массиву с регулярными выражениями
for ( var z = 0; z < pr.length; z++) {
  // на каждом шаге цикла берём содержимое очередного абзаца 
  var par = this.innerHTML;
  // и прогоняем его через подсветку каждого найденного регулярного выражения
  this.innerHTML = highlightText(pr[z],par);
}

Подготавливаем инжект-код

Сно­ва зале­зем в про­ект со сне­жин­ка­ми и посмот­рим, как это сде­ла­но там:

var js = document.createElement('script'); js.src = "https://thecode.local/wp-content/uploads/2019/12/snowfall2020.js"; document.body.appendChild(js);

Полу­ча­ет­ся, что в этом коде нам доста­точ­но поме­нять толь­ко путь к скрип­ту, а всё осталь­ное оста­нет­ся без изме­не­ний. Сохра­ня­ем скрипт в отдель­ный файл, зали­ва­ем его на наш учеб­ный хостинг и пишем пра­виль­ный путь:

var js = document.createElement('script'); js.src = "https://mihailmaximov.ru/projects/lapsha/noodle.js"; document.body.appendChild(js);

Тестирование

На неко­то­рых сай­тах при запус­ке из кон­со­ли мы видим такое:

Тестирование

Это зна­чит, что на сай­те не под­клю­чён jQuery и скрипт не зна­ет, что делать с пере­мен­ной $. Что­бы это испра­вить, сде­ла­ем инжект не из одной строч­ки кода, а из двух:

  1. Пер­вая строч­ка доба­вит на стра­ни­цу под­держ­ку jQuery.
  2. Вто­рая доба­вит наш скрипт и под­све­тит нуж­ные слова.

var js0 = document.createElement('script'); js0.src = "https://thecode.media/wp-content/uploads/2019/06/jquery.js"; document.body.appendChild(js0);
var js = document.createElement('script'); js.src = "https://mihailmaximov.ru/projects/lapsha/noodle.js"; document.body.appendChild(js);

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

Так­же есть малень­кий про­цент сай­тов, где наш код не сра­бо­та­ет и выдаст что-то такое:

Также есть маленький процент сайтов, где наш код не сработает и выдаст что-то такое

Это зна­чит, что сер­вер защи­ща­ет свои стра­ни­цы от инжек­та и не даёт выпол­нять на них чужой код. 

Примеры работы

Для нача­ла при­ме­ним инъ­ек­цию скрип­та на каком-нибудь явном испы­та­тель­ном стен­де. Сде­ла­ем стра­ни­цу с адо­во про­па­ган­дист­ским тек­стом и вста­вим в кон­соль наше заклинание:

var js0 = document.createElement('script'); js0.src = "https://thecode.media/wp-content/uploads/2019/06/jquery.js"; document.body.appendChild(js0);
var js = document.createElement('script'); js.src = "https://mihailmaximov.ru/projects/lapsha/noodle.js"; document.body.appendChild(js);

Резуль­тат:

Подсвечиваем манипуляции и пропаганду на любом сайте

Теперь в бое­вых усло­ви­ях: возь­мём эмо­ци­о­наль­ную, но не слиш­ком заря­жен­ную ста­тью док­то­ра эко­но­ми­че­ских наук Евге­ния Гонт­ма­хе­ра и вста­вим в кон­со­ли наше закли­на­ние. Результат: 

Подсвечиваем манипуляции и пропаганду на любом сайте

Вылез­ли про­бле­мы: регу­ляр­ки сра­ба­ты­ва­ют не на том, захва­ты­ва­ют не все нуж­ные сло­ва, неко­то­рые — толь­ко частич­но. Но уже сей­час неко­то­рые сло­ва под­све­чи­ва­ют­ся, это дви­же­ние в пра­виль­ном направлении. 

Так­же мы обна­ру­жи­ли, что скрипт сра­ба­ты­ва­ет с неко­то­рой задерж­кой: она свя­за­на с тем, что сна­ча­ла стра­ни­ца ждёт загруз­ки всех эле­мен­тов, в том чис­ле рекла­мы, скры­тых пик­се­лей и про­чей слу­жеб­ной мишу­ры, кото­рая в обыч­ной жиз­ни не меша­ла бы про­чи­тать ста­тью. Поэто­му после инъ­ек­ции в неко­то­рых слу­ча­ях нуж­но подо­ждать. Или же мы пере­кро­им инъ­ек­цию так, что­бы она сра­ба­ты­ва­ла мгновенно. 

Ещё всплы­ло, что наша раз­мет­ка может по-варварски разо­рвать суще­ству­ю­щую вёрст­ку стра­ни­цы: сло­мать меню, поме­шать рабо­те всплы­ва­ю­щих окон и дру­гих вещей, запро­грам­ми­ро­ван­ных на JS. Ну что ж… Мож­но было бы вер­нуть в код реа­ги­ро­ва­ние толь­ко на тек­сты внут­ри тега <p>, но это про­ти­во­ре­чит нашей задаче. 

Что значит этот результат

Наш скрипт под­све­чи­ва­ет сло­ва, на кото­рые сто­ит обра­тить вни­ма­ние при чте­нии любо­го эмо­ци­о­наль­но­го тек­ста. Например: 

  • Мно­го­лет­няя стаг­на­ция, застой и дегра­да­ция — по чьим оцен­кам? На осно­ва­нии каких дан­ных? Это лич­ные ощу­ще­ния авто­ра или ста­ти­сти­че­ски зна­чи­мые данные? 
  • Эпо­пея — по чьим оценкам? 
  • Ско­рее все­го — важ­но пони­мать, что это предположение.
  • Все­об­щее ощу­ще­ние, обще­ствен­ное вни­ма­ние — мани­пу­ля­ция через обоб­ще­ние. Очень раз­ных людей опи­сы­ва­ют каким-то одним сло­вом, как буд­то при­сва­и­вая им всем некое общее свойство. 

Эти сло­ва не зна­чат сами по себе, что перед вами мани­пу­ля­ция. Но если текст после инъ­ек­ции зажёг­ся как ново­год­няя ёлка, сто­ит про­чи­тать его осо­бен­но вни­ма­тель­но: обоб­ще­ния раз­об­щить, натяж­ки стя­нуть, пере­дёр­ги­ва­ния задёр­нуть, фак­ты отде­лить от эмоций. 

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

Текст и код:

Миха­ил Полянин

Редак­тор:

Мак­сим Ильяхов

Худож­ник:

Даня Бер­ков­ский

Кор­рек­тор:

Ири­на Михеева

Вёрст­ка:

Мария Дро­но­ва

Соц­се­ти:

Олег Веш­кур­цев