Простейший генератор креативного текста

Без нейросетей, регистрации и СМС.

Настало время крутых проектов!

Представьте, что вы запускаете рассылку в крупной компании, получили уже за это много денег и вам хочется как-то её разнообразить. И вы решаете сделать так, чтобы человеку в ответ приходило письмо-благодарность. Но не одно письмо на всех, а несколько разных, чем больше — тем лучше.

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

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

Алгоритм работы генератора текста будет такой:

  1. Готовим HTML-страницу, где это всё будет работать и запускаться.
  2. Задаём структуру текста на уровне абзацев: приветствие, само письмо, заключение.
  3. Пишем шаблоны для каждого из этих элементов.
  4. Случайным образом выбираем, каким тоном будем общаться — официальным или нет.
  5. Собираем всё письмо в одно целое.
  6. Заменяем шаблонные слова на нужные фразы и словосочетания.
  7. Показываем результат.

Делаем HTML-страницу

Берём за основу стандартный шаблон и добавляем в него:

  • jQuery, чтобы обращаться к элементам страницы по имени;
  • файл скрипта mail.js, в котором будет происходить вся магия JS-кода;
  • стили для элементов страницы, чтобы она выглядела опрятно;
  • блок, куда будем выводить готовое письмо;
  • кнопку, которая по нажатию будет генерировать новый текст.

Всё это мы уже умеем делать, поэтому просто собираем код:

<!DOCTYPE html>
<html>
<!-- служебная часть -->
<head>
  <!-- заголовок страницы -->
  <title>Генератор писем</title>
  <!-- настраиваем служебную информацию для браузеров -->
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- подключаем jQuery -->
  <script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js'></script>
  <!-- подключаем наш скрипт -->
  <script src="mail.js"> </script>
  <!-- настраиваем внешний вид блока для готового письма и кнопки -->
  <style type="text/css">
    .text {
      font-size: 20px;
      width: 80%;
      margin: 30px;
    }
    .controls button {
      font-size: 20px;
      margin-left: 30px;
    }
  </style>
  <!-- закрываем служебную часть страницы -->
</head>
<body>
  <!-- в этом блоке будем выводить готовое письмо -->
  <div class="text" id="text_here"></div>
  <!-- блок с кнопкой, по нажатию на которую запускается нужная функция в скрипте -->
  <div class="controls">
    <button value="Получить новый текст" onclick="get_text();">Получить новый текст</button>
  </div>
</body>
<!-- конец всей страницы -->
</html>
Готовая страница с кнопкой. Текст появится на следующем этапе.

Готовим структуру письма и шаблоны для текста

Теперь нам понадобится отдельный файл для скрипта, который и будет отвечать за генерацию писем — mail.js. Создаём его в той же папке, что и HTML-документ, и начинаем программировать.

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

Для этого мы будем использовать шаблонные слова, у которых с обеих сторон стоят знаки доллара, например, $somebody$. Эти шаблонные слова будут означать для нашего алгоритма, что на их месте появится какое-то конкретное слово или целое предложение. Это мы сами решили, что шаблоны будут работать со знаками доллара — можно было выбрать и другие знаки.

Пример: зададим по шаблону предложение «Я $love$ $you$». Чтобы получилась осмысленная фраза, мы должны задать значения для обоих шаблонных слов. Сделаем это так:

  • пусть $love$ принимает значения «тебя», «вас», «нас», «их», «всех»;
  • пусть $you$ принимает значения «люблю», «ненавижу», «не люблю», «рад видеть».

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

  • Я их люблю.
  • Я тебя ненавижу.
  • Я вас не люблю.
  • Я всех люблю.
  • Я нас рад видеть.

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

Чтобы тексты получались разнообразными, можно сделать так, чтобы шаблон выбирался в зависимости от какой-то переменной. Именно так мы сделаем выбор интонации письма, официальную или неформальную.

Начало

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

Интересный момент — внутри массивов a_text и b_text мы использовали шаблонное слово $somebody$. Алгоритм сначала подставит предложение вместе с этим шаблонным словом, а вторым проходом — заменит его на конкретное значение.

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

// шаблон начала письма
var intro = ['$a_intro$', '$b_intro$ '];
// шаблон тела письма
var text = ['$a_text$', '$b_text$'];
// шаблон концовки письма
var outro = ['$a_outro$', '$b_outro$'];
// задаём набор фраз и слов для всех шаблонов сразу
var text_obj = {
  //структуру письма пока оставляем пустой
  structure: [
    ''
  ],
  // текст для начала
  a_intro: ['Здравствуйте.', 'Добрый день!'],
  b_intro: ['Привет!', 'Хэллоу!', 'Бонжур!'],
  // варианты основного текста
  a_text: ['Перед вами — первое письмо в рассылке. Наш $somebody$ рад тому, что вы не прошли мимо подписки, и приглашает вас на нашу выставку, адрес — во вложении.', 'Меня зовут Михаил Максимов, и я — $somebody$ в этой компании. От лица всех сотрудников я рад приветствовать вас в рядах наших единомышленников.'],
  b_text: ['Если ты видишь это письмо, знай — наш $somebody$ здорово постарался для этого. Зато ты теперь сможешь прийти к нам на выставку и убедиться своими глазами в том, о чём мы тебе говорили!', 'Ты сделал это, а значит, твой $somebody$ будет в восторге! Если нет — дай нам знать, и мы это исправим.'],
  // должность 
  somebody: ['директор', 'руководитель', 'начальник отдела'],
  // текст для концовки
  a_outro: ['Спасибо, что подписались на нашу рассылку!', 'Если будут вопросы — пишите.', 'Если письмо попало к вам по ошибке — проигнорируйте его.'],
  b_outro: ['Теперь ты один из нас!', 'Здорово, что мы теперь — команда!', 'Рады, что ты с нами! Пиши, если есть что спросить.'],
};

Теперь можно собрать письмо в одно целое. Для этого берём начало, середину и концовку, добавляем HTML-теги и соединяем всё в одну строку. Не стоит опасаться того, что всё будет выглядеть некрасиво — для этого мы как раз и добавляем теги, чтобы они разметили наш текст правильно и красиво.

Мы говорили, что сделаем так, чтобы алгоритм сам выбирал, в каком стиле сделать письмо. За это отвечает переменная mood: если она равна 0, то стиль будет официальным, а если она равна 1 — неформальным. Чтобы каждый раз это число определялось случайным образом, добавим функцию, которая возвращает случайное число в заданном диапазоне.

// генератор случайных чисел в диапазоне от минимального до максимального
function randz(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
// собираем письмо в одно целое
function generate_structure() {
  // случайным образом определяем тон письма — официальный (0) или неформальный (1)
  var mood = randz(0, 1);
  // результат помещаем в переменную вместе с тегами
  result = '<h2>' + intro[mood] + '</h1>\n';
  result += '<p>' + text[mood] + '</p>\n';
  result += '<p>' + outro[mood] + '</p>\n';
  // возвращаем результат — одну строку с HTML-разметкой
  return result;
}

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

1-й проход:

<h2>$a_intro$</h2>

<p>$a_text$</p>

<p>$a_outro$</p>

2-й проход:

<h2>Здравствуйте.</h2>

<p>Перед вами — первое письмо в рассылке. Наш $somebody$ рад тому, что вы не прошли мимо подписки, и приглашает вас на нашу выставку, адрес — во вложении.</p>

<p>Спасибо, что подписались на нашу рассылку!</p>

3-й проход:

<h2>Здравствуйте.</h2>

<p>Перед вами — первое письмо в рассылке. Наш директор рад тому, что вы не прошли мимо подписки, и приглашает вас на нашу выставку, адрес — во вложении.</p>

<p>Спасибо, что подписались на нашу рассылку!</p>

Как видим, алгоритм сначала заменил все шаблонные слова первого уровня, а затем — второго уровня. Можно делать сколько угодно вложений — алгоритм заменит их все на нормальные слова.

Меняем шаблонные слова на текст

Здесь принцип работы такой:

  • находим все слова, где есть значки доллара;
  • убираем их и получаем название массива в объекте, где хранятся значения для этого шаблонного слова;
  • идём в этот массив и случайно выбираем оттуда значение;
  • подставляем это значение вместо шаблонного слова;
  • делаем так до тех пор, пока не закончатся все шаблонные слова.

// убираем знаки доллара после замены шаблонного слова на реальный текст
function parse_keywords(string) {
  // задаём шаблон поиска таких слов
  pattern = /\$\w+\$/g;
  // проверяем, есть ли в строке нужные нам слова
  keyword = string.match(pattern);
  // если есть
  if (keyword) {
    // пока не закончатся все такие слова в строке…
    for (var i = keyword.length - 1; i >= 0; i--) {
      // убираем значок доллара
      keyword[i] = keyword[i].replace(/\$/g, '');
    }
  }
  // возвращаем слово без знаков доллара
  return keyword;
}
// выбираем случайный элемент массива
function randomize(arr) {
  return arr[Math.floor(Math.random() * arr.length)];
}
// меняем одно слово на другое
function replace_keyword(source, keyword, variant) {
  return (source.replace('$' + keyword + '$', variant));
}
//подставляем случайным образом готовые слова вместо шаблонных слов со знаком доллара
function bake(object) {
  // переменная, в которой на выходе получится готовый текст
  var result = randomize(object['structure']);
  // пока есть шаблонные слова со знаком доллара
  do {
    // выбираем их
    keywords = parse_keywords(result);
    // если они точно есть, то
    if (keywords) {
      // пока они не закончатся
      for (var i = keywords.length - 1; i >= 0; i--) {
        // случайным образом подставляем вместо шаблонных слов с долларом слова из наборов
        if (object.hasOwnProperty(keywords[i])) {
          result = replace_keyword(result, keywords[i], randomize(object[keywords[i]]));
        }
      }
    }
  } while (keywords);
  // возвращаем готовый результат
  return result;
}  

Привязываем скрипт к кнопке и выводим результат

Мы уже прописали в HTML-документе, что по клику будет вызываться функция get_text(). Давайте это запрограммируем так, чтобы текст письма сразу после сборки появлялся на экране. Чтобы это сделать, используем jQuery, найдём блок для вывода текста text_here и отправим наш текст с HTML-тегами в этот блок.

Браузер сам распознает все теги и отформатирует письмо так, как нужно нам. Если вы хотите другое форматирование — поменяйте теги или пропишите в них нужные стили.

// подставляем текст с тегами в нужне место на странице
function send(text) {
  document.getElementById('text_here').innerHTML = text;
}
// что мы делаем по нажатию на кнопку
function get_text() {
  // заводим переменную для структуры текста
  var currentObject = text_obj;
  // наполняем структуру текстом с шаблонными словами
  currentObject.structure[0] = generate_structure();
  // меняем шаблонные слова на нормальный текст
  result = bake(currentObject);
  // выводим результат
  send(result);
}  
Результат работы скрипта. Похоже на ответ живого человека.

// шаблон начала письма
var intro = ['$a_intro$', '$b_intro$ '];
// шаблон тела письма
var text = ['$a_text$', '$b_text$'];
// шаблон концовки письма
var outro = ['$a_outro$', '$b_outro$'];
// задаём набор фраз и слов для всех шаблонов сразу
var text_obj = {
  //структуру письма пока оставляем пустой
  structure: [
    ''
  ],
  // текст для начала
  a_intro: ['Здравствуйте.', 'Добрый день!'],
  b_intro: ['Привет!', 'Хэллоу!', 'Бонжур!'],
  // варианты основного текста
  a_text: ['Перед вами — первое письмо в рассылке. Наш $somebody$ рад тому, что вы не прошли мимо подписки, и приглашает вас на нашу выставку, адрес — во вложении.', 'Меня зовут Михаил Максимов, и я — $somebody$ в этой компании. От лица всех сотрудников я рад приветствовать вас в рядах наших единомышленников.'],
  b_text: ['Если ты видишь это письмо, знай — наш $somebody$ здорово постарался для этого. Зато ты теперь сможешь прийти к нам на выставку и убедиться своими глазами в том, о чём мы тебе говорили!', 'Ты сделал это, а значит, твой $somebody$ будет в восторге! Если нет — дай нам знать, и мы это исправим.'],
  // должность 
  somebody: ['директор', 'руководитель', 'начальник отдела'],
  // текст для концовки
  a_outro: ['Спасибо, что подписались на нашу рассылку!', 'Если будут вопросы — пишите.', 'Если письмо попало к вам по ошибке — проигнорируйте его.'],
  b_outro: ['Теперь ты один из нас!', 'Здорово, что мы теперь — команда!', 'Рады, что ты с нами! Пиши, если есть что спросить.'],
};
// убираем знаки доллара после замены шаблонного слова на реальный текст
function parse_keywords(string) {
  // задаём шаблон поиска таких слов
  pattern = /\$\w+\$/g;
  // проверяем, есть ли в строке нужные нам слова
  keyword = string.match(pattern);
  // если есть
  if (keyword) {
    // пока не закончатся все такие слова в строке…
    for (var i = keyword.length - 1; i >= 0; i--) {
      // убираем значок доллара
      keyword[i] = keyword[i].replace(/\$/g, '');
    }
  }
  // возвращаем слово без знаков доллара
  return keyword;
}
// генератор случайных чисел в диапазоне от минимального до максимального
function randz(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
// выбираем случайный элемент массива
function randomize(arr) {
  return arr[Math.floor(Math.random() * arr.length)];
}
// меняем одно слово на другое
function replace_keyword(source, keyword, variant) {
  return (source.replace('$' + keyword + '$', variant));
}
//подставляем случайным образом готовые слова вместо шаблонных слов со знаком доллара
function bake(object) {
  // переменная, в которой на выходе получится готовый текст
  var result = randomize(object['structure']);
  // пока есть шаблонные слова со знаком доллара
  do {
    // выбираем их
    keywords = parse_keywords(result);
    // если они точно есть, то
    if (keywords) {
      // пока они не закончатся
      for (var i = keywords.length - 1; i >= 0; i--) {
        // случайным образом подставляем вместо шаблонных слов с долларом слова из наборов
        if (object.hasOwnProperty(keywords[i])) {
          result = replace_keyword(result, keywords[i], randomize(object[keywords[i]]));
        }
      }
    }
  } while (keywords);
  // возвращаем готовый результат
  return result;
}
// собираем письмо в одно целое
function generate_structure() {
  // случайным образом определяем тон письма — официальный (0) или неформальный (1)
  var mood = randz(0, 1);
  // результат помещаем в переменную вместе с тегами
  result = '<h2>' + intro[mood] + '</h1>\n';
  result += '<p>' + text[mood] + '</p>\n';
  result += '<p>' + outro[mood] + '</p>\n';
  // возвращаем результат — одну строку с HTML-разметкой
  return result;
}
// подставляем текст с тегами в нужне место на странице
function send(text) {
  document.getElementById('text_here').innerHTML = text;
}
// что мы делаем по нажатию на кнопку
function get_text() {
  // заводим переменную для структуры текста
  var currentObject = text_obj;
  // наполняем структуру текстом с шаблонными словами
  currentObject.structure[0] = generate_structure();
  // меняем шаблонные слова на нормальный текст
  result = bake(currentObject);
  // выводим результат
  send(result);
}  

Что дальше

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

Например, можно генерировать ответы службы поддержки, сообщения в чат-ботах, подводки для статей или создавать резюме. Этот скрипт — убийца копирайтеров, потому что он делает за них всю основную работу. Вам остаётся только пройти по тексту и убрать шероховатости.

Кстати, в одной редакции (не в нашей) такой алгоритм несколько месяцев генерировал статьи, и никто ничего не заметил. Читателям нравилось. Совпадение?

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства и компенсация, если вы пойдёте работать в «Яндекс».
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
hard