Advertisement
aircampro

more advanced chatbot

Jun 9th, 2021
361
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 16.95 KB | None | 0 0
  1. //
  2. // Skillbox Lecture ChatBot on CL
  3. // To compile under linux needed to upgrade cmake to version 20 and manually install and make the uwebsockets
  4. //
  5. // you need cmake 19
  6. // https://roboticslab-uc3m.github.io/installation-guides/install-cmake.html
  7. //
  8. // install the uWebsockets and nlohmann with vcpkg or sudo apt install as instructed on web
  9. //
  10. // i found
  11. // this tree should be under /usr/include/uWebSockets you copy it manually if its not
  12. // https://github.com/uNetworking/uWebSockets/tree/master/src
  13. //
  14. // copy this to uSockets folder manually from github via clone or zip file
  15. // https://github.com/uNetworking/uSockets/tree/5440dbac79bd76444175b76ee95dfcade12a6aac
  16. //
  17. //
  18. // had to comment out the assert on line 184 App.h
  19. // and in line 870 nlohmann json.hpp
  20. //
  21. // https://question-it.com/questions/2194060/uwebsockets-http-obsluzhivaet-slishkom-medlenno
  22. // make as :-
  23. // make -C uWebSockets/uSockets (clone this folder from github repository)
  24. // compile as :-
  25. // g++-7 -flto -O3 -Wconversion -std=c++17 -IuWebSockets/src -IuWebSockets/uSockets/src chatbot_3.cc -o main uWebSockets/uSockets/*.o -lz -lssl -lcrypto -luv
  26. //
  27.  
  28. #include <iostream>
  29. #include <string>
  30. #include <fstream>
  31. #include <regex>
  32. #include <nlohmann/json.hpp>
  33. #include <uWebSockets/App.h>
  34. #include <algorithm>
  35. #include <map>
  36. #include <optional>
  37.  
  38. #define BOT_REPLY_JSON /* uncomment for bot to reply with straight text */
  39. #define USE_PUBLISH    /* use publish if uncommented use send instead */
  40. #define REMOVE_CHARS_FROM_NAME /* remove chosen charactures from the username */
  41.  
  42. using json = nlohmann::json;
  43. using namespace std;
  44.  
  45. const string COMMAND = "command";
  46. const string USER_ID = "user_id";
  47. const string MESSAGE = "message";
  48. const string USER_FROM = "user_from";
  49. const string ONLINE = "online";
  50.  
  51. const string BROADCAST = "broadcast";
  52.  
  53. const string PRIVATE_MSG = "private_msg";
  54. const string SET_NAME = "set_name";
  55. const string NAME = "name";
  56. const string STATUS = "status";
  57.  
  58. const string SERVER = "my_server";
  59. const string BOT = "roboBot";
  60.  
  61. struct PerSocketData {
  62.     int user_id;
  63.     string name;
  64. };
  65.  
  66. map<int, PerSocketData*> activeUsers;
  67. map<int, PerSocketData*> reusableUsers;
  68.  
  69. int g_numberOfuser {};                    /* define a global which denotes total users */
  70.  
  71. // parses string to lower case
  72. string toLower(string text) {
  73.     transform(text.begin(), text.end(), text.begin(), ::tolower);
  74.     return text;
  75. }
  76.  
  77. map<string, string> database {};
  78.  
  79. // parses string input to an int or null
  80. std::optional<int> ParseStringToInt(string& arg)
  81. {
  82.     try
  83.     {
  84.         return { std::stoi(arg) };
  85.     }
  86.     catch (...)
  87.     {
  88.         std::cout << "cannot convert \'" << arg << "\' to int!\n";
  89.     }
  90.  
  91.     return { };
  92. }
  93.  
  94. // changes char in string to the other
  95. char* changeSymbStr(char* str, char symbToBeChangd, char changeToSymb)
  96. {
  97.     char* start{ str };
  98.     while (*str++)
  99.     {
  100.         if (*str == symbToBeChangd) { *str = changeToSymb; }
  101.     }
  102.     return start;
  103. }
  104.  
  105. // can be useful to find number of letters digits or symbols we have
  106. void statOfStr(char* str, int* letters, int* digits, int* symbols, int* spaces, int* controlChars)
  107. {
  108.     while (*str)
  109.     {
  110.         if ((*str >= 65 and *str <= 90) or (*str >= 97 and *str <= 122)) { (*letters)++; }
  111.         else if (*str >= 48 and *str <= 57) { (*digits)++; }
  112.         else if (*str == 32 or *str == 9) { (*spaces)++; }
  113.         else if (*str <= 31) { (*controlChars)++; }
  114.         else { (*symbols)++; }
  115.         str++;
  116.     }
  117. }
  118.  
  119. // loads phrases from database.txt file
  120. int loadPhrases() {
  121.     ifstream phrases("database.txt"); // ifstream = File Stream, // phrases = переменная
  122.     string line;
  123.     int len = 0;
  124.     while (getline(phrases, line)) { // line = "question $ answer"
  125.         string delimiter = " $ ";
  126.         long unsigned int pos = line.find(delimiter); // сколько символов с начаала занимает вопрос
  127.         string question = toLower(line.substr(0, pos));
  128.         string answer = toLower(line.substr(pos + delimiter.length()));
  129.         database.insert(pair<string, string>(question, answer));
  130.         len++;
  131.     }
  132.     return len;
  133. }
  134.  
  135. // saves a record of phrases in the conversations
  136. void savePhrases(string userInput, string delimeter, string botOutput) {
  137.     // Сохранять фразы в файл
  138.     // Что бы бота можно было научить
  139.     // "Научись: Кто президент США? - Джо Байден" // => добавить в базу вопрос и ответ
  140.     ofstream file;
  141.     file.open("QnA_dictionary.txt", ios_base::app);
  142.     file << ("\n" + userInput + delimeter + botOutput);
  143.     cout << "writing to file " << userInput << delimeter << botOutput << endl;
  144.     // file.close;
  145. }
  146.  
  147. typedef uWS::WebSocket<false, true, PerSocketData> UWEBSOCK;
  148.  
  149. // processes the json communication from the users
  150. #if defined(USE_PUBLISH)
  151. void processMessage(UWEBSOCK* ws, std::string_view message, map<int, PerSocketData*> &users) {
  152. #else
  153. void processMessage(UWEBSOCK* ws, std::string_view message, map<int, PerSocketData*> &users, uWS::OpCode opCode) {
  154. #endif
  155.     PerSocketData* data = ws->getUserData();
  156.     auto parsed = json::parse(message);
  157.     string command = parsed[COMMAND];
  158.     json response; //   { "command": "private_msg", "user_from" : 14, "message" : "Привет, двенатсатй!" }
  159.  
  160.     if (command == PRIVATE_MSG) { // {"command": "private_msg", "user_id" : 12, "message" : "Привет, двенатсатй!"}
  161.         int found = 0;
  162.         int user_id = 0;
  163.         response[COMMAND] = PRIVATE_MSG;
  164.         string errorId = parsed[USER_ID];
  165.         auto valid_user_id = ParseStringToInt(errorId);
  166.         if (!valid_user_id) {
  167.             string noNumberUser = "The user_id must be numeric ! you sent me " + errorId;
  168. #if defined(BOT_REPLY_JSON)
  169.             response[MESSAGE] = noNumberUser;
  170. #if defined(USE_PUBLISH)
  171.             response[USER_FROM] = 1;
  172.             ws->publish("userN" + to_string(data->user_id), response.dump());
  173. #else
  174.             ws->send(response. dump(), opCode, true);
  175. #endif // end pub
  176. #else
  177. #if defined(USE_PUBLISH)
  178.             response[USER_FROM] = 1;
  179.             ws->publish("userN" + to_string(data->user_id), noNumberUser);
  180. #else
  181.             ws->send(noNumberUser, opCode, true);
  182. #endif // end pub
  183. #endif // end json
  184.             return;
  185.         } else {
  186.             user_id = *valid_user_id;
  187.         }
  188.         string user_msg = parsed[MESSAGE];
  189.         response[USER_FROM] = data->user_id;
  190.         response[MESSAGE] = user_msg;
  191.         if (user_id != 1) {                           /* if we are not sending to the bot look for the user */
  192.             for (auto id : users ) {
  193.                 if (id.first == user_id) {
  194.                     found = 1;
  195.                     break;
  196.                 }
  197.             }
  198.         }
  199.         if (user_id == 1) {
  200.              string lower_user_msg = toLower(user_msg);
  201.              for (auto entry : database) {  // the user is the bot and we answer with the replies that are listed in the input file
  202.                 regex pattern(".*" + entry.first + ".*");
  203.                 if (regex_match(lower_user_msg, pattern)) {
  204. #if defined(BOT_REPLY_JSON)
  205.                      response[MESSAGE] = entry.second;
  206. #if defined(USE_PUBLISH)
  207.                      response[USER_FROM] = 1;
  208.                      ws->publish("userN" + to_string(data->user_id), response.dump());
  209. #else
  210.                      ws->send(response. dump(), opCode, true);
  211. #endif  // pub end
  212. #else
  213. #if defined(USE_PUBLISH)                
  214.                      ws->publish("userN" + to_string(data->user_id), entry.second);
  215. #else
  216.                      ws->send(entry.second, opCode, true);
  217. #endif // pub end
  218. #endif // end of bot reply outer loop
  219.                     savePhrases(user_msg, " $ ", entry.second);
  220.                 }
  221.             }
  222.         }
  223.         else if (found == 0)   /* check if the user exhists must be less in id than the total number of users */
  224.         {
  225.             string error_message = "Error, there is no user with ID = " + to_string(user_id);
  226. #if defined(BOT_REPLY_JSON)
  227.             response[MESSAGE] = error_message;
  228. #if defined(USE_PUBLISH)
  229.             response[USER_FROM] = 1;
  230.             ws->publish("userN" + to_string(data->user_id), response.dump());                /* send error back to user */
  231. #else
  232.             ws->send(response.dump(), opCode, true);
  233. #endif // pub end
  234. #else
  235. #if defined(USE_PUBLISH)
  236.             response[USER_FROM] = 1;
  237.             ws->publish("userN" + to_string(data->user_id), string error_message);                /* send error back to user */
  238. #else
  239.             ws->send(string error_message, opCode, true);
  240. #endif // pub end
  241. #endif // json end
  242.         }
  243.         else {
  244.             ws->publish("userN" + to_string(user_id), response.dump());                      /* send message to user */
  245.         }
  246.     }
  247.     else if (command == SET_NAME) {
  248.         data->name = parsed[NAME];
  249. #if defined(REMOVE_CHARS_FROM_NAME)
  250.         string user_name;
  251.         string read_name = data->name;
  252.         char* removeSlash;                                                            /* if name has slash- change to a underscore_ */
  253.         removeSlash = &read_name[0];
  254.         char* noSlash = changeSymbStr(removeSlash, '-', '_');
  255.         removeSlash = changeSymbStr(noSlash, ' ', '_');                              /* remove spaces in name */
  256.         user_name.append(removeSlash);
  257. #else
  258.         string user_name = data->name;
  259. #endif
  260.         response[COMMAND] = SET_NAME;
  261.         response[USER_FROM] = data->user_id;
  262.         regex pattern(".*::.*");                                                       /* exclude a user which has :: */
  263.         if ((!regex_match(user_name, pattern)) && (user_name.length()<=255))           /* exclude :: name or longer than 255 */
  264.         {
  265.            string user_msg = "set new user name to" + user_name;
  266.            response[MESSAGE] = user_msg;
  267.            activeUsers.erase(data->user_id);                                           /* if we got a new name delete the user and add it */
  268.            //activeUsers.insert(make_pair(data->user_id, &data));
  269.            activeUsers[data->user_id] = data;
  270.         }
  271.         else {
  272.            string user_msg = "name is either > 255 chars or contains :: supplied as " + user_name;
  273.            response[MESSAGE] = user_msg;
  274.         }
  275. #if defined(USE_PUBLISH)
  276.         ws->publish("userN" + to_string(data->user_id), response.dump());  
  277. #else
  278.         ws->send(response. dump(), opCode, true);
  279. #endif      
  280.     }
  281. }
  282.  
  283. string status (PerSocketData *data, bool b) {
  284.   json request;
  285.   request[COMMAND] = STATUS;
  286.   request[NAME] = data->name;
  287.   request[USER_ID] = data->user_id;
  288.   request[ONLINE] = b;
  289.   return request.dump();
  290. }
  291.  
  292. int main() {
  293.  
  294.     int latest_id = 1;                                                                        /* ws->getUserData returns one of these */
  295.     int maxUserExceeded = 0;
  296.  
  297.     g_numberOfuser = 1;                                                                       /* chatbot is assigned user 1 */
  298.  
  299.     PerSocketData server = {0, SERVER};                                                       /* add server and bot to user table */
  300.     PerSocketData bot = {1, BOT};
  301.     activeUsers.insert(make_pair(0, &server));
  302.     activeUsers.insert(make_pair(1, &bot));
  303.  
  304.     int phraseCount = loadPhrases();                                                          /* load the bots data file */
  305.  
  306.     uWS::App().ws<PerSocketData>("/*", {
  307.             .idleTimeout = 9999,
  308.             /* Handlers */
  309.             .upgrade = nullptr,
  310.             /* .upgrade = [](auto *res, auto *req, auto *context){}, */
  311. #if defined(USE_PUBLISH)
  312.             .open = [&latest_id,&maxUserExceeded](auto* ws) {
  313. #else    
  314.             .open = [&latest_id,&maxUserExceeded](auto* ws, uWS::OpCode opCode) {
  315. #endif                
  316.                 PerSocketData *data = ws->getUserData();
  317.                 string user_name;
  318.                 user_name = data->name;
  319.  
  320.                 regex pattern(".*::.*");                                                       /* exclude a user which has :: */
  321.                 if ((!regex_match(user_name, pattern)) && (user_name.length()<=255))           /* exclude :: name or longer than 255 */
  322.                 {
  323.                     cout << "User N " << data->user_id << " connected\n";
  324.                                           /* increment the number of users */
  325.                     if ((latest_id == INT16_MAX) || (maxUserExceeded != 0))
  326.                     {
  327.                        maxUserExceeded = 1;
  328.                        for (auto entry : reusableUsers) {
  329.                            data->user_id = entry.first;  
  330.                            reusableUsers.erase(data->user_id);
  331.                            maxUserExceeded = 2;  
  332.                            break;              
  333.                        }
  334.                        if (maxUserExceeded == 1) {
  335.                          string errorMessage = "No available users max exceeded";
  336. #if defined(USE_PUBLISH)
  337.                          ws->publish("userN" + to_string(data->user_id), errorMessage);              // no user found
  338. #else
  339.                          ws->send(errorMessage, opCode, true);
  340. #endif
  341.                        }
  342.                     }
  343.                     else
  344.                     {
  345.                        data->user_id = ++latest_id;
  346.                     }
  347.                     if (maxUserExceeded != 1) {      
  348.                         ws->publish(BROADCAST, status(data, true));                                 // user number has been assigned
  349.                         ws->subscribe("broadcast");                                                 // broadcast = сообщения получают все пользователи
  350.                         ws->subscribe("userN" + to_string(data->user_id));                          // личный канал
  351.                         //activeUsers.insert(make_pair(data->user_id, &data));
  352.                         activeUsers[data->user_id] = data;
  353.                         for (auto entry : activeUsers) {
  354.                             ws->send(status(entry.second, true));
  355.                         }
  356.                         g_numberOfuser = activeUsers.size();
  357.                         string messageOfUser;
  358.                         messageOfUser = "number of users currently online " + to_string(g_numberOfuser);
  359. #if defined(USE_PUBLISH)
  360.                         ws->publish("userN" + to_string(data->user_id), messageOfUser);
  361. #else
  362.                         ws->send(messageOfUser, opCode, true);
  363. #endif
  364.                     }
  365.                 }
  366.                 else
  367.                 {
  368.                     string errorMessage = "Your user ID is invalid couldnt register you";
  369. #if defined(USE_PUBLISH)
  370.                     ws->publish("userN" + to_string(data->user_id), errorMessage);                   // too long or user name contians bad string  
  371. #else
  372.                     ws->send(errorMessage, opCode, true);
  373. #endif                  
  374.                 }
  375.  
  376.             },
  377. /*          .message = [&latest_id](auto* ws, std::string_view message, uWS::OpCode opCode) {   thats if i wanted to pass this into here we use global */
  378. #if defined(USE_PUBLISH)
  379.             .message = [](auto* ws, std::string_view message) {
  380. #else
  381.             .message = [](auto* ws, std::string_view message, uWS::OpCode opCode) {
  382. #endif
  383.                 PerSocketData* data = ws->getUserData();
  384.                 cout << "Message from N " << data->user_id << ": " << message << endl;
  385. #if defined(USE_PUBLISH)
  386.                 processMessage(ws, message, activeUsers);
  387. #else
  388.                 processMessage(ws, message, activeUsers, opCode);
  389. #endif                
  390.             },
  391.             .close = [](auto *ws, int /*code*/, std::string_view /*message*/) {
  392.                 PerSocketData* data = ws->getUserData();
  393.                 cout <<  "User № "  << data->user_id <<  " exited the chat."  << endl;          /* user has exited we dont clear the user just count up to max */
  394.                 activeUsers.erase(data->user_id);
  395.                 //reusableUsers.insert(make_pair(data->user_id, &data));
  396.                 reusableUsers[data->user_id] = data;
  397.                 g_numberOfuser = activeUsers.size();
  398.                 ws->publish(BROADCAST, status(data, false));
  399.                 cout << "close\n";
  400.             }
  401.             }). listen(9001, [](auto *listen_socket) {
  402.                 if (listen_socket) {
  403.                    cout <<  "Listening on port "  << 9001 <<  ": "  << endl;
  404.                 }
  405.             }). run();
  406.             //
  407.             //.get("/*", [](auto *res, auto *req) {
  408.             //    res->end("Hello world!");
  409.             //})
  410.             //.listen(3000, [](auto *token) {
  411.             //    if (token) {
  412.             //        std::cout << "Listening on port " << 3000 << std::endl;
  413.             //    }
  414.             //})
  415.  
  416.     cout << "Failed to listen on port 9001" << endl;
  417.     return 0;
  418. }
  419.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement