Coder Social home page Coder Social logo

vitalets / alice-renderer Goto Github PK

View Code? Open in Web Editor NEW
29.0 5.0 5.0 663 KB

Node.js библиотека для формирования ответов в навыках Яндекс Алисы.

JavaScript 99.74% Shell 0.26%
yandex alice alice-skills alice-sdk

alice-renderer's Introduction

alice-renderer

Actions Status Coverage Status Known Vulnerabilities npm license

Node.js библиотека для формирования ответов в навыках Яндекс Алисы.

Позволяет:

  • компактно записывать ответ
  • разделять данные на текст и голос (там где нужно)
  • добавлять паузы, звуки и аудио-эффекты
  • вставлять изображения
  • добавлять вариативность ответов
  • вставлять кнопки-подсказки

Основана на использовании Tagged templates.

Содержание

Установка

npm i alice-renderer

Использование

Базовый пример

В простейшем варианте нужно применить тег-функцию reply к некоторой строке, заключённой в backticks `:

reply`строка`;

В результате получим объект с полями text и tts, в которых записана переданная строка:

{
  text: 'строка',
  tts: 'строка',
  end_session: false
}

При этом для текстового представления из строки вырезаются акценты (+):

const { reply } = require('alice-renderer');

const response = reply`Как дел+а?`;

console.log(response);

/*
{
  text: 'Как дела?',
  tts: 'Как дел+а?',
  end_session: false
}
*/

Пример с модификаторами

Функции-модификаторы позволяют обогащать ответ отдельно в текстовом и голосовом каналах. Например, модификатор audio добавляет звук - он запишется только в поле tts:

const { reply, audio } = require('alice-renderer');

reply`${audio('sounds-game-win-1')} Как дел+а?`;

/*
{
  text: 'Как дела?',
  tts: '<speaker audio="alice-sounds-game-win-1.opus"> Как дел+а?',
  end_session: false
}
*/

Модификатор buttons позволяет добавить кнопки:

const { reply, buttons } = require('alice-renderer');

reply`
  Как дел+а?
  ${buttons(['Отлично', 'Супер'])}
`;

/*
{
  text: 'Как дела?',
  tts: 'Как дел+а?',
  buttons: [
    {title: 'Отлично', hide: true},
    {title: 'Супер', hide: true},
  ],
  end_session: false
}
*/

Чтобы сделать ответ более разнообразным можно передавать в reply массивы значений:${[item1, item2, ...]}. При рендеренге из массива выберется один случайный элемент:

const { reply } = require('alice-renderer');

reply`
  ${['Привет', 'Здор+ово']}! Как дел+а?
`;

/*
{
  text: 'Здорово! Как дела?',
  tts: 'Здор+ово! Как дел+а?',
  end_session: false
}
*/

Пример с параметрами

Для проброса параметров удобно использовать reply вместе со стрелочной функцией:

const { reply, pause, buttons } = require('alice-renderer');

const welcome = username => reply`
  ${['Здравствуйте', 'Добрый день']}, ${username}! ${pause(500)} Как дел+а?
  ${buttons(['Отлично', 'Супер'])}
`;

const response = welcome('Виталий Пот+апов');

console.log(response);

/*
{
  text: 'Добрый день, Виталий Потапов! Как дела?',
  tts: 'Добрый день, Виталий Пот+апов! - - - - - - - Как дел+а?',
  buttons: [
    {title: 'Отлично', hide: true},
    {title: 'Супер', hide: true},
  ],
  end_session: false
}
*/

Переданные параметры также очищаются от акцентов при записи в поле text.

API

reply

Основная функция библиотеки. Используется как тег-функция для template literal:

 reply`строка`;

Формирует ответ для Алисы, раскладывая переданную строку на текст, голос и кнопки. По умолчанию строка записывается одновременно в оба поля text и tts. Применяя модификаторы можно кастомизировать текстовую и голосовую часть:

const { reply, pause, audio, buttons } = require('alice-renderer');

reply`
  ${audio('sounds-game-win-1')} ${['Привет', 'Здор+ово']}! ${pause(500)} Как дел+а?
  ${buttons(['Отлично', 'Супер'])}
`;

/*
{
  text: 'Здорово! Как дела?',
  tts: '<speaker audio="alice-sounds-game-win-1.opus"> Здор+ово! - - - - - - - Как дел+а?',
  buttons: [
    {title: 'Отлично', hide: true},
    {title: 'Супер', hide: true},
  ],
  end_session: false
}
*/

reply.end

Формирует ответ ровно также, как и reply, но завершает сессию:

const { reply } = require('alice-renderer');

reply.end`До новых встреч!`;

/*
{
  text: 'До новых встреч!',
  tts: 'До новых встреч!',
  end_session: true
}
*/

buttons(items, [defaults])

Добавляет в ответ кнопки.
Параметры:

  • items {Array<String|Object>} - тайтлы/описания кнопок.
  • defaults {?Object} - дефолтные свойства создаваемых кнопок.

В простейшем варианте кнопки можно задавать текстом:

const { reply, buttons } = require('alice-renderer');

reply`Хотите продолжить? ${buttons(['Да', 'Нет'])}`;

/*
{
  text: 'Хотите продолжить?',
  tts: 'Хотите продолжить?',
  buttons: [
    {title: 'Да', hide: true},
    {title: 'Нет', hide: true},
  ],
  end_session: false
}
*/

Если нужно изменить тип кнопок, то дополнительно выставляем defaults:

const { reply, buttons } = require('alice-renderer');

reply`
  Хотите продолжить? 
  ${buttons(['Да', 'Нет'], {hide: false})}
`;

/*
{
  text: 'Хотите продолжить?',
  tts: 'Хотите продолжить?',
  buttons: [
    {title: 'Да', hide: false},
    {title: 'Нет', hide: false},
  ],
  end_session: false
}
*/

Для полной кастомизации можно задавать кнопки объектами:

const { reply, buttons } = require('alice-renderer');

reply`
  Хотите продолжить? 
  ${buttons([
    {title: 'Да', payload: 'yes'},
    {title: 'Нет', payload: 'no'},
  ])}
`;

/*
{
  text: 'Хотите продолжить?',
  tts: 'Хотите продолжить?',
  buttons: [
    {title: 'Да', payload: 'yes', hide: true},
    {title: 'Нет', payload: 'no', hide: true},
  ],
  end_session: false
}
*/

audio(name)

Добавляет звук в голосовой канал.
Параметры:

const { reply, audio } = require('alice-renderer');

reply`${audio('sounds-game-win-1')} Ура!`;

/*
{
  text: 'Ура!',
  tts: '<speaker audio="alice-sounds-game-win-1.opus"> Ура!',
  end_session: false
}
*/

effect(name)

Добавляет голосовой эффект.
Параметры:

const { reply, effect } = require('alice-renderer');

reply`${effect('hamster')} Я говорю как хомяк`;

/*
{
  text: 'Я говорю как хомяк',
  tts: '<speaker effect="hamster"> Я говорю как хомяк',
  end_session: false
}
*/

image(imageId, [options])

Добавляет изображение BigImage в ответ.

Параметры:

  • imageId {String} - идентификатор изображения, загруженного в навык
  • options.title {String} - заголовок изображения
  • options.description {String} - описание изображения
  • options.appendDescription {String} - описание изображения, которое будет добавлено при автозаполнении текстом
  • options.button {Object} - действие по клику на изображение

Если не указывать title / description изображения, то эти поля автоматически заполняются из поля response.text. Логика заполнения следующая: сначала заполняется title, если длина превышает максимальную длину тайтла (128 символов), то заполняется description. Если и в description не помещается (256 символов) - то пробуем заполнить и title и description, остальное обрезаем.

Если изначально указано title / description - то они сохраняют исходное значение.

Пример с автозаполнением title:

const { reply, image } = require('alice-renderer');

reply`
  Вот моя фотка.
  ${image('1234567/xxx')}
`;

/*
{
  text: 'Вот моя фотка.',
  tts: 'Вот моя фотка.',
  end_session: false,
  card: {
    type: 'BigImage',
    image_id: '1234567/xxx',
    title: 'Вот моя фотка.',
  }
}
*/

Пример с автозаполнением description:

const { reply, image } = require('alice-renderer');

reply`
  Вот моя фотка.
  ${image('1234567/xxx', { title: 'Заголовок' })}
`;

/*
{
  text: 'Вот моя фотка.',
  tts: 'Вот моя фотка.',
  end_session: false,
  card: {
    type: 'BigImage',
    image_id: '1234567/xxx',
    title: 'Заголовок',
    description: 'Вот моя фотка.',
  }
}
*/

Пример с параметром appendDescription. Например, в поле description требуется показывать имя автора фото. А сам текст под изображением генерится динамически и может иметь разную длину. Указав appendDescription: 'Автор фото: Иван Иванов', получим следующее:

  • если текст под изображением поместился в title, то в description будет просто Автор фото: Иван Иванов
  • если текст под изображением не поместился в title, то часть его перенесется в description и к нему через пробел добавится Автор фото: Иван Иванов
const { reply, image } = require('alice-renderer');

reply`
  ${getLongText()}
  ${image('1234567/xxx', { appendDescription: 'Автор фото: Иван Иванов' })}
`;

/*
{
  text: 'long text...',
  tts: 'long text...',
  end_session: false,
  card: {
    type: 'BigImage',
    image_id: '1234567/xxx',
    title: 'long text...',
    description: 'continue of long text... Автор фото: Иван Иванов',
  }
}
*/

pause([ms])

Добавляет паузу.
Параметры:

  • ms {?Number=500} - время в милисекундах.
const { reply, pause } = require('alice-renderer');

reply`Дайте подумать... ${pause()} Вы правы!`;

/*
{
  text: 'Дайте подумать. Вы правы!',
  tts: 'Дайте подумать. sil <[500]> Вы правы!',
  end_session: false
}
*/

br([count])

Добавляет перенос строки в текстовый канал. Вставка \n не подходит, т.к. исходные переносы строк вырезаются для удобства записи ответов в backticks `...`.
Параметры:

  • count {?Number=1} - кол-во переносов строк.
const { reply, br } = require('alice-renderer');

reply`
  Следующий вопрос: ${br()}
  "В каком году отменили крепостное право?"
`;

/*
{
  text: 'Следующий вопрос:\n"В каком году отменили крепостное право?"',
  tts: 'Следующий вопрос: "В каком году отменили крепостное право?"',
  end_session: false
}
*/

text(value)

Добавляет строку только в текстовый канал.
Параметры:

  • value {String|Array<String>} - строка текста (или массив строк, из которого будет выбран случайный элемент).

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

const { reply, text } = require('alice-renderer');

reply`Жизнь сложная штука${text('...')}`;

/*
{
  text: 'Жизнь сложная штука...',
  tts: 'Жизнь сложная штука',
  end_session: false
}
*/

tts(value)

Добавляет строку только в голосовой канал.
Параметры:

  • value {String|Array<String>} - строка для голоса (или массив строк, из которого будет выбран случайный элемент).

Полезно например для выражения эмоций:

const { reply, tts } = require('alice-renderer');

reply`${tts('Йохохо!')} Вы угадали!`;

/*
{
  text: 'Вы угадали!',
  tts: ''Йохохо! Вы угадали!',
  end_session: false
}
*/

textTts(textValue, ttsValue)

Добавляет первый аргумент в текстовую часть, а второй - в голосовую.
Параметры:

  • textValue {String|Array<String>} - строка для поля text.
  • ttsValue {String|Array<String>} - строка для поля tts.

Это полезно при выводе значений предназначенных исключительно для экрана (например email):

const { reply, textTts } = require('alice-renderer');

reply`
  Вы можете написать нам на емейл${textTts(': [email protected]', '. Он на вашем экране.')}
`;

/*
{
  text: 'Вы можете написать нам на емейл: [email protected]',
  tts: 'Вы можете написать нам на емейл. Он на вашем экране.',
  end_session: false
}
*/

Или при вставке emoji:

  • в текст лучше их добавить без знаков препинания (так смотрится лучше)
  • в голос наоборот вставить знак препинания без emoji (тогда Алиса прочитает с правильной интонацией)
const { reply, textTts } = require('alice-renderer');

reply`Отлично${textTts(' 👌', '!')} Будет скучно - обращайтесь.`;

/*
{
  text: 'Отлично 👌 Будет скучно - обращайтесь.',
  tts: 'Отлично! Будет скучно - обращайтесь.',
  end_session: false
}
*/

plural(number, one, two, five)

Подстановка форм слова для числительных. В строках можно использовать плейсхолдеры $1, $2, $5.
Параметры:

  • number {Number} - число.
  • one {String} - строка для 1.
  • two {String} - строка для 2.
  • five {String} - строка для 5.
const { reply, plural } = require('alice-renderer');

const getResponse = count => reply`
  У вас ${plural(count, '$1 правильный ответ', '$2 правильных ответа', '$5 правильных ответов')}
`;

getResponse(1); // response.text = "У вас 1 правильный ответ"
getResponse(2); // response.text = "У вас 2 правильных ответа"
getResponse(5); // response.text = "У вас 5 правильных ответов"
getResponse(121); // response.text = "У вас 121 правильный ответ"

enumerate(arr, { separator = ', ', lastSeparator = ' или ' })

Перечисляет не-пустые значения в строку, добавляя "или" перед последним. Это более человеко-привычное перечисление. Параметры:

  • arr {array} - список элементов.
  • separator {string} - разделитель элементов, кроме последней пары (по умолчанию ', ')
  • lastSeparator {string} - разделитель для последней пары (по умолчанию ' или ')

Возвращает:

  • {string}

Пример:

const { reply, enumerate } = require('alice-renderer');

const getActions = hasHints => reply`
  Вы можете 
  ${enumerate([
    'ответить', 
    'сдаться', 
    hasHints && 'взять подсказку',
  ])}
`;

// если подсказку можно брать:
getActions(true) // => "Вы можете ответить, сдаться или взять подсказку"

// если подсказку нельзя брать:
getActions(false) // => "Вы можете ответить или сдаться"

userify(userId, target)

Персонализирует функции рендеринга под конкретного пользователя. Это позволяет избежать повторений при выборе ответов из массива.
Параметры:

  • userId {String} - идентификатор пользователя.
  • target {Function|Object} - функция рендеринга или объект, ключи которого - функции рендеринга.

Возвращает:

  • {Function|Object}

Например, без использования userify:

const { reply } = require('alice-renderer');

const replySuccess = () => reply`
  ${['Отлично', 'Супер', 'Класс']}! 
  Это правильный ответ!
`;

replySuccess();
replySuccess();
replySuccess();

// может оказаться так:
// => "Супер! Это правильный ответ!"
// => "Супер! Это правильный ответ!"
// => "Супер! Это правильный ответ!"

С использованием userify ответы будут гарантированно разные:

const { reply, userify } = require('alice-renderer');

const replySuccess = () => reply`
  ${['Отлично', 'Супер', 'Класс']}!
  Это правильный ответ!
`;

const userReplySuccess = userify(userId, replySuccess);

userReplySuccess();
userReplySuccess();
userReplySuccess();

// => "Супер! Это правильный ответ!"
// => "Отлично! Это правильный ответ!"
// => "Класс! Это правильный ответ!"

Также в userify можно передать объект - тогда будут обернуты все его методы:

const { reply, userify } = require('alice-renderer');

const replies = {
  success: () => reply`${['Отлично', 'Супер', 'Класс']}! Это правильный ответ!`,
  fail: () => reply`${['Нет', 'Неверно']}! Это неправильный ответ!`,
};
const userReplies = userify(userId, replies);

userReplies.success();

select(array)

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

Параметры:

  • array {Array} - массив возможных значений.

Возвращает:

  • {*}

Пример: Есть два варианта ответа, которые должны чередоваться:

const longAnswer = reply`
  ${['Отлично', 'Супер', 'Класс']}!
  Это правильный ответ!
`;

const shortAnswer = reply`
  ${['Верно', 'Точно']}!
`;

На первый взгляд можно просто положить их еще одним массивом в reply:

const answer = reply`
  ${[ longAnswer, shortAnswer ]}
`;

Но это будет работать неправильно, т.к. сами ответы содержат внутри себя вариативность (массивы значений). Поэтому ключ для общего массива будет всегда вычисляться разный (это делается через JSON.stringify()).

Решить эту проблему можно используя select() и вспомогательный массив:

const { reply, userify, select } = require('alice-renderer');

const replySuccess = () => {
  const answerType = select([ 'answer-success-long', 'answer-success-short' ]);
  if (answerType === 'answer-success-long') {
    return reply`
      ${['Отлично', 'Супер', 'Класс']}!
      Это правильный ответ!
    `;
  } else {
    return reply`
      ${['Верно', 'Точно']}!
    `;
  }
};

const userReplySuccess = userify(userId, replySuccess);

userReplySuccess();
userReplySuccess();
userReplySuccess();
userReplySuccess();
userReplySuccess();

// => "Супер! Это правильный ответ!"
// => "Верно!"
// => "Отлично! Это правильный ответ!"
// => "Точно!"
// => "Класс! Это правильный ответ!"

once(options, response)

Возвращает заданный ответ не чаще чем 1 раз в заданное кол-во вызовов или секунд.

Параметры:

  • options.calls {Number} - вернет response не чаще чем 1 раз в заданное кол-во вызовов.
  • options.seconds {Number} - вернет response не чаще чем 1 раз в заданное кол-во секунд.
  • options.leading {Boolean} - возвращять ли response при первом вызове. по умолчанию false.
  • response {String|Object} - ответ

Возвращает:

  • {String|Object}

Используется только совместно с userify. Например, чтобы раз в 5 вызовов добавлять "Вы классно отвечаете на вопросы!", можно написать так:

const { reply, userify, once } = require('alice-renderer');

const replySuccess = () => reply`
  Правильно!
  ${once({ calls: 5 }, 'Вы классно отвечаете на вопросы!')}
`;
const userReplySuccess = userify(userId, replySuccess); // важно применить userify, чтобы сохранять вызовы для текущего пользователя

// => "Правильно!"
// => "Правильно!"
// => "Правильно!"
// => "Правильно!"
// => "Правильно! Вы классно отвечаете на вопросы!"
// => "Правильно!"
// => ...

Либо чтобы добавлять "Вы классно отвечаете на вопросы!" не чаще чем раз в минуту:

reply`
  Правильно!
  ${once({ seconds: 60 }, 'Вы классно отвечаете на вопросы!')}
`;

configure(options)

Глобальная конфигурация модуля.
Параметры:

  • options.disableRandom {Boolean} = false - отключает рандом. Все ответы, содержащие массивы, всегда возвращают первый элемент. Это удобно для тестов.

Возвращает:

  • {undefined}

Например:

const { reply, configure } = require('alice-renderer');

configure({disableRandom: true});

reply`${['Раз', 'Два', 'Три']}`;
reply`${['Раз', 'Два', 'Три']}`;
reply`${['Раз', 'Два', 'Три']}`;
reply`${['Раз', 'Два', 'Три']}`;

// всегда возвращает {text: "Раз", tts: "Раз"}

Рецепты

Вариативность через массивы

Вариативность в ответах удобно добавлять через массивы, которые выносить в отдельные переменные:

const { reply } = require('alice-renderer');

const greetingText = [
  'Привет',
  'Здор+ово',
  'Здравствуйте',
  'Добр+о пожаловать',
  'Йох+анга',
];

const greetingSound = [
  audio('sounds-game-win-1'),
  audio('sounds-game-win-2'),
  audio('sounds-game-win-3'),
];

const welcome = () => reply`
  ${greetingSound} ${greetingText}! Я голосовой помощник Алиса.
`;

Модуль рендеринга ответов

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

replies.js

const { reply, buttons } = require('alice-renderer');

exports.welcome = () => reply`
  Привет! Я голосовой помощник Алиса.
`;

exports.showMenu = username => reply`
  ${username}, вы можете сразу начать игру или узнать подробнее о правилах.
  ${buttons(['Начать игру', 'Правила'])}
`;

exports.goodbye = () => reply.end`
  Отлично! Будет скучно - обращайтесь.
`;

logic.js

const replies = require('./replies');

function handleMenuRequest() {
  // логика формирования ответа...
  return replies.showMenu(session.username);
}

Обработка условий

Можно вставлять обработку условных операторов прям в формируемую строку. Falsy значения в ответ не попадут:

const { reply } = require('alice-renderer');

exports.correctAnswer = score => reply`
  Правильный ответ!
  ${score > 100 && 'Вы набрали больше 100 баллов и получаете приз!'}
`;

Также можно использовать вложенный reply, если требуется обработка строки в условии:

const { reply, audio } = require('alice-renderer');

exports.correctAnswer = score => reply`
  Правильный ответ!
  ${score > 100 && reply`${audio('sounds-game-win-1')}Вы набрали больше 100 баллов и получаете приз!`}
`;

Лицензия

MIT @ Vitaliy Potapov

alice-renderer's People

Contributors

vitalets avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

alice-renderer's Issues

Использовать es2015 imports

Я хочу использовать эту библиотеку на cloudflare-workers. Под капотом там v8-isolates и браузерное окружение.

Я использую rollup для сборки проекта и чтобы использовать этот пакет я вынужден тащить rollup-plugin-commonjs и rollup-plugin-node-resolve.

Если использовать esmodules, то иблитека станет более универсальной, а это приятно :)

Maximum description length for image

Hi! 👋

Firstly, thanks for your work on this project! 🙂

In documentation https://yandex.ru/dev/dialogs/alice/doc/response-card-bigimage.html at current moment max description value is 1024 symbols, and in current code description limited by 256 symbols.

I used patch-package to patch [email protected] for the project I'm working on.

Here is the diff that solved my problem:

diff --git a/node_modules/alice-renderer/src/image.js b/node_modules/alice-renderer/src/image.js
index b64b7e8..c1f7883 100644
--- a/node_modules/alice-renderer/src/image.js
+++ b/node_modules/alice-renderer/src/image.js
@@ -4,7 +4,7 @@
 const {truncate} = require('./utils');
 
 const MAX_TITLE_LENGTH = 128;
-const MAX_DESCRIPTION_LENGTH = 256;
+const MAX_DESCRIPTION_LENGTH = 1024;
 
 // Use symbols to ignore these keys in JSON.stringify
 const hasInitialTitle = Symbol('hasInitialTitle');

Поддержать нативную паузу

Теперь в навыках есть нативная разметка для паузы. Нужно поддержать ее, вместо дефисов (- - - ):

Каждый отделенный пробелами пунктуационный знак преобразуется в фонему pau (пауза в 50-100 мс). Чтобы задать длительную паузу между словами, используйте синтаксис sil <[ <количество_миллисекунд> ]>. Например:
смелость sil <[ 500 ]> город+а берет.

tests failed from master

tests failed from master

  1 failing

  1) userify
       session cleanup:

      3 sessions after TTL until cleanup
      + expected - actual

      -1
      +3

      at Context.<anonymous> (test/specs/userify.spec.js:228:12)

how to reproduce:

git clone git clone https://github.com/vitalets/alice-renderer.git
npm test

Использование в yandex-dialogs-sdk

хочу задепрекейтить нашу тему со статичным классом Reply https://github.com/fletcherist/yandex-dialogs-sdk/blob/master/src/reply/reply.ts

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

Как думаешь, как лучше это сделать?
Сейчас пока из мыслей просто добавить пакет в наш проект и сопроводить доку примерами из либы

Спасибо за крутую библиотеку 👍

cloudflare

https://github.com/vitalets/alice-renderer/blob/41836753b27b7fa252abb08e3e6e3e5dff236c5e/src/sessions.js
код session.js не работает в Cloudflare. подскажите как переписать?

Error: Something went wrong with the request to Cloudflare... Uncaught Error: Some functionality, such as asynchronous I/O, timeouts, and generating random values, can only be performed while handling a request.

const startCleanupService = () => {

  if (cleanupTimer) {
     clearInterval(cleanupTimer);
  }
  cleanupTimer =  setInterval(cleanup, CLEANUP_INTERVAL);
  if (cleanupTimer.unref) {
    cleanupTimer.unref();
  }
};

Хелпер для числительных?

Вот думаю, добавлять или нет.
Есть существующие модули:

Судя по доке pluralize-ru требует форму для значения 0, что не всегда нужно.
plural-ru содержит код для склонения глаголов, с которыми все очень неоднозначно.

Я бы взял минимальный код из гиста plural.js и добавил в таком формате:

const {reply, plural} = require('alice-renderer');

const response = count => reply`
  У вас ${count} ${plural(count, 'правильный ответ', 'правильных ответа', 'правильных ответов')}
`;

// => У вас 1 правильный ответ
// => У вас 2 правильных ответа
// => У вас 5 правильных ответов

А подстановку числа я бы сделал не через %d как в библиотеках выше, а через $1, $2, $5. Имхо так выглядит чуть понятнее:

const response = count => reply`
  ${plural(count, 'добавлен $1 бонус', 'добавлено $2 бонуса', 'добавлено $5 бонусов')}
`;

// vs

const response = count => reply`
  ${plural(count, 'добавлен %d бонус', 'добавлено %d бонуса', 'добавлено %d бонусов')}
`;

Добавить параметр debugMode, в котором кидать ошибку при превышении лимитов

Сейчас Алиса просто закрывает навык, если ответ превышает какой либо из лимитов: длина текста, итд. Будет удобно при разработке видеть такие места сразу - поэтому лучше кидать ошибку. А в продакшене просто обрезать лишний текст, чтобы навык в любом случае продолжал работать. Это можно разрулить как раз параметром debugMode.

Привязка к сессии, чтобы вариативные значения реже повторялись

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

const successWord = ['Отлично', 'Супер', 'Зд+орово'];

const success = () => reply`${successWord}! Это правильный ответ!`;

// может оказаться так:
// => "Супер! Это правильный ответ!"
// => "Супер! Это правильный ответ!"
// => "Супер! Это правильный ответ!"

Было бы круто привязать ответы к сессии пользователя, чтобы гарантированно исключать повторения. Примерно так:

const successWord = ['Отлично', 'Супер', 'Зд+орово'];

const success = ctx => ctx.reply`${successWord}! Это правильный ответ!`;

// => "Супер! Это правильный ответ!"
// => "Отлично! Это правильный ответ!"
// => "Зд+орово! Это правильный ответ!"

Над синтаксисом надо еще подумать.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.