Недавно, в попытках разобраться с nlp, мне пришла идея написать простого telegram бота, который будет разговаривать, как дерзкий гопник. То есть:
- давать ответ по слову-триггеру, как "хочу", "короче", "нет" и т.д.;
- отвечать дерзким вопросом на вопрос;
- отвечать нецензурной рифмой;
- если ничего не подходит и бот в замешательстве, отвечать злой фразой.
Для имплементации был выбран JavaScript с ES6 и Flow. Возможно, Python подошёл бы лучше, так как под него существует больше стабильных и проверенных библиотек для nlp. Но для JS есть Az.js, которого вполне хватило.
Для работы с Telegram API был использован node-telegram-bot-api.
TLDR: бот, исходный код
Осторожно, под катом присутствует нецензурная речь и детали реализации!
Часть с реализацией работы с Telegram API не сильно интересная, и про это уже написано множество статей, и её я опущу.
Начну сразу с того, как бот пытается найти подходящий ответ. Первый метод поиска ответа – слово-триггер:
user: Хочу новую машину!
bot: Хотеть невредно!
Для начала мы имеем список пар [regexp, ответ]
:
const TRIGGERS = [ [/^к[оа]роч[ье]?$/i, 'У кого короче, тот дома сидит!'], [/^нет$/i, 'Пидора ответ!'], [/^хо(чу|тим|тят|тел|тела)$/i, 'Хотеть невредно!'], ];
Потом мы должны разбить сообщение от пользователя на слова:
const getWords = (text: string): string[] => Az.Tokens(text) .tokens .filter(({type}) => type === Az.Tokens.WORD) .map(({st, length}) => text.substr(st, length).toLowerCase());
Пройти по всем триггерам и вернуть возможные ответы:
const getByWordTrigger = function*(text: string): Iterable<string> { for (const word of getWords(text)) { for (const [regexp, answer] of constants.TRIGGERS) { if (word.match(regexp)) { yield answer; } } } };
Вышло очень просто. Теперь пришло время второго метода поиска ответа – отвечать дерзким вопросом на вопрос:
user: Когда мы уже пойдём домой?
bot: А тебя ебёт?
Для того чтобы определить, является ли сообщение вопросом, мы должны проверить его на наличие вопросительного знака в конце и на наличие вопросительных слов, как "когда", "где" и т.д.:
const getAnswerToQuestion = (text: string): string[] => { if (text.trim().endsWith('?')) { return [constants.ANSWER_TO_QUESTION]; } const questionWords = getWords(text) .map((word) => Az.Morph(word)) .filter((morphs) => morphs.length && morphs[0].tag.Ques); if (questionWords.length) { return [constants.ANSWER_TO_QUESTION]; } else { return []; } };
Так, в случае вопроса, бот вернёт захардкоженый constants.ANSWER_TO_QUESTION
.
Третий метод поиска ответа – ответ нецензурной рифмой. Этот метод наиболее сложный:
user: хочу в Австрию!
bot: хуявстрию
user: у него есть трактор
bot: хуяктор
Вкратце: мы просто заменяем первый слог существительного или прилагательного на "ху" и трансформированную гласную из слога, как "о" → "ё", "а" → "я" и т.д.
Для начала мы должны уметь получать первый слог слова. Это несложно:
const getFirstSyllable = (word: string): string => { const result = []; let readVowel = false; for (const letter of word) { const isVowel = constants.VOWELS.indexOf(letter) !== -1; if (readVowel && !isVowel) { break; } if (isVowel) { readVowel = true; } result.push(letter); } return result.join(''); };
Потом нужно заменять первый слог на "ху" + гласную, если это возможно:
const getRhyme = (word: string): ?string => { const morphs = Az.Morph(word); if (!morphs.length) { return; } const {tag} = morphs[0]; if (!tag.NOUN && !tag.ADJF) { return; } const syllable = getFirstSyllable(word); if (!syllable || syllable === word) { return; } const prefix = constants.VOWEL_TO_RHYME[last(syllable)]; const postfix = word.substr(syllable.length); return `${prefix}${postfix}`; };
И, наконец, возвращать все возможные рифмы для слов из сообщения:
const getRhymes = (text: string): string[] => getWords(text) .map(getRhyme) .filter(Boolean) .reverse();
Последний метод поиска ответа – отвечать в замешательстве грубой фразой:
user: wtf
bot: Чё?
Этот метод более чем простой, поэтому будет реализован в агрегирующей все методы функции:
export default (text: string): string[] => { const answers = uniq([ ...getByWordTrigger(text), ...getAnswerToQuestion(text), ...getRhymes(text), ]); if (answers.length) { return answers; } else { return constants.NO_ANSWERS; } };
И это всё. Бот, исходный код.
ссылка на оригинал статьи https://habrahabr.ru/post/327586/
Добавить комментарий