├── .gitattributes ├── .gitignore ├── README.md ├── chat_bot ├── info_base_example │ ├── config.json │ └── questions.json ├── log_example │ ├── error.log │ └── info.log └── src │ ├── index.js │ ├── lib │ ├── configuration.js │ ├── constants.js │ ├── logger.js │ ├── moodleAPI.js │ └── msgHandlers.js │ ├── package-lock.json │ └── package.json ├── chat_bot_logo.jpg ├── chat_bot_logo_old.jpg └── documentation ├── how_to_use ├── install_guide │ ├── en_install.txt │ └── бг_инсталация.txt └── user_guide │ ├── en_guide.txt │ ├── бг_наръчник.txt │ └── подготовка.txt ├── idea ├── en_idea.txt └── бг_идея.txt └── implementation_details ├── todo.txt └── used_technologies.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | info_base/ 3 | .vscode/ 4 | log/ 5 | chat_bot/src/.vscode/launch.json 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatBot_FMI_students 2 | 3 | a telegram based chat-bot application for better education and student awareness
4 | integrated with moodle e-learning system 5 | 6 | written as a project assignment during the FMI's JavaScript Advanced 17/18 course 7 | 8 | fast links :
9 | [idea](https://github.com/IvanFilipov/ChatBot_FMI_students/tree/master/documentation/idea/en_idea.txt)
10 | [how to use](http://telegra.ph/User-guide-how-to-use-the-chatbot-01-03)
11 | 12 | ## repository organization : 13 | 14 | > `/chat_bot/`
15 | >> `src` - the source for the chatbot written in JavaScript
16 | >> `info_base` - the bot's knowledge base example
17 | 18 | > `/documentation/`
19 | >> `idea` - what the idea is and what problems it solves
20 | >> `implementation_details` - information about what was used while writting the code
21 | >> `how_to_use` - guides for both bot user and bot host
22 | 23 | 24 | # Чатбот за студентите от ФМИ 25 | 26 | приложение, базирано на телеграмски чат бот, за по - добра информираност и обучение на студентите от ФМИ,
27 | по - специално тези, които взимат участие в курсовете по УП, ООП и СДП
28 | интегриран със moodle системата за електроно обучение 29 | 30 | проектно задание като част от курса "JavaScript за напреднали" 17/18 воден във ФМИ 31 | 32 | бързи връзи :
33 | [идеята](https://github.com/IvanFilipov/ChatBot_FMI_students/tree/master/documentation/idea/бг_идея.txt)
34 | [как да използваме](http://telegra.ph/Narchnik-za-izpolzvane-na-bota-01-03) 35 | 36 | ## организация на хранилището : 37 | 38 | > `/chat_bot/`
39 | >> `src` - целия код свързан с приложението
40 | >> `info_base` - базата от познания на бота
41 | 42 | > `/documentation/` 43 | >> `idea` - каква е идеята, какви проблеми решава
44 | >> `implementation_details` - техническа информация за това, което е било използвано, докато е писан кода
45 | >> `how_to_use` - наръчници за използването на бота като клиент в чат приложението и като създател и поддържащ бота
46 | -------------------------------------------------------------------------------- /chat_bot/info_base_example/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "questionsPath" : "../info_base/questions.json", 3 | "externalLinks" : { 4 | 5 | 6 | "courseInfo" : "https://docs.google.com/document/d/1UaaONmxmHAJXpL4RcGcgMSptBVLR4guacb45CdYN770", 7 | "teamInfo" : "https://docs.google.com/document/d/1UaaONmxmHAJXpL4RcGcgMSptBVLR4guacb45CdYN770", 8 | "booksInfo" : "https://docs.google.com/document/d/1Q9P_YwHMFULFn84VK-VLggO0JOmGs3Cn-B9klSmIrJs", 9 | "themesInfo" : "https://docs.google.com/document/d/1tKRmULwk2tb_iKXIGD3jDqSNRlFidCDBv8WipIGMzyo", 10 | 11 | 12 | "helpBG" : "(http://telegra.ph/Narchnik-za-izpolzvane-na-bota-01-03)", 13 | "helpEN" : "(http://telegra.ph/User-guide-how-to-use-the-chatbot-01-03)" 14 | 15 | 16 | }, 17 | "moodleConfig": { 18 | "baseUrl": "http://dev.learn.fmi.uni-sofia.bg/", 19 | "serviceUrl": "/webservice/rest/server.php?", 20 | "fun_forumDiscussions": "mod_forum_get_forum_discussions_paginated", 21 | "forumid": 2293 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /chat_bot/info_base_example/questions.json: -------------------------------------------------------------------------------- 1 | { 2 | "questions" : [ 3 | [ 4 | { 5 | "text" : "Memory leak is :", 6 | "answerOptions" : [ 7 | "someone, who forgets a lot", 8 | "a dynamic allocated memory without a pointer to it", 9 | "C/C++ lack of garbage collector", 10 | "a null pointer" 11 | ], 12 | "correctAnswer" : 1 13 | }, 14 | { 15 | "text" : "Утечка на памет наричаме", 16 | "answerOptions" : [ 17 | "някого, който много забравя", 18 | "динамично заделена памет, към която не сочи никой указател", 19 | "липсата на \"събирач на боклука\" в C/C++", 20 | "указател сочещ към NULL" 21 | ], 22 | "correctAnswer" : 1 23 | } 24 | ] 25 | ] 26 | } -------------------------------------------------------------------------------- /chat_bot/log_example/error.log: -------------------------------------------------------------------------------- 1 | 2018-1-25 / 21:11:29 / : ERROR --- EFATAL --- 2 | 2018-1-25 / 21:12:54 / : ERROR --- --- 3 | 2018-1-25 / 21:44:24 / : ERROR --- TypeError: Cannot read property 'forEach' of undefined --- 4 | -------------------------------------------------------------------------------- /chat_bot/log_example/info.log: -------------------------------------------------------------------------------- 1 | 2018-1-25 / 02:03:01 / : INFO --- hi, i am the chatbot :) --- 2 | 2018-1-25 / 02:03:01 / : INFO --- 538534505:AAHy5BujBqmST6aqyAM4aUOg_jgYiULcqqk --- 3 | 2018-1-25 / 02:03:27 / : INFO --- TEST 481967012 OK --- 4 | 2018-1-25 / 02:03:29 / : INFO --- TEST CALLBACK 481967012 OK --- 5 | 2018-1-25 / 21:11:29 / : INFO --- hi, i am the chatbot :) --- 6 | 2018-1-25 / 21:11:29 / : INFO --- --- 7 | 2018-1-25 / 21:11:29 / : ERROR --- EFATAL --- 8 | 2018-1-25 / 21:12:46 / : INFO --- hi, i am the chatbot :) --- 9 | 2018-1-25 / 21:12:46 / : INFO --- 538534505:AAHy5BujBqmST6aqyAM4aUOg_jgYiULcqqk --- 10 | 2018-1-25 / 21:12:47 / : INFO --- GENERAL INFO 481967012 OK --- 11 | 2018-1-25 / 21:12:50 / : INFO --- GENERAL INFO 481967012 OK --- 12 | 2018-1-25 / 21:12:52 / : INFO --- GENERAL INFO 481967012 OK --- 13 | 2018-1-25 / 21:12:54 / : ERROR --- --- 14 | 2018-1-25 / 21:13:58 / : INFO --- hi, i am the chatbot :) --- 15 | 2018-1-25 / 21:13:58 / : INFO --- 538534505:AAHy5BujBqmST6aqyAM4aUOg_jgYiULcqqk --- 16 | 2018-1-25 / 21:19:21 / : INFO --- hi, i am the chatbot :) --- 17 | 2018-1-25 / 21:19:21 / : INFO --- 538534505:AAHy5BujBqmST6aqyAM4aUOg_jgYiULcqqk --- 18 | 2018-1-25 / 21:19:54 / : INFO --- GET NEWS 481967012 OK --- 19 | 2018-1-25 / 21:23:40 / : INFO --- hi, i am the chatbot :) --- 20 | 2018-1-25 / 21:23:40 / : INFO --- 538534505:AAHy5BujBqmST6aqyAM4aUOg_jgYiULcqqk --- 21 | 2018-1-25 / 21:24:24 / : INFO --- hi, i am the chatbot :) --- 22 | 2018-1-25 / 21:24:24 / : INFO --- 538534505:AAHy5BujBqmST6aqyAM4aUOg_jgYiULcqqk --- 23 | 2018-1-25 / 21:27:10 / : INFO --- hi, i am the chatbot :) --- 24 | 2018-1-25 / 21:27:10 / : INFO --- 538534505:AAHy5BujBqmST6aqyAM4aUOg_jgYiULcqqk --- 25 | 2018-1-25 / 21:30:07 / : INFO --- hi, i am the chatbot :) --- 26 | 2018-1-25 / 21:30:07 / : INFO --- 538534505:AAHy5BujBqmST6aqyAM4aUOg_jgYiULcqqk --- 27 | 2018-1-25 / 21:39:24 / : INFO --- STARTED --- 28 | 2018-1-25 / 21:41:49 / : INFO --- STARTED --- 29 | 2018-1-25 / 21:42:05 / : INFO --- GET NEWS 481967012 OK --- 30 | 2018-1-25 / 21:44:24 / : ERROR --- TypeError: Cannot read property 'forEach' of undefined --- 31 | 2018-1-25 / 21:44:26 / : INFO --- GET NEWS 508550150 OK --- 32 | 2018-1-25 / 21:44:48 / : INFO --- GET NEWS 481967012 OK --- 33 | 2018-1-25 / 21:45:01 / : INFO --- STARTED --- 34 | 2018-1-25 / 21:45:09 / : INFO --- GET NEWS 481967012 OK --- 35 | 2018-1-25 / 21:47:18 / : INFO --- GET NEWS 481967012 OK --- 36 | 2018-1-25 / 21:48:49 / : INFO --- TEST 481967012 OK --- 37 | 2018-1-25 / 21:48:52 / : INFO --- TEST CALLBACK 481967012 OK --- 38 | 2018-1-25 / 21:48:53 / : INFO --- TEST CALLBACK 481967012 OK --- 39 | 2018-1-25 / 21:49:01 / : INFO --- TEST CALLBACK 481967012 OK --- 40 | 2018-1-25 / 21:53:15 / : INFO --- GENERAL INFO 508550150 OK --- 41 | 2018-1-25 / 21:53:16 / : INFO --- GENERAL INFO 508550150 OK --- 42 | 2018-1-25 / 21:53:16 / : INFO --- TEST 508550150 OK --- 43 | 2018-1-25 / 21:53:17 / : INFO --- TEST CALLBACK 508550150 OK --- 44 | 2018-1-25 / 21:53:18 / : INFO --- GENERAL INFO 508550150 OK --- 45 | 2018-1-25 / 21:53:18 / : INFO --- GENERAL INFO 508550150 OK --- 46 | 2018-1-25 / 21:53:23 / : INFO --- GET NEWS 508550150 OK --- 47 | 2018-1-25 / 21:53:24 / : INFO --- TEST 508550150 OK --- 48 | 2018-1-25 / 21:53:31 / : INFO --- TEST CALLBACK 508550150 OK --- 49 | 2018-1-25 / 21:53:35 / : INFO --- UNKNOWN 508550150 OK --- 50 | 2018-1-25 / 21:57:16 / : INFO --- UNKNOWN 471870834 OK --- 51 | 2018-1-25 / 21:57:21 / : INFO --- GET NEWS 471870834 OK --- 52 | 2018-1-25 / 21:57:29 / : INFO --- GENERAL INFO 471870834 OK --- 53 | 2018-1-25 / 21:58:04 / : INFO --- GET NEWS 471870834 OK --- 54 | -------------------------------------------------------------------------------- /chat_bot/src/index.js: -------------------------------------------------------------------------------- 1 | const TelegramBot = require('node-telegram-bot-api'), 2 | schedule = require('node-schedule'), 3 | msgHandlers = require('./lib/msgHandlers'), 4 | config = require('./lib/configuration'), 5 | logger = require('./lib/logger'); 6 | 7 | 8 | const { 9 | BG, EN, 10 | commandList, 11 | buttonLists, 12 | enumOptions 13 | } = require('./lib/constants'); 14 | 15 | logger.info('STARTED'); 16 | 17 | //getting the token from the argv 18 | const botToken = config.get('bToken'); 19 | 20 | //creating the bot context 21 | const bot = new TelegramBot(botToken, {polling: true}); 22 | 23 | //will be used to remember language settings 24 | //chatId -> BG | EN 25 | let usersLangs = {}; 26 | 27 | //will hold chatId -> correct answer 28 | //map information for Test option 29 | let callBacks = {}; 30 | 31 | //will hold all bot's stickers 32 | let stickers = []; 33 | 34 | //polling error handler 35 | let online = true; //use to avoid multiple error logs 36 | bot.on('polling_error', (err) => { 37 | 38 | if(online){ 39 | 40 | logger.error(err.code); 41 | online = false; 42 | } 43 | }); 44 | 45 | //on first message ever 46 | bot.onText(/\/start/, (msg) => { 47 | 48 | //saving user preferences 49 | usersLangs[msg.chat.id] = BG; //default language is bulgarian 50 | 51 | msgHandlers.welcome(bot, msg) 52 | .then(() => logger.info('WELCOME ' + msg.chat.id + ' OK')) 53 | .catch(err => logger.error(err.toString())); 54 | }); 55 | 56 | //handling language changes 57 | bot.onText(/\/lang_(en|bg)/, (msg, res) => { 58 | //the result parameter is 59 | //the result of executing exec on the regular expression 60 | 61 | //exec gives us an array with matched results 62 | let ln = (res[1] === 'bg') ? BG : EN; 63 | 64 | //saving the choice 65 | usersLangs[msg.chat.id] = ln; 66 | 67 | msgHandlers.langChanged(bot, msg, ln) 68 | .then(() => logger.info('CHANGE LN ' + msg.chat.id + ' OK')) 69 | .catch(err => logger.error(err.toString())); 70 | }); 71 | 72 | //handling /help option 73 | bot.onText(/\/help+/, (msg) => { 74 | 75 | //will match everything starting with help 76 | 77 | //if undefined => bulgarian 78 | const ln = (usersLangs[msg.chat.id] === EN) ? EN : BG; 79 | 80 | msgHandlers.help(bot, msg, ln) 81 | .then(() => logger.info('HELP ' + msg.chat.id + ' OK')) 82 | .catch(err => logger.error(err.toString())); 83 | }); 84 | 85 | //handling personal information by faculty number 86 | bot.onText(/\d+/, (msg, res) => { 87 | 88 | const ln = (usersLangs[msg.chat.id] === EN) ? EN : BG; 89 | 90 | //faculty ids are at least 5 digits, 91 | //not handled by the regexp on purpose 92 | if (res.input.length < 5 || res.input.length > 8) { 93 | 94 | msgHandlers.invalidFacultyNumber(bot, msg, ln) 95 | .then(() => logger.info('PERSONAL INFO ' + msg.chat.id + ' INVALID ID')) 96 | .catch(err => logger.error(err.toString())); 97 | 98 | return; 99 | } 100 | 101 | msgHandlers.personalInfo(bot, msg, ln) 102 | .then(() => logger.info('PERSONAL INFO ' + msg.chat.id + ' OK')) 103 | .catch(err => logger.error(err.toString())); 104 | }); 105 | 106 | //handle key command 107 | bot.onText(/\/key/, (msg, res) => { 108 | 109 | const ln = (usersLangs[msg.chat.id] === EN) ? EN : BG; 110 | 111 | msgHandlers.getMoodleKey(bot, msg, ln) 112 | .then(() => logger.info('MOODLE KEY ' + msg.chat.id + ' OK')) 113 | .catch(err => logger.error(err.toString())); 114 | }); 115 | 116 | 117 | 118 | bot.on('message', (msg) => { 119 | 120 | OK = true; //back online 121 | 122 | //language of communication 123 | //undefined -> BG 124 | const ln = (usersLangs[msg.chat.id] === EN) ? EN : BG; 125 | 126 | //first of all checking for callback from "test me" option 127 | let isCallback = callBacks[msg.chat.id]; 128 | 129 | //!== undefined 130 | if(isCallback){ 131 | 132 | msgHandlers.testCallback(bot, msg, ln, callBacks) 133 | .then(() => logger.info('TEST CALLBACK ' + msg.chat.id + ' OK')) 134 | .catch(err => logger.error(err.toString())); 135 | 136 | return; 137 | } 138 | 139 | if(msg.sticker !== undefined) { 140 | 141 | let ans_sticker = stickers.find(el => el.emoji == msg.sticker.emoji); 142 | 143 | if(ans_sticker === undefined) { 144 | 145 | if(stickers.length !== 0) { 146 | 147 | let index = Math.floor(Math.random() * Math.floor(stickers.length)); 148 | ans_sticker = stickers[index]; 149 | } else { 150 | 151 | ans_sticker = msg.sticker; 152 | } 153 | } 154 | 155 | bot.sendSticker(msg.chat.id, ans_sticker.file_id) 156 | .then(() => logger.info("sticker OK")) 157 | .catch(() => logger.error("sticker PROBLEM")); 158 | 159 | return; 160 | } 161 | 162 | if(msg.text === undefined) { 163 | 164 | msgHandlers.unknown(bot, msg, ln) 165 | .then(msg => logger.info('UNKNOWN ' + msg.chat.id + ' OK')) 166 | .catch(err => logger.error(err.toString())); 167 | return; 168 | } 169 | 170 | //it is a known command, it should be handled somewhere else 171 | if(commandList.find((el) => el === msg.text) !== undefined || 172 | msg.text.indexOf('/help') !== -1 || !isNaN(msg.text)) 173 | return; 174 | 175 | //searching for the option in the current language 176 | const optIndex = buttonLists[ln].indexOf(msg.text); 177 | 178 | 179 | switch (optIndex) { 180 | 181 | case enumOptions.G_INFO_INDEX: 182 | 183 | msgHandlers.getGeneralInfo(bot, msg, ln) 184 | .then(() => logger.info('GENERAL INFO ' + msg.chat.id + ' OK')) 185 | .catch(err => logger.error(err.toString())); 186 | 187 | return; 188 | 189 | case enumOptions.TEST_INDEX: 190 | 191 | msgHandlers.testMe(bot, msg, ln, callBacks) 192 | .then(() => logger.info('TEST ' + msg.chat.id + ' OK')) 193 | .catch(err => logger.error(err.toString())); 194 | 195 | return; 196 | 197 | case enumOptions.INVALID: 198 | 199 | msgHandlers.unknown(bot, msg, ln) 200 | .then(msg => logger.info('UNKNOWN ' + msg.chat.id + ' OK')) 201 | .catch(err => logger.error(err.toString())); 202 | 203 | return; 204 | 205 | case enumOptions.NEWS_INDEX: 206 | 207 | msgHandlers.getNews(bot, msg, ln) 208 | .then(msg => logger.info('GET NEWS ' + msg.chat.id + ' OK')) 209 | .catch(err => logger.error(err.toString())); 210 | 211 | return; 212 | 213 | case enumOptions.ASSIGN_INDEX: 214 | 215 | msgHandlers.getAssignments(bot, msg, ln) 216 | .then(msg => logger.info('ASSIGNMENTS ' + msg.chat.id + ' OK')) 217 | .catch(err => logger.error(err.toString())); 218 | return; 219 | 220 | case enumOptions.P_INFO_INDEX: 221 | 222 | let answer = ln ? 'моля, въведи факултетния си номер 🎓' 223 | : 'please, enter your faculty number 🎓'; 224 | 225 | bot.sendMessage(msg.chat.id, answer, { 226 | reply_markup: JSON.stringify({ //hides the keyboard 227 | hide_keyboard: true 228 | }) 229 | }); 230 | return; 231 | 232 | default: 233 | //debug reason only... 234 | bot.sendMessage(msg.chat.id, msg.text + ' pressed!'); 235 | } 236 | }); 237 | 238 | 239 | // Handle callback queries from inline keyboard 240 | bot.on('callback_query', callbackQuery => { 241 | 242 | const wantedId = parseInt(callbackQuery.data); 243 | const msg = callbackQuery.message; 244 | 245 | msgHandlers.getNewsContain(bot, msg, wantedId) 246 | .then(msg => logger.info('GET NEWS CONTAIN ' + msg.chat.id + ' OK')) 247 | .catch(err => logger.error(err.toString())); 248 | }); 249 | 250 | 251 | bot.getStickerSet('HackerBoyStickers') 252 | .then(res => { 253 | 254 | stickers = res.stickers; 255 | }) 256 | 257 | //update all info on starting... 258 | msgHandlers.update() 259 | .then(() => logger.info('UPDATE NEWS AND ASSIGNMENTS : OK')) 260 | .catch(err => logger.error('UPDATE : ' + err.toString())); 261 | 262 | 263 | //try to update on every five minutes 264 | const updateJob = schedule.scheduleJob('*/15 * * * *', () => { 265 | 266 | msgHandlers.update() 267 | .then(() => logger.info('UPDATE NEWS AND ASSIGNMENTS : OK')) 268 | .catch(err => logger.error('UPDATE : ' + err.toString())); 269 | }); -------------------------------------------------------------------------------- /chat_bot/src/lib/configuration.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'), 2 | path = require('path'), 3 | nconf = require('nconf'); 4 | 5 | //a wrapper class for working with nconf 6 | class Config { 7 | 8 | constructor() { 9 | 10 | //everything will be 'in-memory' 11 | nconf.use('memory'); 12 | 13 | //take all arguments from the command line 14 | nconf.argv(); 15 | 16 | nconf.required(['pName','bToken', 'mToken', 'configFile']); 17 | 18 | process.title = nconf.get('pName'); //process rename better DevOps 19 | 20 | //loading all config data 21 | nconf.add('conf', { type: 'file', file: nconf.get('configFile')}); 22 | 23 | //will throw an error if any key is missing 24 | nconf.required(['externalLinks', 'questionsPath', 25 | 'moodleConfig', "logDirectoryPath"]); 26 | 27 | //loading all questions 28 | nconf.add('quest', { type: 'file', file: nconf.get('questionsPath') }); 29 | //adding it to the required fields 30 | nconf.required(['questions']); 31 | 32 | } 33 | 34 | get(key) { 35 | 36 | return nconf.get(key); 37 | } 38 | } 39 | 40 | //only one instance 41 | let config; 42 | 43 | //if one of the keys is missing 44 | //the program cannot start... 45 | try { 46 | 47 | config = new Config(); 48 | 49 | } catch (err) { 50 | 51 | console.error('FATAL ERROR WHILE INITIALIZATION : ' + err.toString()); 52 | process.exit(-1); 53 | } 54 | 55 | module.exports = config; -------------------------------------------------------------------------------- /chat_bot/src/lib/constants.js: -------------------------------------------------------------------------------- 1 | const config = require('./configuration'); 2 | 3 | //loading all URLs from the config file 4 | const externalLinks = config.get('externalLinks'); 5 | 6 | //loading questions from file 7 | const questions = config.get('questions'); 8 | 9 | module.exports = { 10 | 11 | //taken externally from a file 12 | questionsList : questions, 13 | 14 | //language number constants 15 | BG : 1, 16 | EN : 0, 17 | 18 | //all supported commands 19 | commandList : ['/lang_bg' , '/lang_en', '/start', '/help', '/key'], 20 | 21 | //all supported buttons / text messages 22 | buttonLists : [ 23 | 24 | ['News','Assignments', 'Personal information','General information','Test my knowledge'], 25 | 26 | [ 'Новини', 'Задания', 'Лична информация', 'Обща информация', 'Тествай познанията ми'] 27 | 28 | ], 29 | 30 | //constants for indexes of the buttons 31 | enumOptions: { 32 | NEWS_INDEX: 0, 33 | ASSIGN_INDEX: 1, 34 | P_INFO_INDEX: 2, 35 | G_INFO_INDEX: 3, 36 | TEST_INDEX: 4, 37 | INVALID : -1 38 | }, 39 | 40 | 41 | //keyboardOptions[0] - > object with options in EN 42 | //keyboardOptions[1] - > with options in BG 43 | keyboardOptions : [ 44 | 45 | { 46 | 'reply_markup': { 47 | 48 | 'keyboard': [ 49 | ['News'], 50 | ['Assignments', 'Personal information'], 51 | ['General information'], 52 | ['Test my knowledge'] 53 | ] 54 | } 55 | 56 | }, 57 | 58 | { 59 | 60 | 'reply_markup': { 61 | 62 | 'keyboard': [ 63 | ['Новини'], 64 | ['Задания', 'Лична информация'], 65 | ['Обща информация'], 66 | ['Тествай познанията ми'] 67 | ] 68 | } 69 | } 70 | 71 | ], 72 | 73 | //test keyboard options 74 | testKeyboardOptions : [ 75 | 76 | { 77 | 'reply_markup': { 78 | 79 | 'keyboard': [ 80 | ['A', 'B', 'C', 'D'], 81 | ['See the answer'], 82 | ['Give me another question'] 83 | ] 84 | } 85 | 86 | }, 87 | 88 | { 89 | 90 | 'reply_markup': { 91 | 92 | 'keyboard': [ 93 | ['А', 'Б', 'В', 'Г'], 94 | ['Виж отговора'], 95 | ['Задай ми друг въпорс'] 96 | ] 97 | } 98 | } 99 | 100 | ], 101 | 102 | //creates inline keyboard 103 | //to give options for 104 | //general information links 105 | generalInfo: [ 106 | 107 | { 108 | 'reply_markup': { 109 | 'inline_keyboard': [ 110 | 111 | [ 112 | { 113 | text: 'course info', 114 | url: externalLinks.courseInfo 115 | }, 116 | { 117 | text: 'staff & contacts', 118 | url: externalLinks.teamInfo 119 | } 120 | ], 121 | [ 122 | { 123 | text: 'books & links', 124 | url: externalLinks.booksInfo 125 | }, 126 | { 127 | text: 'syllabus', 128 | url: externalLinks.themesInfo 129 | } 130 | ] 131 | ] 132 | } 133 | }, 134 | { 135 | 'reply_markup': { 136 | 'inline_keyboard': [ 137 | 138 | [ 139 | { 140 | text: 'За курса', 141 | url: externalLinks.courseInfo 142 | }, 143 | { 144 | text: 'екип & контакти', 145 | url: externalLinks.teamInfo 146 | } 147 | ], 148 | [ 149 | { 150 | text: 'книги & връзки', 151 | url: externalLinks.booksInfo 152 | }, 153 | { 154 | text: 'конспект', 155 | url: externalLinks.themesInfo 156 | } 157 | ] 158 | ] 159 | } 160 | } 161 | ], 162 | 163 | //messages for picking a choice 164 | choose : [ 165 | 166 | 'Chose from below 🔗 :' 167 | , 168 | 'Избери от опциите 🔗 :' 169 | ], 170 | 171 | //messages for unknown commands 172 | unknownCommand : [ 173 | 174 | 'I don\'t understand you 😓\nI am just a chatbot a don\'t have all the answers🧐' 175 | , 176 | 'Не те разбирам 😓\nАз съм просто чатбот нямам отговори за всичко 🧐' 177 | ], 178 | 179 | //messages for invalid test answer 180 | invalidTestAnswer : [ 181 | 182 | 'Sorry, but I don\'t understand your answer 🤔\nThe next time just press a button 🤓' 183 | , 184 | 'Съжалявам, но не мога да разбера отговора ти 🤔\nСледващия път просто натисни бутонче 🤓' 185 | ], 186 | 187 | //messages for successful language change 188 | languageChanged :[ 189 | 190 | 'Now we are talking in english! 🇬🇧󠁧󠁢󠁥󠁮󠁧󠁿' 191 | , 192 | 'Вече си говорим на български! 🇧🇬' 193 | ], 194 | 195 | //message for invalid faculty number 196 | invalidFn :[ 197 | 198 | 'Invalid faculty number! ⚠️' 199 | , 200 | 'Невалиден факултетен номер! ⚠️' 201 | ], 202 | 203 | unseenFn :[ 204 | 205 | 'I can\'t see a student with such faculty number from this course! 👀' 206 | , 207 | 'Не "виждам" студент с такъв факултетен номер в този курс! 👀' 208 | ], 209 | 210 | //message for internal error 211 | internalError :[ 212 | 213 | 'Internal error, please excuse us! 🙇\nwrite us about the problem : fmichatbot@gmail.com' 214 | , 215 | 'Вътрешна грешка, моля да ни извиниш! 🙇\nпиши ни за проблема : fmichatbot@gmail.com' 216 | ], 217 | 218 | //messages for access denied error 219 | accessDeniedEnrol :[ 220 | 221 | 'Access denied : Not enrolled! ⛔' 222 | , 223 | 'Достъпът отказан : Не си записан/а за този курс! ⛔' 224 | ], 225 | 226 | accessDeniedOtherFn :[ 227 | 228 | 'Access denied : This is not your account! 🚫' 229 | , 230 | 'Достъпът отказан : Това не е твоят профил! 🚫' 231 | 232 | ], 233 | 234 | accessDeniedMoodleConfig :[ 235 | 236 | 'Access denied : Moodle profile is not configured! ⚠️' 237 | , 238 | 'Достъпът отказан : Профилът ти в moodle не е конфигуриран! ⚠️' 239 | ], 240 | 241 | //message for get news 242 | news :[ 243 | 244 | 'These are the tittles of forum news, click on a tittle to read it\'s contain 📃\n' 245 | , 246 | 'Това са заглавията на новините от форума, кликни на някое заглавиe, за да получиш съдържанието на новината 📃\n' 247 | ], 248 | 249 | keyInfo : [ 250 | 251 | 'This is your telegram key for moodle 🔑 : ' 252 | , 253 | 'Това е ключът ти за moodle 🔑 : ' 254 | ], 255 | 256 | //links to instant views 257 | //urls with how to use information 258 | helpUrl : [ 259 | //'[click here]' + 260 | externalLinks.helpEN 261 | , 262 | //'[кликни тук]' + 263 | externalLinks.helpBG 264 | ] 265 | }; -------------------------------------------------------------------------------- /chat_bot/src/lib/logger.js: -------------------------------------------------------------------------------- 1 | const winston = require('winston'), 2 | config = require('./configuration'), 3 | fs = require( 'fs' ), 4 | path = require('path'); 5 | 6 | const dirPath = config.get('logDirectoryPath'); 7 | 8 | if ( !fs.existsSync( dirPath ) ) { 9 | // Create the directory if it does not exist 10 | fs.mkdirSync( dirPath ); 11 | } 12 | 13 | //how to format the timestamp 14 | const tsFormat = () => { 15 | 16 | let date = new Date(); 17 | 18 | return date.toLocaleDateString() + ' / ' 19 | + date.toLocaleTimeString() + ' / '; 20 | } 21 | 22 | //how each text to be logged 23 | const formatFunc = (options) => { 24 | 25 | return tsFormat() + ' : ' + 26 | options.level.toUpperCase() + ' --- ' + 27 | (options.message ? options.message : '') 28 | + ' --- '; 29 | } 30 | 31 | const logger = new (winston.Logger)({ 32 | transports: [ 33 | //debugger log config 34 | new (winston.transports.Console)({ 35 | level: 'debug', 36 | json: false, 37 | formatter: formatFunc 38 | }), 39 | //info logger config 40 | new (winston.transports.File)({ 41 | name: 'info-file', 42 | filename: path.join(dirPath, '/infoLog.txt'), 43 | level: 'info', 44 | json: false, 45 | formatter : formatFunc 46 | }), 47 | 48 | //error logger config 49 | new (winston.transports.File)({ 50 | name: 'error-file', 51 | filename: path.join(dirPath, '/errorLog.txt'), 52 | level: 'error', 53 | json: false, 54 | formatter: formatFunc 55 | 56 | }) 57 | ] 58 | }); 59 | 60 | //logger.add(winston.transports.Console); 61 | try{ 62 | module.exports = logger;}catch(err) 63 | {console.log(11)}; -------------------------------------------------------------------------------- /chat_bot/src/lib/moodleAPI.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | const config = require('./configuration'); 4 | 5 | const moodleConfig = config.get('moodleConfig'), 6 | moodleToken = config.get('mToken'); 7 | 8 | //a request template to get 9 | //a forum news from moodle 10 | const forumReq = axios.create({ 11 | 12 | url : moodleConfig.serviceUrl , 13 | baseURL: moodleConfig.baseUrl, 14 | method : 'get', 15 | timeout: 5000, 16 | 17 | params: { 18 | wstoken: moodleToken, 19 | wsfunction : moodleConfig.fun_forumDiscussions, 20 | forumid : moodleConfig.forumid, 21 | moodlewsrestformat : 'json' 22 | }, 23 | 24 | }); 25 | 26 | //a request template to get 27 | //a forum news from moodle 28 | const assignReq = axios.create({ 29 | 30 | url : moodleConfig.serviceUrl , 31 | baseURL: moodleConfig.baseUrl, 32 | method : 'get', 33 | timeout: 5000, 34 | 35 | params: { 36 | wstoken: moodleToken, 37 | wsfunction : moodleConfig.fun_assignments, 38 | courseids : [moodleConfig.courseid], 39 | moodlewsrestformat : 'json' 40 | }, 41 | 42 | }); 43 | 44 | //a request to get a user 45 | //from moodle by faculty number 46 | const userReq = axios.create({ 47 | 48 | url : moodleConfig.serviceUrl , 49 | baseURL: moodleConfig.baseUrl, 50 | method : 'get', 51 | timeout: 3000, 52 | 53 | params: { 54 | wstoken: moodleToken, 55 | wsfunction : moodleConfig.fun_user, 56 | field : moodleConfig.field, 57 | values : [0], 58 | moodlewsrestformat : 'json' 59 | }, 60 | 61 | }); 62 | 63 | //a request template to get 64 | //all courses that a student is enrolled to 65 | const coursesReq = axios.create({ 66 | 67 | url : moodleConfig.serviceUrl , 68 | baseURL: moodleConfig.baseUrl, 69 | method : 'get', 70 | timeout: 5000, 71 | 72 | params: { 73 | wstoken: moodleToken, 74 | wsfunction : moodleConfig.fun_courses, 75 | userid : 0, 76 | moodlewsrestformat : 'json' 77 | }, 78 | 79 | }); 80 | 81 | //a request template to get 82 | //all grades for a given student 83 | const gradesReq = axios.create({ 84 | 85 | url : moodleConfig.serviceUrl , 86 | baseURL: moodleConfig.baseUrl, 87 | method : 'get', 88 | timeout: 5000, 89 | 90 | params: { 91 | wstoken: moodleToken, 92 | wsfunction : moodleConfig.fun_grades, 93 | courseid : moodleConfig.courseid, 94 | userid : 0, 95 | moodlewsrestformat : 'json' 96 | }, 97 | 98 | }); 99 | 100 | module.exports = { 101 | forumReq, assignReq, userReq, 102 | gradesReq, coursesReq 103 | }; 104 | -------------------------------------------------------------------------------- /chat_bot/src/lib/msgHandlers.js: -------------------------------------------------------------------------------- 1 | const htmlToText = require('html-to-text'); 2 | 3 | const { 4 | keyboardOptions, 5 | unknownCommand, languageChanged, 6 | helpUrl, generalInfo, 7 | choose, testKeyboardOptions, 8 | questionsList, invalidFn, 9 | internalError, news, 10 | accessDeniedEnrol, accessDeniedOtherFn, 11 | accessDeniedMoodleConfig, unseenFn, 12 | keyInfo, invalidTestAnswer, 13 | EN, BG } = require('./constants'); 14 | 15 | 16 | const { forumReq, assignReq, 17 | userReq, gradesReq, 18 | coursesReq, updatesReq } = require('./moodleAPI'); 19 | 20 | //each function will handle a message 21 | //received by the bot 22 | //and will return a promise 23 | module.exports = { 24 | 25 | welcome: function (bot, msg) { 26 | 27 | //first name is required 28 | let name = msg.chat.first_name; 29 | 30 | if (msg.chat.last_name !== undefined) 31 | name += ' ' + msg.chat.last_name; 32 | 33 | const answerEN = `Welcome,${name}!\nI am the FMI\'s chatbot 🤖. \ 34 | \n\n${keyInfo[EN]}${msg.from.id} \ 35 | \n\nuse /lang_en to change the language to english 🇬🇧󠁧󠁢󠁥󠁮󠁧󠁿 \ 36 | \nthen type /help to see how to configure your moodle profile \ 37 | \nand how to communicate with me 😎`; 38 | 39 | 40 | const answerBG = `Здравей,${name}!\nАз съм чатботът на ФМИ 🤖. \ 41 | \n\n${keyInfo[BG]}${msg.from.id} \ 42 | \nИзползвай /help за да видиш\nкак да конфигурираш своя moodle профил \ 43 | \nи как най - лесно да комуникираш с мен 😎`; 44 | 45 | 46 | //because sendMessage changes its param 47 | //deep copy objects 48 | //const optEN = JSON.parse(JSON.stringify(keyboardOptions[EN])); 49 | const optBG = JSON.parse(JSON.stringify(keyboardOptions[BG])); 50 | 51 | return bot.sendMessage(msg.chat.id, answerEN)//, optEN) 52 | .then(() => bot.sendMessage(msg.chat.id, answerBG, optBG)) 53 | }, 54 | 55 | langChanged: function (bot, msg, ln) { 56 | 57 | const opt = JSON.parse(JSON.stringify(keyboardOptions[ln])); 58 | 59 | return bot.sendMessage(msg.chat.id, languageChanged[ln], opt); 60 | }, 61 | 62 | help: function (bot, msg, ln) { 63 | 64 | return bot.sendMessage(msg.chat.id, helpUrl[ln], { parse_mode: "Markdown" }); 65 | }, 66 | 67 | unknown: function (bot, msg, ln) { 68 | 69 | return bot.sendMessage(msg.chat.id, unknownCommand[ln], keyboardOptions[ln]); 70 | }, 71 | 72 | getGeneralInfo: function (bot, msg, ln) { 73 | 74 | return bot.sendMessage(msg.chat.id, choose[ln], generalInfo[ln]); 75 | }, 76 | 77 | testCallback: function (bot, msg, ln, callBacks) { 78 | 79 | const ansOpt = ['ABCD', 'АБВГ']; 80 | let userAnswer = ansOpt[ln].indexOf(msg.text); 81 | let [questionId, correctAnswer] = callBacks[msg.chat.id]; 82 | 83 | //remove from callback list 84 | delete callBacks[msg.chat.id]; 85 | 86 | //see answer option 87 | if (msg.text === 'See the answer' || 88 | msg.text === 'Виж отговора') { 89 | 90 | let answerMsg = ln ? 'Верният отговор е: \n' 91 | : 'The correct answer is :\n '; 92 | 93 | answerMsg += questionsList[questionId][ln].answerOptions[correctAnswer]; 94 | 95 | const opt = JSON.parse(JSON.stringify(keyboardOptions[ln])); 96 | return bot.sendMessage(msg.chat.id, answerMsg, opt); 97 | } 98 | 99 | //another question option 100 | if (msg.text === 'Give me another question' || 101 | msg.text === 'Задай ми друг въпорс') { 102 | 103 | return this.testMe(bot, msg, ln, callBacks); 104 | } 105 | 106 | //invalid answer 107 | if (userAnswer === -1) 108 | return bot.sendMessage(msg.chat.id, invalidTestAnswer[ln], keyboardOptions[ln]); 109 | 110 | //wrong answer 111 | if (userAnswer !== -1 && userAnswer !== correctAnswer) { 112 | 113 | let answerMsg = ln ? 'Грешен отговор 😞\nВерният отговор е :\n' 114 | :'Wrong answer 😞\nThe correct answer is :\n '; 115 | 116 | answerMsg += questionsList[questionId][ln].answerOptions[correctAnswer]; 117 | 118 | const opt = JSON.parse(JSON.stringify(keyboardOptions[ln])); 119 | return bot.sendMessage(msg.chat.id, answerMsg, opt); 120 | } 121 | 122 | //correct answer 123 | let answerMsg = ln ? 'Верен отговор 👍' : 'Correct answer 👍\n'; 124 | const opt = JSON.parse(JSON.stringify(keyboardOptions[ln])); 125 | return bot.sendMessage(msg.chat.id, answerMsg, opt); 126 | }, 127 | 128 | testMe: function (bot, msg, ln, callBacks) { 129 | 130 | let qIndex = getRandomInt(questionsList.length); 131 | 132 | let question = questionsList[qIndex][ln]; 133 | 134 | let answer = formatQuestion(question, ln); 135 | 136 | return bot.sendMessage(msg.chat.id, answer, testKeyboardOptions[ln]) 137 | .then(() => callBacks[msg.chat.id] = [qIndex, question.correctAnswer]); 138 | }, 139 | 140 | getNews: function (bot, msg, ln) { 141 | 142 | if (discussions.length === 0) 143 | return bot.sendMessage(msg.chat.id, internalError[ln]); 144 | 145 | let res = getTittles(discussions); 146 | return bot.sendMessage(msg.chat.id, news[ln], res); 147 | }, 148 | 149 | getNewsContain : function (bot, msg, action) { 150 | 151 | let disc = discussions.find(el => el.id === action); 152 | 153 | if(disc === undefined) 154 | return bot.sendMessage(msg.chat.id, 'Currently unavailable'); 155 | 156 | //only basic HTML is supported ... 157 | //all moodle discussions are formatted as HTML, 158 | //so we need to convert them to plain text 159 | let answer = htmlToText.fromString(disc.message); 160 | 161 | return bot.sendMessage(msg.chat.id, answer)//, { parse_mode: "HTML" }) 162 | //.catch(() => bot.sendMessage(msg.chat.id, disc.message)); // send raw .. :( 163 | }, 164 | 165 | getAssignments : function (bot, msg, ln) { 166 | 167 | if (assignments.length === 0) 168 | return bot.sendMessage(msg.chat.id, internalError[ln]); 169 | 170 | let res = getAssignmentsInfo(assignments, ln); 171 | return bot.sendMessage(msg.chat.id, res); 172 | }, 173 | 174 | personalInfo: function (bot, msg, ln) { 175 | 176 | let facultyId = msg.text; 177 | let courseid = gradesReq.defaults.params['courseid']; 178 | let userid; 179 | 180 | userReq.defaults.params['values'] = [facultyId]; 181 | 182 | return userReq.request() 183 | .then(response => { 184 | 185 | try { 186 | coursesReq.defaults.params['userid'] = 187 | checkUserTelegram(response.data[0], msg.from.id.toString(), ln); 188 | } 189 | catch (err) { //authentication error 190 | bot.sendMessage(msg.chat.id, err, keyboardOptions[ln]); 191 | throw err; 192 | } 193 | 194 | return coursesReq.request(); 195 | 196 | }) 197 | .then(response => { 198 | 199 | let courses = response.data; 200 | 201 | let ind = courses.find(el => el.id === courseid); 202 | 203 | if (ind === undefined) { 204 | 205 | bot.sendMessage(msg.chat.id, accessDeniedEnrol[ln], keyboardOptions[ln]); 206 | throw accessDeniedEnrol[ln]; 207 | } 208 | 209 | gradesReq.defaults.params['userid'] = 210 | coursesReq.defaults.params['userid']; 211 | 212 | return gradesReq.request(); 213 | 214 | }) 215 | .then(response => formatGradesAnswer(response.data, ln)) 216 | .then(res => bot.sendMessage(msg.chat.id, res, keyboardOptions[ln])) 217 | .catch(err => { 218 | 219 | if (err.code !== undefined) { 220 | bot.sendMessage(msg.chat.id, internalError[ln], keyboardOptions[ln]); 221 | throw err.code; 222 | } 223 | 224 | throw err; 225 | }); 226 | }, 227 | 228 | invalidFacultyNumber: function (bot, msg, ln) { 229 | 230 | return bot.sendMessage(msg.chat.id, invalidFn[ln], keyboardOptions[ln]); 231 | }, 232 | 233 | getMoodleKey : function (bot, msg, ln) { 234 | 235 | return bot.sendMessage(msg.chat.id, keyInfo[ln] + msg.from.id, keyboardOptions[ln]); 236 | }, 237 | 238 | update: function () { 239 | 240 | return fetchDiscussions() 241 | .then(() => { return fetchAssignments() }) 242 | } 243 | }; 244 | 245 | //will hold all forum's discussions 246 | let discussions = []; 247 | //will hold all assignments 248 | let assignments = []; 249 | 250 | //makes a request to moodule in order to get 251 | //all course assignments 252 | const fetchAssignments = () => { 253 | 254 | return assignReq.request() 255 | .then(response => assignments = response.data.courses[0].assignments); 256 | 257 | } 258 | 259 | //makes a request to moodule in order to get 260 | //all news from the forum 261 | const fetchDiscussions = () => { 262 | 263 | return forumReq.request() 264 | .then(response => discussions = response.data.discussions); 265 | 266 | } 267 | 268 | //a helper function to get tittles of news in forum 269 | //and create an inline keyboard from them 270 | const getTittles = (discussions) => { 271 | 272 | const opts = { 273 | reply_markup: { 274 | inline_keyboard: [ 275 | ] 276 | } 277 | }; 278 | 279 | 280 | discussions.forEach(el => { 281 | opts.reply_markup.inline_keyboard.push([{ 282 | text : el.name, 283 | callback_data : el.id 284 | }]); 285 | }); 286 | 287 | return opts; 288 | } 289 | 290 | //a helper function to check fn - telegram id 291 | //personal data of a user 292 | const checkUserTelegram = (user, fromId, ln) => { 293 | 294 | if(user === undefined) 295 | throw unseenFn[ln]; 296 | 297 | if(user.customfields === undefined) 298 | throw accessDeniedMoodleConfig[ln]; 299 | 300 | let telegramId = user.customfields.find(el => el.shortname === "telegramid").value; 301 | 302 | if(telegramId === undefined) 303 | throw accessDeniedMoodleConfig[ln]; 304 | 305 | if(telegramId !== fromId) 306 | throw accessDeniedOtherFn[ln]; 307 | 308 | return user.id; 309 | } 310 | 311 | //a helper that returns all 312 | //upcoming assignments formatted 313 | const getAssignmentsInfo = (assignments, ln) => { 314 | 315 | //UTC current time 316 | let currTime = Math.floor((new Date()).getTime() / 1000); 317 | 318 | let upcomingAssignments = assignments.filter(assign => assign.duedate > currTime); 319 | 320 | if(upcomingAssignments.length === 0){ 321 | 322 | return ln ? "Нямаш предстоящи задания, можеш да си починеш 🙃" 323 | : "There aren\'t any assignments, you can have a rest 🙃" 324 | } 325 | 326 | let res = ln ? 'Предстоящите ти задания са 🗓️ : \n\n' 327 | : 'Your upcoming assignments are 🗓️ : \n\n'; 328 | 329 | upcomingAssignments.forEach(assign => res += formatAssignment(assign, ln)); 330 | 331 | return res; 332 | } 333 | 334 | //a helper to format a single assignment 335 | const formatAssignment = (assignment, ln) => { 336 | 337 | let helpWords = [['\n\nfrom : ', '\nto : ', '\nwhere : '], 338 | ['\n\nот : ', '\nдо : ', '\nкъде : ']]; 339 | 340 | //UNIX timestamp is in seconds ... JS's is in milliseconds 341 | let timeDiff = 180 * 60, //BG is +2GMT 342 | from = (assignment.allowsubmissionsfromdate + timeDiff) * 1000, 343 | to = (assignment.duedate + timeDiff) * 1000; 344 | 345 | let fromTimeFormated = (new Date(from)).toUTCString().slice(0, -4), //removes " GMT" 346 | dueTimeFormated = (new Date(to)).toUTCString().slice(0, -4); 347 | 348 | let where = 'moodle'; 349 | 350 | let start = assignment.intro.indexOf('Ще проведем'); 351 | 352 | if(start >= 0){ 353 | 354 | start = assignment.intro.indexOf('в '); 355 | let end = assignment.intro.indexOf('.'); 356 | 357 | where = assignment.intro.slice(start, end); 358 | } 359 | 360 | return assignment.name 361 | + helpWords[ln][0] + fromTimeFormated 362 | + helpWords[ln][1] + dueTimeFormated 363 | + helpWords[ln][2] + where 364 | + '\n\n'; 365 | } 366 | 367 | //a helper function used for formating the answer with 368 | //a user's grades 369 | const formatGradesAnswer = (user, ln) => { 370 | 371 | let res = ln ? "Оценките, които имаме за теб са 🏫 :\n" 372 | : "Your grades are 🏫 :\n" ; 373 | 374 | let arrGrades = user.usergrades[0].gradeitems; 375 | 376 | arrGrades.forEach(el => { 377 | //skip overall grade 378 | if (el.itemname !== null) { 379 | 380 | res += el.itemname + '\n' 381 | + el.gradeformatted + ' / ' 382 | + el.grademax + '\n\n'; 383 | } 384 | 385 | }) 386 | 387 | return res; 388 | } 389 | 390 | //a helper function to represent a question 391 | //as a test 392 | const formatQuestion = (question, ln) => { 393 | 394 | let format = [ 395 | 396 | ['\nA) ', '\nB) ', '\nC) ', '\nD) '], 397 | ['\nА) ', '\nБ) ', '\nВ) ', '\nГ) '] 398 | ]; 399 | 400 | return question.text + 401 | format[ln][0] + question.answerOptions[0] + 402 | format[ln][1] + question.answerOptions[1] + 403 | format[ln][2] + question.answerOptions[2] + 404 | format[ln][3] + question.answerOptions[3] + '\n'; 405 | 406 | } 407 | 408 | //a helper function to get a random index for 409 | //a question from the test 410 | const getRandomInt = (max) => { 411 | 412 | return Math.floor(Math.random() * Math.floor(max)); 413 | } 414 | 415 | //used to replace substring in a given string 416 | const replaceAll = (str, find, replace) => { 417 | 418 | return str.replace(new RegExp(find, 'g'), replace); 419 | } 420 | 421 | //a helper designed to removed unsupported HTML tags 422 | //currently unused 423 | const clearTags = (str) => { 424 | 425 | let answer = replaceAll(str, '', '\n'); 426 | answer = replaceAll(answer, '

', ''); 427 | answer = replaceAll(answer, '', '\n'); 428 | 429 | return answer; 430 | } -------------------------------------------------------------------------------- /chat_bot/src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fmichatbot", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ajv": { 8 | "version": "5.5.2", 9 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", 10 | "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", 11 | "requires": { 12 | "co": "4.6.0", 13 | "fast-deep-equal": "1.0.0", 14 | "fast-json-stable-stringify": "2.0.0", 15 | "json-schema-traverse": "0.3.1" 16 | } 17 | }, 18 | "ansi-regex": { 19 | "version": "2.1.1", 20 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 21 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 22 | }, 23 | "array.prototype.findindex": { 24 | "version": "2.0.2", 25 | "resolved": "https://registry.npmjs.org/array.prototype.findindex/-/array.prototype.findindex-2.0.2.tgz", 26 | "integrity": "sha1-WAaNJYh+9QXknckssAxE3O5VsGc=", 27 | "requires": { 28 | "define-properties": "1.1.2", 29 | "es-abstract": "1.10.0" 30 | } 31 | }, 32 | "asn1": { 33 | "version": "0.2.3", 34 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 35 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 36 | }, 37 | "assert-plus": { 38 | "version": "1.0.0", 39 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 40 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 41 | }, 42 | "async": { 43 | "version": "1.5.2", 44 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 45 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" 46 | }, 47 | "asynckit": { 48 | "version": "0.4.0", 49 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 50 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 51 | }, 52 | "aws-sign2": { 53 | "version": "0.7.0", 54 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 55 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 56 | }, 57 | "aws4": { 58 | "version": "1.6.0", 59 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", 60 | "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" 61 | }, 62 | "axios": { 63 | "version": "0.17.1", 64 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz", 65 | "integrity": "sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0=", 66 | "requires": { 67 | "follow-redirects": "1.4.1", 68 | "is-buffer": "1.1.6" 69 | } 70 | }, 71 | "bcrypt-pbkdf": { 72 | "version": "1.0.1", 73 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", 74 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", 75 | "optional": true, 76 | "requires": { 77 | "tweetnacl": "0.14.5" 78 | } 79 | }, 80 | "bl": { 81 | "version": "1.2.1", 82 | "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", 83 | "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=", 84 | "requires": { 85 | "readable-stream": "2.3.3" 86 | } 87 | }, 88 | "bluebird": { 89 | "version": "3.5.1", 90 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 91 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 92 | }, 93 | "boom": { 94 | "version": "4.3.1", 95 | "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", 96 | "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", 97 | "requires": { 98 | "hoek": "4.2.0" 99 | } 100 | }, 101 | "camelcase": { 102 | "version": "2.1.1", 103 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", 104 | "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" 105 | }, 106 | "caseless": { 107 | "version": "0.12.0", 108 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 109 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 110 | }, 111 | "cliui": { 112 | "version": "3.2.0", 113 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", 114 | "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", 115 | "requires": { 116 | "string-width": "1.0.2", 117 | "strip-ansi": "3.0.1", 118 | "wrap-ansi": "2.1.0" 119 | } 120 | }, 121 | "co": { 122 | "version": "4.6.0", 123 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 124 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 125 | }, 126 | "code-point-at": { 127 | "version": "1.1.0", 128 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 129 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 130 | }, 131 | "colors": { 132 | "version": "1.0.3", 133 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", 134 | "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" 135 | }, 136 | "combined-stream": { 137 | "version": "1.0.5", 138 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", 139 | "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", 140 | "requires": { 141 | "delayed-stream": "1.0.0" 142 | } 143 | }, 144 | "core-util-is": { 145 | "version": "1.0.2", 146 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 147 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 148 | }, 149 | "cron-parser": { 150 | "version": "2.4.4", 151 | "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.4.4.tgz", 152 | "integrity": "sha512-lNWu5pGRGF7y4kl/uRXY69mC8n0qhjTIDQmc3MIfNY5eEvGyYqFPewn+2YQXybJoa2LVVOmDQ/1WTWyQzAM8uA==", 153 | "requires": { 154 | "is-nan": "1.2.1", 155 | "moment-timezone": "0.5.14" 156 | } 157 | }, 158 | "cryptiles": { 159 | "version": "3.1.2", 160 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", 161 | "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", 162 | "requires": { 163 | "boom": "5.2.0" 164 | }, 165 | "dependencies": { 166 | "boom": { 167 | "version": "5.2.0", 168 | "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", 169 | "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", 170 | "requires": { 171 | "hoek": "4.2.0" 172 | } 173 | } 174 | } 175 | }, 176 | "cycle": { 177 | "version": "1.0.3", 178 | "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", 179 | "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" 180 | }, 181 | "dashdash": { 182 | "version": "1.14.1", 183 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 184 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 185 | "requires": { 186 | "assert-plus": "1.0.0" 187 | } 188 | }, 189 | "debug": { 190 | "version": "3.1.0", 191 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 192 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 193 | "requires": { 194 | "ms": "2.0.0" 195 | } 196 | }, 197 | "decamelize": { 198 | "version": "1.2.0", 199 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 200 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 201 | }, 202 | "define-properties": { 203 | "version": "1.1.2", 204 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", 205 | "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", 206 | "requires": { 207 | "foreach": "2.0.5", 208 | "object-keys": "1.0.11" 209 | } 210 | }, 211 | "delayed-stream": { 212 | "version": "1.0.0", 213 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 214 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 215 | }, 216 | "depd": { 217 | "version": "1.1.1", 218 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 219 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 220 | }, 221 | "dom-serializer": { 222 | "version": "0.1.0", 223 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", 224 | "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", 225 | "requires": { 226 | "domelementtype": "1.1.3", 227 | "entities": "1.1.1" 228 | }, 229 | "dependencies": { 230 | "domelementtype": { 231 | "version": "1.1.3", 232 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", 233 | "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" 234 | } 235 | } 236 | }, 237 | "domelementtype": { 238 | "version": "1.3.0", 239 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", 240 | "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" 241 | }, 242 | "domhandler": { 243 | "version": "2.4.1", 244 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", 245 | "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", 246 | "requires": { 247 | "domelementtype": "1.3.0" 248 | } 249 | }, 250 | "domutils": { 251 | "version": "1.7.0", 252 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", 253 | "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", 254 | "requires": { 255 | "dom-serializer": "0.1.0", 256 | "domelementtype": "1.3.0" 257 | } 258 | }, 259 | "ecc-jsbn": { 260 | "version": "0.1.1", 261 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 262 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 263 | "optional": true, 264 | "requires": { 265 | "jsbn": "0.1.1" 266 | } 267 | }, 268 | "end-of-stream": { 269 | "version": "1.4.0", 270 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", 271 | "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", 272 | "requires": { 273 | "once": "1.4.0" 274 | } 275 | }, 276 | "entities": { 277 | "version": "1.1.1", 278 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", 279 | "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" 280 | }, 281 | "es-abstract": { 282 | "version": "1.10.0", 283 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.10.0.tgz", 284 | "integrity": "sha512-/uh/DhdqIOSkAWifU+8nG78vlQxdLckUdI/sPgy0VhuXi2qJ7T8czBmqIYtLQVpCIFYafChnsRsB5pyb1JdmCQ==", 285 | "requires": { 286 | "es-to-primitive": "1.1.1", 287 | "function-bind": "1.1.1", 288 | "has": "1.0.1", 289 | "is-callable": "1.1.3", 290 | "is-regex": "1.0.4" 291 | } 292 | }, 293 | "es-to-primitive": { 294 | "version": "1.1.1", 295 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", 296 | "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", 297 | "requires": { 298 | "is-callable": "1.1.3", 299 | "is-date-object": "1.0.1", 300 | "is-symbol": "1.0.1" 301 | } 302 | }, 303 | "eventemitter3": { 304 | "version": "3.0.0", 305 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.0.0.tgz", 306 | "integrity": "sha512-62TxCtz4m2LRaOERVEvLJJ4A6rsg8lC9Xm+FLg2y/1fB/v4ZZ9JCOn+/Ppl5KkH6sRih6bhix724PVanmXYZJQ==" 307 | }, 308 | "extend": { 309 | "version": "3.0.1", 310 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 311 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" 312 | }, 313 | "extsprintf": { 314 | "version": "1.3.0", 315 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 316 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 317 | }, 318 | "eyes": { 319 | "version": "0.1.8", 320 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 321 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" 322 | }, 323 | "fast-deep-equal": { 324 | "version": "1.0.0", 325 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", 326 | "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" 327 | }, 328 | "fast-json-stable-stringify": { 329 | "version": "2.0.0", 330 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 331 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 332 | }, 333 | "file-type": { 334 | "version": "3.9.0", 335 | "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", 336 | "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=" 337 | }, 338 | "follow-redirects": { 339 | "version": "1.4.1", 340 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz", 341 | "integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==", 342 | "requires": { 343 | "debug": "3.1.0" 344 | } 345 | }, 346 | "foreach": { 347 | "version": "2.0.5", 348 | "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", 349 | "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" 350 | }, 351 | "forever-agent": { 352 | "version": "0.6.1", 353 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 354 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 355 | }, 356 | "form-data": { 357 | "version": "2.3.1", 358 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", 359 | "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", 360 | "requires": { 361 | "asynckit": "0.4.0", 362 | "combined-stream": "1.0.5", 363 | "mime-types": "2.1.17" 364 | } 365 | }, 366 | "function-bind": { 367 | "version": "1.1.1", 368 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 369 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 370 | }, 371 | "getpass": { 372 | "version": "0.1.7", 373 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 374 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 375 | "requires": { 376 | "assert-plus": "1.0.0" 377 | } 378 | }, 379 | "har-schema": { 380 | "version": "2.0.0", 381 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 382 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 383 | }, 384 | "har-validator": { 385 | "version": "5.0.3", 386 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", 387 | "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", 388 | "requires": { 389 | "ajv": "5.5.2", 390 | "har-schema": "2.0.0" 391 | } 392 | }, 393 | "has": { 394 | "version": "1.0.1", 395 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", 396 | "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", 397 | "requires": { 398 | "function-bind": "1.1.1" 399 | } 400 | }, 401 | "hawk": { 402 | "version": "6.0.2", 403 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", 404 | "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", 405 | "requires": { 406 | "boom": "4.3.1", 407 | "cryptiles": "3.1.2", 408 | "hoek": "4.2.0", 409 | "sntp": "2.1.0" 410 | } 411 | }, 412 | "he": { 413 | "version": "1.1.1", 414 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 415 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" 416 | }, 417 | "hoek": { 418 | "version": "4.2.0", 419 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", 420 | "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" 421 | }, 422 | "html-to-text": { 423 | "version": "3.3.0", 424 | "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-3.3.0.tgz", 425 | "integrity": "sha1-aptjxpm4hbt7qEsURr/mh2u/z7c=", 426 | "requires": { 427 | "he": "1.1.1", 428 | "htmlparser2": "3.9.2", 429 | "optimist": "0.6.1", 430 | "underscore": "1.8.3", 431 | "underscore.string": "3.3.4" 432 | } 433 | }, 434 | "htmlparser2": { 435 | "version": "3.9.2", 436 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", 437 | "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", 438 | "requires": { 439 | "domelementtype": "1.3.0", 440 | "domhandler": "2.4.1", 441 | "domutils": "1.7.0", 442 | "entities": "1.1.1", 443 | "inherits": "2.0.3", 444 | "readable-stream": "2.3.3" 445 | } 446 | }, 447 | "http-signature": { 448 | "version": "1.2.0", 449 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 450 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 451 | "requires": { 452 | "assert-plus": "1.0.0", 453 | "jsprim": "1.4.1", 454 | "sshpk": "1.13.1" 455 | } 456 | }, 457 | "inherits": { 458 | "version": "2.0.3", 459 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 460 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 461 | }, 462 | "ini": { 463 | "version": "1.3.5", 464 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 465 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" 466 | }, 467 | "invert-kv": { 468 | "version": "1.0.0", 469 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", 470 | "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" 471 | }, 472 | "is-buffer": { 473 | "version": "1.1.6", 474 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 475 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 476 | }, 477 | "is-callable": { 478 | "version": "1.1.3", 479 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", 480 | "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=" 481 | }, 482 | "is-date-object": { 483 | "version": "1.0.1", 484 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 485 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" 486 | }, 487 | "is-fullwidth-code-point": { 488 | "version": "1.0.0", 489 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 490 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 491 | "requires": { 492 | "number-is-nan": "1.0.1" 493 | } 494 | }, 495 | "is-nan": { 496 | "version": "1.2.1", 497 | "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.2.1.tgz", 498 | "integrity": "sha1-n69ltvttskt/XAYoR16nH5iEAeI=", 499 | "requires": { 500 | "define-properties": "1.1.2" 501 | } 502 | }, 503 | "is-regex": { 504 | "version": "1.0.4", 505 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 506 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 507 | "requires": { 508 | "has": "1.0.1" 509 | } 510 | }, 511 | "is-symbol": { 512 | "version": "1.0.1", 513 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", 514 | "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=" 515 | }, 516 | "is-typedarray": { 517 | "version": "1.0.0", 518 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 519 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 520 | }, 521 | "isarray": { 522 | "version": "1.0.0", 523 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 524 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 525 | }, 526 | "isstream": { 527 | "version": "0.1.2", 528 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 529 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 530 | }, 531 | "jsbn": { 532 | "version": "0.1.1", 533 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 534 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 535 | "optional": true 536 | }, 537 | "json-schema": { 538 | "version": "0.2.3", 539 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 540 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 541 | }, 542 | "json-schema-traverse": { 543 | "version": "0.3.1", 544 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", 545 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" 546 | }, 547 | "json-stringify-safe": { 548 | "version": "5.0.1", 549 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 550 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 551 | }, 552 | "jsprim": { 553 | "version": "1.4.1", 554 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 555 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 556 | "requires": { 557 | "assert-plus": "1.0.0", 558 | "extsprintf": "1.3.0", 559 | "json-schema": "0.2.3", 560 | "verror": "1.10.0" 561 | } 562 | }, 563 | "lcid": { 564 | "version": "1.0.0", 565 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", 566 | "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", 567 | "requires": { 568 | "invert-kv": "1.0.0" 569 | } 570 | }, 571 | "lodash": { 572 | "version": "4.17.4", 573 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 574 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 575 | }, 576 | "long-timeout": { 577 | "version": "0.1.1", 578 | "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", 579 | "integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ=" 580 | }, 581 | "mime": { 582 | "version": "1.6.0", 583 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 584 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 585 | }, 586 | "mime-db": { 587 | "version": "1.30.0", 588 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", 589 | "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" 590 | }, 591 | "mime-types": { 592 | "version": "2.1.17", 593 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", 594 | "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", 595 | "requires": { 596 | "mime-db": "1.30.0" 597 | } 598 | }, 599 | "minimist": { 600 | "version": "0.0.10", 601 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", 602 | "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" 603 | }, 604 | "moment": { 605 | "version": "2.20.1", 606 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", 607 | "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" 608 | }, 609 | "moment-timezone": { 610 | "version": "0.5.14", 611 | "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz", 612 | "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=", 613 | "requires": { 614 | "moment": "2.20.1" 615 | } 616 | }, 617 | "ms": { 618 | "version": "2.0.0", 619 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 620 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 621 | }, 622 | "nconf": { 623 | "version": "0.10.0", 624 | "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.10.0.tgz", 625 | "integrity": "sha512-fKiXMQrpP7CYWJQzKkPPx9hPgmq+YLDyxcG9N8RpiE9FoCkCbzD0NyW0YhE3xn3Aupe7nnDeIx4PFzYehpHT9Q==", 626 | "requires": { 627 | "async": "1.5.2", 628 | "ini": "1.3.5", 629 | "secure-keys": "1.0.0", 630 | "yargs": "3.32.0" 631 | } 632 | }, 633 | "node-schedule": { 634 | "version": "1.3.0", 635 | "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.0.tgz", 636 | "integrity": "sha512-NNwO9SUPjBwFmPh3vXiPVEhJLn4uqYmZYvJV358SRGM06BR4UoIqxJpeJwDDXB6atULsgQA97MfD1zMd5xsu+A==", 637 | "requires": { 638 | "cron-parser": "2.4.4", 639 | "long-timeout": "0.1.1", 640 | "sorted-array-functions": "1.1.0" 641 | } 642 | }, 643 | "node-telegram-bot-api": { 644 | "version": "0.30.0", 645 | "resolved": "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.30.0.tgz", 646 | "integrity": "sha512-+EeM+fe3Xt81KIPqN3L6s6eK+FK4QaqyDcwCwkY/jqsleERXwwjGlVbf4lJCOZ0uJuF5PfqTmvVNtua7AZfBXg==", 647 | "requires": { 648 | "array.prototype.findindex": "2.0.2", 649 | "bl": "1.2.1", 650 | "bluebird": "3.5.1", 651 | "debug": "3.1.0", 652 | "depd": "1.1.1", 653 | "eventemitter3": "3.0.0", 654 | "file-type": "3.9.0", 655 | "mime": "1.6.0", 656 | "pump": "2.0.0", 657 | "request": "2.83.0", 658 | "request-promise": "4.2.2" 659 | } 660 | }, 661 | "number-is-nan": { 662 | "version": "1.0.1", 663 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 664 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 665 | }, 666 | "oauth-sign": { 667 | "version": "0.8.2", 668 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 669 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 670 | }, 671 | "object-keys": { 672 | "version": "1.0.11", 673 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", 674 | "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=" 675 | }, 676 | "once": { 677 | "version": "1.4.0", 678 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 679 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 680 | "requires": { 681 | "wrappy": "1.0.2" 682 | } 683 | }, 684 | "optimist": { 685 | "version": "0.6.1", 686 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 687 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 688 | "requires": { 689 | "minimist": "0.0.10", 690 | "wordwrap": "0.0.3" 691 | } 692 | }, 693 | "os-locale": { 694 | "version": "1.4.0", 695 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", 696 | "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", 697 | "requires": { 698 | "lcid": "1.0.0" 699 | } 700 | }, 701 | "performance-now": { 702 | "version": "2.1.0", 703 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 704 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 705 | }, 706 | "process-nextick-args": { 707 | "version": "1.0.7", 708 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 709 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" 710 | }, 711 | "pump": { 712 | "version": "2.0.0", 713 | "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.0.tgz", 714 | "integrity": "sha512-6MYypjOvtiXhBSTOD0Zs5eNjCGfnqi5mPsCsW+dgKTxrZzQMZQNpBo3XRkLx7id753f3EeyHLBqzqqUymIolgw==", 715 | "requires": { 716 | "end-of-stream": "1.4.0", 717 | "once": "1.4.0" 718 | } 719 | }, 720 | "punycode": { 721 | "version": "1.4.1", 722 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 723 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 724 | }, 725 | "qs": { 726 | "version": "6.5.1", 727 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 728 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 729 | }, 730 | "readable-stream": { 731 | "version": "2.3.3", 732 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", 733 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", 734 | "requires": { 735 | "core-util-is": "1.0.2", 736 | "inherits": "2.0.3", 737 | "isarray": "1.0.0", 738 | "process-nextick-args": "1.0.7", 739 | "safe-buffer": "5.1.1", 740 | "string_decoder": "1.0.3", 741 | "util-deprecate": "1.0.2" 742 | } 743 | }, 744 | "request": { 745 | "version": "2.83.0", 746 | "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", 747 | "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", 748 | "requires": { 749 | "aws-sign2": "0.7.0", 750 | "aws4": "1.6.0", 751 | "caseless": "0.12.0", 752 | "combined-stream": "1.0.5", 753 | "extend": "3.0.1", 754 | "forever-agent": "0.6.1", 755 | "form-data": "2.3.1", 756 | "har-validator": "5.0.3", 757 | "hawk": "6.0.2", 758 | "http-signature": "1.2.0", 759 | "is-typedarray": "1.0.0", 760 | "isstream": "0.1.2", 761 | "json-stringify-safe": "5.0.1", 762 | "mime-types": "2.1.17", 763 | "oauth-sign": "0.8.2", 764 | "performance-now": "2.1.0", 765 | "qs": "6.5.1", 766 | "safe-buffer": "5.1.1", 767 | "stringstream": "0.0.5", 768 | "tough-cookie": "2.3.3", 769 | "tunnel-agent": "0.6.0", 770 | "uuid": "3.1.0" 771 | } 772 | }, 773 | "request-promise": { 774 | "version": "4.2.2", 775 | "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz", 776 | "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", 777 | "requires": { 778 | "bluebird": "3.5.1", 779 | "request-promise-core": "1.1.1", 780 | "stealthy-require": "1.1.1", 781 | "tough-cookie": "2.3.3" 782 | } 783 | }, 784 | "request-promise-core": { 785 | "version": "1.1.1", 786 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", 787 | "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", 788 | "requires": { 789 | "lodash": "4.17.4" 790 | } 791 | }, 792 | "safe-buffer": { 793 | "version": "5.1.1", 794 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 795 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 796 | }, 797 | "secure-keys": { 798 | "version": "1.0.0", 799 | "resolved": "https://registry.npmjs.org/secure-keys/-/secure-keys-1.0.0.tgz", 800 | "integrity": "sha1-8MgtmKOxOah3aogIBQuCRDEIf8o=" 801 | }, 802 | "sntp": { 803 | "version": "2.1.0", 804 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", 805 | "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", 806 | "requires": { 807 | "hoek": "4.2.0" 808 | } 809 | }, 810 | "sorted-array-functions": { 811 | "version": "1.1.0", 812 | "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.1.0.tgz", 813 | "integrity": "sha512-zq6fLdGQixb9VZfT/tLgU+LzoedJyTbcf1I/TKETFeUVoWIfcs5HNr+SJSvQJLXRlEZjB1gpILTrxamxAdCcgA==" 814 | }, 815 | "sprintf-js": { 816 | "version": "1.1.1", 817 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.1.tgz", 818 | "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw=" 819 | }, 820 | "sshpk": { 821 | "version": "1.13.1", 822 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", 823 | "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", 824 | "requires": { 825 | "asn1": "0.2.3", 826 | "assert-plus": "1.0.0", 827 | "bcrypt-pbkdf": "1.0.1", 828 | "dashdash": "1.14.1", 829 | "ecc-jsbn": "0.1.1", 830 | "getpass": "0.1.7", 831 | "jsbn": "0.1.1", 832 | "tweetnacl": "0.14.5" 833 | } 834 | }, 835 | "stack-trace": { 836 | "version": "0.0.10", 837 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 838 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" 839 | }, 840 | "stealthy-require": { 841 | "version": "1.1.1", 842 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 843 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" 844 | }, 845 | "string-width": { 846 | "version": "1.0.2", 847 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 848 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 849 | "requires": { 850 | "code-point-at": "1.1.0", 851 | "is-fullwidth-code-point": "1.0.0", 852 | "strip-ansi": "3.0.1" 853 | } 854 | }, 855 | "string_decoder": { 856 | "version": "1.0.3", 857 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 858 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 859 | "requires": { 860 | "safe-buffer": "5.1.1" 861 | } 862 | }, 863 | "stringstream": { 864 | "version": "0.0.5", 865 | "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", 866 | "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" 867 | }, 868 | "strip-ansi": { 869 | "version": "3.0.1", 870 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 871 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 872 | "requires": { 873 | "ansi-regex": "2.1.1" 874 | } 875 | }, 876 | "tough-cookie": { 877 | "version": "2.3.3", 878 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", 879 | "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", 880 | "requires": { 881 | "punycode": "1.4.1" 882 | } 883 | }, 884 | "tunnel-agent": { 885 | "version": "0.6.0", 886 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 887 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 888 | "requires": { 889 | "safe-buffer": "5.1.1" 890 | } 891 | }, 892 | "tweetnacl": { 893 | "version": "0.14.5", 894 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 895 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 896 | "optional": true 897 | }, 898 | "underscore": { 899 | "version": "1.8.3", 900 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", 901 | "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" 902 | }, 903 | "underscore.string": { 904 | "version": "3.3.4", 905 | "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.4.tgz", 906 | "integrity": "sha1-LCo/n4PmR2L9xF5s6sZRQoZCE9s=", 907 | "requires": { 908 | "sprintf-js": "1.1.1", 909 | "util-deprecate": "1.0.2" 910 | } 911 | }, 912 | "util-deprecate": { 913 | "version": "1.0.2", 914 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 915 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 916 | }, 917 | "uuid": { 918 | "version": "3.1.0", 919 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 920 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 921 | }, 922 | "verror": { 923 | "version": "1.10.0", 924 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 925 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 926 | "requires": { 927 | "assert-plus": "1.0.0", 928 | "core-util-is": "1.0.2", 929 | "extsprintf": "1.3.0" 930 | } 931 | }, 932 | "window-size": { 933 | "version": "0.1.4", 934 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", 935 | "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" 936 | }, 937 | "winston": { 938 | "version": "2.4.0", 939 | "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.0.tgz", 940 | "integrity": "sha1-gIBQuT1SZh7Z+2wms/DIJnCLCu4=", 941 | "requires": { 942 | "async": "1.0.0", 943 | "colors": "1.0.3", 944 | "cycle": "1.0.3", 945 | "eyes": "0.1.8", 946 | "isstream": "0.1.2", 947 | "stack-trace": "0.0.10" 948 | }, 949 | "dependencies": { 950 | "async": { 951 | "version": "1.0.0", 952 | "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", 953 | "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" 954 | } 955 | } 956 | }, 957 | "wordwrap": { 958 | "version": "0.0.3", 959 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 960 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" 961 | }, 962 | "wrap-ansi": { 963 | "version": "2.1.0", 964 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", 965 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", 966 | "requires": { 967 | "string-width": "1.0.2", 968 | "strip-ansi": "3.0.1" 969 | } 970 | }, 971 | "wrappy": { 972 | "version": "1.0.2", 973 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 974 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 975 | }, 976 | "y18n": { 977 | "version": "3.2.1", 978 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", 979 | "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" 980 | }, 981 | "yargs": { 982 | "version": "3.32.0", 983 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", 984 | "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", 985 | "requires": { 986 | "camelcase": "2.1.1", 987 | "cliui": "3.2.0", 988 | "decamelize": "1.2.0", 989 | "os-locale": "1.4.0", 990 | "string-width": "1.0.2", 991 | "window-size": "0.1.4", 992 | "y18n": "3.2.1" 993 | } 994 | } 995 | } 996 | } 997 | -------------------------------------------------------------------------------- /chat_bot/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fmichatbot", 3 | "version": "1.0.0", 4 | "description": "a telegram based chatbot for FMI students", 5 | "main": "bot.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/IvanFilipov/ChatBot_FMI_students.git" 12 | }, 13 | "keywords": [ 14 | "telegram", 15 | "chatbot", 16 | "FMI" 17 | ], 18 | "author": "Ivan Filipov", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/IvanFilipov/ChatBot_FMI_students/issues" 22 | }, 23 | "homepage": "https://github.com/IvanFilipov/ChatBot_FMI_students#readme", 24 | "dependencies": { 25 | "axios": "^0.17.1", 26 | "html-to-text": "^3.3.0", 27 | "nconf": "^0.10.0", 28 | "node-schedule": "^1.3.0", 29 | "node-telegram-bot-api": "^0.30.0", 30 | "winston": "^2.4.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chat_bot_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanFilipov/ChatBot_FMI_students/822238fd11e4d7a82e7c9ddabfdc8dc86fbaa6de/chat_bot_logo.jpg -------------------------------------------------------------------------------- /chat_bot_logo_old.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanFilipov/ChatBot_FMI_students/822238fd11e4d7a82e7c9ddabfdc8dc86fbaa6de/chat_bot_logo_old.jpg -------------------------------------------------------------------------------- /documentation/how_to_use/install_guide/en_install.txt: -------------------------------------------------------------------------------- 1 | some info about how to install the bot on your machine -------------------------------------------------------------------------------- /documentation/how_to_use/install_guide/бг_инсталация.txt: -------------------------------------------------------------------------------- 1 | как да инсталираме бота на нашата машина? 2 | 3 | - първо създайте бота чрез @Botfather в телеграм /newbot 4 | - запазете индетифициращия го номер ( жетон ) 5 | - добавете описание на бота чрез /setdescription 6 | 7 | 8 | - инсталирайте nodejs на вашата машина / версия > 8.7 9 | - инсталирайте npm / версия > 5.5 10 | - създайте директория, в която ще съхраните бота / (например : >mkdir chat_bot ) 11 | - влезте в директорията / ( >cd chat_bot ) 12 | - инициализирайте директорията / ( >npm init ) 13 | - клонирайте кода от репото /scr 14 | 15 | -допълнителни зависимости : 16 | 17 | npm install 18 | 19 | как да го конфигурираме спрямо нашите нужди? 20 | 21 | -нужно е само да премахнете разширението _example от името 22 | на директориията info_base_example и да редактирате config.json 23 | файла спрямо вашето желание 24 | 25 | как да го стартираме, след като сме го инсталирали? 26 | 27 | >node bot.js --bToken bot_token --mToken moodle_token 28 | 29 | забележете наличието на аргумента ! 30 | приложението очаква да получи от командия ред жетони за ботa и комуникацията с моодле 31 | -------------------------------------------------------------------------------- /documentation/how_to_use/user_guide/en_guide.txt: -------------------------------------------------------------------------------- 1 | User guide how to use the chatbot 2 | Ivan Filipov 3 | Moodle configuration : 4 | 5 | Enter your moodle profile, 6 | 7 | Home -> Dashboard -> Preferences -> User account -> Edit profile 8 | 9 | scroll down until you see "Other fields", click on it, find "Telegram ID" input form. 10 | 11 | Enter the key that you have receive from the chatbot. Save the changes. 12 | 13 | That's it, you are ready. 14 | 15 | 16 | 17 | Communication : 18 | 19 | Basically there are two ways of communication with the chatbot : 20 | 21 | 1) Commands - for using a command just type it starting with a backslash 22 | 23 | *update - use only a backslash and the bot will provide you with all available commands. 24 | 25 | Here is the full list of supported commands : 26 | 27 | /help - an instant view / link with information how to use the bot 28 | 29 | /lang_bg - switch to Bulgarian 30 | 31 | /lang_en - switch to English 32 | 33 | /key - personal key for moodle 34 | 35 | /start - restart the conversation 36 | 37 | 38 | 39 | 2) Buttons - just press a button from the menu to get the information you want in the fastest possible way. 40 | 41 | The buttons are : 42 | 43 | - News - gives you a list with all heading from moodle's forum. Click on a heading to receive the whole new information. 44 | 45 | - Assignments - a list with upcoming assignments, where and when they will be. 46 | 47 | - Personal information - you will be asked about your faculty number and if you have configured your moodle account properly, you will receive all your available grades. 48 | 49 | - General information - gives you four links to resources related with the course. 50 | 51 | - Test my knowledge - A/B/C or D test questions 52 | 53 | Try sending a sticker to the bot :) 54 | 55 | E-mail : fmichatbot@gmail.com -------------------------------------------------------------------------------- /documentation/how_to_use/user_guide/бг_наръчник.txt: -------------------------------------------------------------------------------- 1 | Наръчник за използване на бота 2 | Иван Филипов 3 | Видео демонстрация : 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Текстово описание : 12 | 13 | За да комуникирате с бота Вие можете да 14 | 15 | пращате два вида съобщения. 16 | 17 | 18 | 19 | 1) Команди - за да използвате команда, просто напишете текста й с наклонена черта в началото. 20 | 21 | *update - вече можете да напишете само наклонена черта и ботът ще ви подскаже кои са поддържаните команди. 22 | 23 | Команда е някое от следните : 24 | 25 | /help - дава ви линк с описание как да ползвате бота 26 | 27 | /lang_bg - сменя езика на комуникация с български 28 | 29 | /lang_en - сменя езика на комуникация с английски 30 | 31 | /key - показва персоналния ключ за moodle 32 | 33 | /start - рестартирате разговора си с бота 34 | 35 | 36 | 37 | 2) Бутони - за да извършвате желано действие възможно най-бързо просто натиснете бутона с исканата от вас опция. 38 | 39 | Бутоните от главното меню са следните : 40 | 41 | - Новини - изкарва списък от всички заглавия на теми във форума. При кликване върху заглавие ще получите съдържанието на конкретната новина. 42 | 43 | - Задания - показва списък от предстоящите задани, къде и кога ще бъдат проведени. 44 | 45 | - Лична информация - ботът ще попита за факултения Ви номер, ако сте конфигуирали правилно моодле профила си, то ще получите оценките си от всички проверени задания. 46 | 47 | - Обща информация - дава четири препрадки към външни ресурси свързани с курса. 48 | 49 | - Тествай познанията ми - ботът отговаря, задавайки Ви тестови въпрос. Имате 4 възможни отговора, както и опциите "виж отговора" и "задаване на друг въпрос" 50 | 51 | 52 | 53 | Пробвайте да изпратите стикер на бота :) 54 | 55 | E-mail за контакт : fmichatbot@gmail.com 56 | 57 | -------------------------------------------------------------------------------- /documentation/how_to_use/user_guide/подготовка.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanFilipov/ChatBot_FMI_students/822238fd11e4d7a82e7c9ddabfdc8dc86fbaa6de/documentation/how_to_use/user_guide/подготовка.txt -------------------------------------------------------------------------------- /documentation/idea/en_idea.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanFilipov/ChatBot_FMI_students/822238fd11e4d7a82e7c9ddabfdc8dc86fbaa6de/documentation/idea/en_idea.txt -------------------------------------------------------------------------------- /documentation/idea/бг_идея.txt: -------------------------------------------------------------------------------- 1 | Проблемът : 2 | 3 | В днешно време всеки иска да е информиран по възможност 4 | на момента, с един клик. 5 | 6 | Същото в частност е напълно валидно за студентите 7 | от специалност Информатика към ФМИ, участващи в курсовете 8 | "Увод в програмитането", "Обектно ориентирано програмиране" и 9 | "Структори от данни и алгоритми". 10 | 11 | Търсейки възможно най - бързия начин да получат проста, 12 | но същевременно важна информация, тези студенти се сещат 13 | за варианта най - близък до ежедневието им - чата. Отварят 14 | съответния чат клиент и пишат на асистентите си. 15 | И тук идва проблемът ... тези асистенти също са хора, 16 | имат нужда от почивка и временно откъсване от ангажиментите 17 | свързани със студентите си. Точно затова не винаги 18 | могат да отговарят на момента, докато обаче студентите 19 | са свикнали на "синхронна" комуникация - веднъж писали, те 20 | очакват да получат отговор мигновенно. 21 | 22 | Така и двете страни остават разочаровани и неудовлетворени, 23 | студентите защото не получават нужните отговори достатъчно 24 | бързо, а пък асистентите защото са затрупани от запитвания 25 | и постоянен "чат тормоз". 26 | 27 | Решението : 28 | 29 | Създаването на информативен чатбот, който да обслужва 30 | всички основни и по - често срещани запитвания на студентите. 31 | 32 | Ботът би допринесъл за това всеки да получи каквото иска и очаква. 33 | Студентите - информация в реално време, 34 | а пък асистентите - спокойствие и нужната почивка. 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /documentation/implementation_details/todo.txt: -------------------------------------------------------------------------------- 1 | пространство от състоянията, 2 | база с познанията, 3 | комуникация с външни ресурси -------------------------------------------------------------------------------- /documentation/implementation_details/used_technologies.txt: -------------------------------------------------------------------------------- 1 | nodeJS /javaScript - EcmaScript 2 | 3 | libs : https://github.com/yagop/node-telegram-bot-api / for talking with telegram api 4 | https://github.com/axios/axios / for asynchronous http request 5 | https://github.com/indexzero/nconf / for configurations 6 | https://github.com/winstonjs/winston / for logging 7 | --------------------------------------------------------------------------------