diff --git a/bot.js b/bot.js deleted file mode 100644 index 1cf66a9..0000000 --- a/bot.js +++ /dev/null @@ -1,66 +0,0 @@ -/** __ __ __ ___ ___ __ __ ___ - * / ' / \ |__) | |__ \_/ |__) / \ | - * \__. \__/ | \ | |___ / \ |__) \__/ | - * - * @copyright © 2021 hepller - */ - -// Импорт модулей -const {VK} = require('vk-io') -const Markov = require('markov-generator') - -// Импорт функций -const Utils = require('./utils.js') - -// Конфиг, паттерн и файл проекта -const config = require('./config') -const data = require('./data') -const project = require('./package') - -// Инициализация vk-io -const vk = new VK({token: config.token, v: 5.131}) - -// Сообщения при запуске -console.log(`Cortex Bot v${project.version}`) -console.log('VK API connection ...') - -// Запуск обработчика событий ВК -vk.updates.startPolling() - .then(() => console.log('Successfully connected')) - -// Обработчик сообщений ВК -vk.updates.on('message_new', async ctx => { - - // Игнорирование лишних сообщений - if (!ctx.isChat || ctx.isOutbox || ctx.isGroup) return - - // Генерация сообщения с определенным шансом - if (Utils.getWithChance(config.chance)) { - - // Сообщение в консоль о начале генеарции - console.log(`Generation started ${Utils.getTimeString()} <#${ctx.chatId}>`) - - // Статус набора текста - await ctx.setActivity() - - // Время в начале генерации - const start_time = Date.now() - - // Генерация текста - const sentence = new Markov({input: data, minLenght: config.min_words}).makeChain() - - // Предупреждение при превышении лимита символов - if (sentence.split('').length > config.max_symbols) return console.log(`#${ctx.chatId} Symbols limit exceeded (${sentence.split('').length}/${config.max_symbols})`) - - // Время в конце генерации (преобразованное в милисекунды) - const final_time = (Date.now() - start_time) % 1000 - - // Отправка сообщения - await ctx.send(config.format ? Utils.formatText(sentence) : sentence) - .then(() => console.log(`Text generated ${final_time}ms <#${ctx.chatId}>`)) - } -}) - -// Обработка возможных ошибок -process.on('uncaughtException', error => console.error(`Error: ${error.message}`)) -process.on('unhandledRejection', error => console.error(`Error: ${error.message}`)) \ No newline at end of file diff --git a/config.json b/config.json deleted file mode 100644 index 6b10b78..0000000 --- a/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "token": "TOKEN", - "chance": 10, - "min_words": 2, - "max_symbols": 250, - "format": true -} \ No newline at end of file diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..e4a6406 --- /dev/null +++ b/config.yml @@ -0,0 +1,40 @@ +# __ __ __ ___ ___ +# / / \ |__) | |__ \_/ +# \__ \__/ | \ | |___ / \ + +# Настройки VK API +longpoll: + # Токен доступа + token: TOKEN + # Версия + version: 5.131 + # ID группы с ботом + # * При работе на странице укажите 0 + group_id: ID + +# Отправка сообщений +messages: + # Шанс генерации текста + chance: 100 + # Максимальное кол-во символов в тексте + # * Если символов в тексте будет больше данного значения сообщение не отправится + symbols_limit: 250 + # ID бесед, в которых бот активен + # Чтобы бот работал везде поставьте значение <> + active_chats: all + # Форматирование текста + format: false + # Имитация написания текста + # * При включении появляется задержка перед отправкой сообщений + writing_imitation: true + +# Генерация текста +generation: + # Минимальное кол-во слов в генерируемом тексте + min_words: 5 + +# Настройки БД +database: + # Сохранение сообщений пользователей + # * Одинаковые строки не сохраняются + data_save: true \ No newline at end of file diff --git a/core/core.js b/core/core.js new file mode 100644 index 0000000..5963236 --- /dev/null +++ b/core/core.js @@ -0,0 +1,95 @@ +/** __ __ __ ___ ___ + * / / \ |__) | |__ \_/ + * \__ \__/ | \ | |___ / \ + * + * @copyright © 2021-2022 hepller + */ + +// Импорт зависимостей +import { VK } from 'vk-io' +import YAML from 'yaml' +import Markov from 'markov-generator' + +// Импорт системных модулей +import { readFileSync } from 'fs' + +// Импорт компонентов ядра +import Logger from './logger.js' +import Utils from './utils.js' +import DataManager from './data_manager.js' + +// Парсинг конфигураций проекта +const config = YAML.parse(readFileSync('config.yml', 'utf8')) +const project = JSON.parse(readFileSync('package.json', 'utf8')) + +// Инициализация vk-io +const vk = new VK( + + // Определение места работы бота (группа / страница) + config.longpoll.group_id + + // При работе в группе + ? {token: config.longpoll.token, v: config.longpoll.version, pollingGroupId: config.longpoll.group_id} + + // При работе на странице + : {token: config.longpoll.token, v: config.longpoll.version} +) + +// Сообщение о запуске +Logger.info(`Cortex Bot v${project.version} запущен`) + +// Сообщение о подключении к VK API +Logger.info('Подключение к VK Long Poll ...') + +// Старт получения событий ВК +vk.updates.startPolling().then(() => Logger.info('VK Long Poll подключен')) + +// Обработчик сообщений ВК +vk.updates.on('message_new', async ctx => { + + // Игнорирование лишних сообщений + if (!ctx.isChat || ctx.isOutbox) return + if (config.messages.active_chats != 'all' && !config.messages.active_chats.includes(ctx.chatId)) return + + // Логирование сообщений + Logger.info(`#${ctx.chatId} ${ctx.senderId} '->' ${ctx.text || ''} ${ctx?.attachments}`) + + // Получение чата из БД + ctx.chat = DataManager.getChat(ctx.chatId) + + // Запись сообщения в БД + if (!ctx.isGroup && ctx.text && config.database.data_save && !ctx.chat.data.includes(ctx.text)) DataManager.writeChatData(ctx.chatId, ctx.text) + + // Генерация сообщения с определенным шансом + if (Utils.getWithChance(config.messages.chance)) { + + // Сообщение в консоль о генерации текста + Logger.info(`#${ctx.chatId} Генерация текста ...`) + + // Генерация текста + // * Если данных будет недостаточно произойдет ошибка <> + const sentence = new Markov({input: ctx.chat.data, minLength: config.generation.min_words}).makeChain() + + // Форматирование текста (если включено) + const formatted_sentence = config.messages.format ? Utils.formatString(sentence) : sentence + + // Предупреждение при превышении лимита символов + if (formatted_sentence.split('').length > config.messages.symbols_limit) return Logger.warn(`#${ctx.chatId} Превышен лимит символов (${formatted_sentence.split('').length}/${config.messages.symbols_limit})`) + + // Установка статуса набора текста + ctx.setActivity() + + // Задержка перед отправкой сообщений для имитации написания (если включено) + // * Высчитывается на основе количества символов в сгенерированном тексте + if (config.messages.writing_imitation) await Utils.sleep(formatted_sentence.split('').length * 2 + '00') + + // Отправка сообщения + ctx.send(formatted_sentence).then(() => Logger.info(`#${ctx.chatId} Сообщение сгенерировано ("${sentence}")`)) + } +}) + +// Логирование неперехваченных исключений +process.on('uncaughtException', error => Logger.error(error.stack)) + +// Логирование необработанных ошибок (promise error) +process.on('unhandledRejection', error => Logger.error(error.stack)) \ No newline at end of file diff --git a/core/data_manager.js b/core/data_manager.js new file mode 100644 index 0000000..bfe794a --- /dev/null +++ b/core/data_manager.js @@ -0,0 +1,56 @@ +/** __ __ __ ___ ___ + * / / \ |__) | |__ \_/ + * \__ \__/ | \ | |___ / \ + * + * @copyright © 2021-2022 hepller + */ + +// Импорт системных модулей +import { readFileSync, writeFileSync } from 'fs' + +// Получение БД +const db = JSON.parse(readFileSync('data.json', 'utf8')) + +/** Менеджер БД */ +export default class DataManager { + + /** + * Получает чат из БД + * @param {number} chat_id ID чата + */ + static getChat(chat_id) { + + // Поиск чата в БД + let chat = db.chats.find(chat => chat.id == chat_id) + + // Добавление чата в БД при его отсутствии + if (!chat) { + + // Добавление + db.chats.push({id: chat_id, data: []}) + + // Запись + writeFileSync('data.json', JSON.stringify(db, null, '\t')) + + // Обновление значения переменной + chat = db.chats.find(chat => chat.id == chat_id) + } + + // Возвращение объекта чата + return chat + } + + /** + * Записывает новые данные для чата + * @param {number} chat_id ID чата + * @param {string} data Данные + */ + static writeChatData(chat_id, data) { + + // Добавление данных в БД + DataManager.getChat(chat_id).data.push(data) + + // Запись БД + writeFileSync('data.json', JSON.stringify(db, null, '\t')) + } +} \ No newline at end of file diff --git a/core/logger.js b/core/logger.js new file mode 100644 index 0000000..7f3c5c7 --- /dev/null +++ b/core/logger.js @@ -0,0 +1,37 @@ +/** __ __ __ ___ ___ + * / / \ |__) | |__ \_/ + * \__ \__/ | \ | |___ / \ + * + * @copyright © 2021-2022 hepller + */ + +// Импорт компонентов ядра +import Utils from './utils.js' + +/** Логер */ +export default class Logger { + + /** + * Выводит в лог информацию + * @param {string} text Текст сообщения + */ + static info(text) { + console.log(`[${Utils.getTimeString()} INFO]: ${text}`.replace(/\n/g, '\\n')) + } + + /** + * Выводит в лог предупреждения + * @param {string} text Текст предупреждения + */ + static warn(text) { + console.log(`\u001B[33m[${Utils.getTimeString()} WARN]: ${text}\u001B[0m`) + } + + /** + * Выводит в лог ошибки + * @param {string} text Текст ошибки + */ + static error(text) { + console.log(`\u001B[31m[${Utils.getTimeString()} ERRO]: ${text}\u001B[0m`) + } +} \ No newline at end of file diff --git a/core/utils.js b/core/utils.js new file mode 100644 index 0000000..dc8235c --- /dev/null +++ b/core/utils.js @@ -0,0 +1,53 @@ +/** __ __ __ ___ ___ + * / / \ |__) | |__ \_/ + * \__ \__/ | \ | |___ / \ + * + * @copyright © 2021-2022 hepller + */ + +/** Функции */ +export default class Utils { + + /** Возвращает строку со временем в формате `hh:mm:ss` */ + static getTimeString() { + + // Переменные + let hours = new Date().getHours() + let minutes = new Date().getMinutes() + let seconds = new Date().getSeconds() + + // Исправление одинарных символов + if (hours < 10) hours = `0${hours}` + if (minutes < 10) minutes = `0${minutes}` + if (seconds < 10) seconds = `0${seconds}` + + // Возвращение строки + return `${hours}:${minutes}:${seconds}` + } + + /** + * Возвращает `true` с определенным шансом + * @param {number} likelihood Вероятность (%) + */ + static getWithChance(likelihood) { + + // Замена некорректных значений + if (likelihood < 0) likelihood = 0 + if (likelihood > 100) likelihood = 100 + + // Возвращение значения + return Math.random() * 100 < likelihood + } + + /** + * Останавливает выполнение функции на указанное время + * @param {number} ms Время остановки (в миллисекундах) + */ + static sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) + } + + static formatString(text) { + return text.toLowerCase().charAt(0).toUpperCase() + text.slice(1) + '.' + } +} \ No newline at end of file diff --git a/data.json b/data.json index 0637a08..7dbabf4 100644 --- a/data.json +++ b/data.json @@ -1 +1,3 @@ -[] \ No newline at end of file +{ + "chats": [] +} \ No newline at end of file diff --git a/license b/license index a1cf3b8..86acaf0 100644 --- a/license +++ b/license @@ -1,6 +1,6 @@ MIT License -Copyright © 2021 hepller +Copyright © 2021-2022 hepller Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package.json b/package.json index 25a1ab0..2712400 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,18 @@ { "name": "cortex-bot", - "version": "0.7.3", - "description": "vk text generator bot", - "main": "bot.js", + "version": "0.9.0", + "description": "VK delirium generator bot", + "main": "core/core.js", + "type": "module", "repository": { "type": "git", "url": "git+https://github.com/hepller/cortex-bot.git" }, "author": "hepller", "license": "MIT", - "bugs": { - "url": "https://github.com/hepller/cortex-bot/issues" - }, - "homepage": "https://github.com/hepller/cortex-bot#readme", "dependencies": { "markov-generator": "^1.2.3", - "vk-io": "^4.4.0" + "vk-io": "^4.4.0", + "yaml": "^1.10.2" } } diff --git a/readme.md b/readme.md index 32148dd..250a2a4 100644 --- a/readme.md +++ b/readme.md @@ -1,43 +1,20 @@ -# 🧠 Cortex Bot +# 🧠 Cortex Bot ![Release](https://img.shields.io/github/v/release/hepller/cortex-bot) -Бот ВКонтакте для генерации сообщений с помощью цепей Маркова +> Бот [ВКонтакте](https://vk.com) для генерации сообщений с помощью цепей Маркова ---- +![Size](https://img.shields.io/github/repo-size/hepller/cortex-bot) +![License](https://img.shields.io/github/license/hepller/cortex-bot) ## ⚙ Конфигурация -- `token` - Токен доступа VK API -- `chance` - Шанс генерации сообщения -- `min_words` - Минимальное кол-во слов в сгенерированном сообщении -- `max_symbols` - Максимальное кол-во символов в сообщении -- `format` - Форматирование текста (заглавная буква и точка в конце) - ---- +Все настройки бота и их описания находятся в файле `config.yml` ## 💾 Данные -Перед началом использования загрузите свой датасет с достаточным объемом текста в файл `data.json` - -**Пример небольшого датасета:** - -```json -[ - "Я вот подумал. Если быстро заморозить человека, то в какой момент он умрет?", - "есть еще три:\n\nгносеоногия (эпистемология) - о познании", - "логика - о мышлении (из нее как ы слеуют большинство наук естественных и абстрактных (базируются на логических законах)", - "ну вот тут открытый же вопрос. С т.з. медицины: есть критерии естественной смерти, которые гуглятся", - "а с т.з. онтологии - тут может готового ответа и не быть. В этом как бы и отличие - очень часто нет готовых и четких ответов", - "ну, например, когда между нейронами сигналы перестанут бегать", - "Читаю, но как бы там все неолнозначно", - "клеточная смерть - не очень катит, т.к. те же волосы и ногти долго еще растут", - "Пульса нету, сердечной активности нету" -] -``` - -_При недостатке данных для генерации будет возникать ошибка `Maximum call stack size exceeded`_ - ---- +Перед началом полноценной работы боту нужно достаточное количество строк для генерации текста. При недостатке данных для генерации будет возникать ошибка `Maximum call stack size exceeded` ## 📄 Лицензия -Проект распространяется под лицензией **MIT** +Copyright © 2021-2022 [hepller](https://github.com/hepller) + +Проект распространяется под лицензией [MIT](license) diff --git a/utils.js b/utils.js deleted file mode 100644 index ff2888f..0000000 --- a/utils.js +++ /dev/null @@ -1,43 +0,0 @@ -/** __ __ __ ___ ___ __ __ ___ - * / ' / \ |__) | |__ \_/ |__) / \ | - * \__. \__/ | \ | |___ / \ |__) \__/ | - * - * @copyright © 2021 hepller - */ - -/** Функции */ -module.exports = class Utils { - - /** Получает текущее время в формате `hh:mm:ss` */ - static getTimeString() { - - // Переменные - let hours = new Date().getHours() - let minutes = new Date().getMinutes() - let seconds = new Date().getSeconds() - - // Исправление одинарных символов - if (hours < 10) hours = `0${hours}` - if (minutes < 10) minutes = `0${minutes}` - if (seconds < 10) seconds = `0${seconds}` - - // Возвращение строки - return `${hours}:${minutes}:${seconds}` - } - - /** - * Возвращает `true` с определенным шансом - * @param {number} likelihood Вероятность (%) - */ - static getWithChance(likelihood) { - return Math.random() * 100 < likelihood - } - - /** - * Форматирует текст (устанавливает заглавную букву и точку в конце) - * @param {string} text Текст для форматирования - */ - static formatText(text) { - return text.charAt(0).toUpperCase() + text.slice(1) + '.' - } -} \ No newline at end of file