Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //
- // Skillbox Lecture ChatBot on CL
- // To compile under linux needed to upgrade cmake to version 20 and manually install and make the uwebsockets
- //
- // you need cmake 19
- // https://roboticslab-uc3m.github.io/installation-guides/install-cmake.html
- //
- // install the uWebsockets and nlohmann with vcpkg or sudo apt install as instructed on web
- //
- // i found
- // this tree should be under /usr/include/uWebSockets you copy it manually if its not
- // https://github.com/uNetworking/uWebSockets/tree/master/src
- //
- // copy this to uSockets folder manually from github via clone or zip file
- // https://github.com/uNetworking/uSockets/tree/5440dbac79bd76444175b76ee95dfcade12a6aac
- //
- //
- // had to comment out the assert on line 184 App.h
- // and in line 870 nlohmann json.hpp
- //
- // https://question-it.com/questions/2194060/uwebsockets-http-obsluzhivaet-slishkom-medlenno
- // make as :-
- // make -C uWebSockets/uSockets (clone this folder from github repository)
- // compile as :-
- // 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
- //
- #include <iostream>
- #include <string>
- #include <fstream>
- #include <regex>
- #include <nlohmann/json.hpp>
- #include <uWebSockets/App.h>
- #include <algorithm>
- #include <map>
- #include <optional>
- #define BOT_REPLY_JSON /* uncomment for bot to reply with straight text */
- #define USE_PUBLISH /* use publish if uncommented use send instead */
- #define REMOVE_CHARS_FROM_NAME /* remove chosen charactures from the username */
- using json = nlohmann::json;
- using namespace std;
- const string COMMAND = "command";
- const string USER_ID = "user_id";
- const string MESSAGE = "message";
- const string USER_FROM = "user_from";
- const string ONLINE = "online";
- const string BROADCAST = "broadcast";
- const string PRIVATE_MSG = "private_msg";
- const string SET_NAME = "set_name";
- const string NAME = "name";
- const string STATUS = "status";
- const string SERVER = "my_server";
- const string BOT = "roboBot";
- struct PerSocketData {
- int user_id;
- string name;
- };
- map<int, PerSocketData*> activeUsers;
- map<int, PerSocketData*> reusableUsers;
- int g_numberOfuser {}; /* define a global which denotes total users */
- // parses string to lower case
- string toLower(string text) {
- transform(text.begin(), text.end(), text.begin(), ::tolower);
- return text;
- }
- map<string, string> database {};
- // parses string input to an int or null
- std::optional<int> ParseStringToInt(string& arg)
- {
- try
- {
- return { std::stoi(arg) };
- }
- catch (...)
- {
- std::cout << "cannot convert \'" << arg << "\' to int!\n";
- }
- return { };
- }
- // changes char in string to the other
- char* changeSymbStr(char* str, char symbToBeChangd, char changeToSymb)
- {
- char* start{ str };
- while (*str++)
- {
- if (*str == symbToBeChangd) { *str = changeToSymb; }
- }
- return start;
- }
- // can be useful to find number of letters digits or symbols we have
- void statOfStr(char* str, int* letters, int* digits, int* symbols, int* spaces, int* controlChars)
- {
- while (*str)
- {
- if ((*str >= 65 and *str <= 90) or (*str >= 97 and *str <= 122)) { (*letters)++; }
- else if (*str >= 48 and *str <= 57) { (*digits)++; }
- else if (*str == 32 or *str == 9) { (*spaces)++; }
- else if (*str <= 31) { (*controlChars)++; }
- else { (*symbols)++; }
- str++;
- }
- }
- // loads phrases from database.txt file
- int loadPhrases() {
- ifstream phrases("database.txt"); // ifstream = File Stream, // phrases = переменная
- string line;
- int len = 0;
- while (getline(phrases, line)) { // line = "question $ answer"
- string delimiter = " $ ";
- long unsigned int pos = line.find(delimiter); // сколько символов с начаала занимает вопрос
- string question = toLower(line.substr(0, pos));
- string answer = toLower(line.substr(pos + delimiter.length()));
- database.insert(pair<string, string>(question, answer));
- len++;
- }
- return len;
- }
- // saves a record of phrases in the conversations
- void savePhrases(string userInput, string delimeter, string botOutput) {
- // Сохранять фразы в файл
- // Что бы бота можно было научить
- // "Научись: Кто президент США? - Джо Байден" // => добавить в базу вопрос и ответ
- ofstream file;
- file.open("QnA_dictionary.txt", ios_base::app);
- file << ("\n" + userInput + delimeter + botOutput);
- cout << "writing to file " << userInput << delimeter << botOutput << endl;
- // file.close;
- }
- typedef uWS::WebSocket<false, true, PerSocketData> UWEBSOCK;
- // processes the json communication from the users
- #if defined(USE_PUBLISH)
- void processMessage(UWEBSOCK* ws, std::string_view message, map<int, PerSocketData*> &users) {
- #else
- void processMessage(UWEBSOCK* ws, std::string_view message, map<int, PerSocketData*> &users, uWS::OpCode opCode) {
- #endif
- PerSocketData* data = ws->getUserData();
- auto parsed = json::parse(message);
- string command = parsed[COMMAND];
- json response; // { "command": "private_msg", "user_from" : 14, "message" : "Привет, двенатсатй!" }
- if (command == PRIVATE_MSG) { // {"command": "private_msg", "user_id" : 12, "message" : "Привет, двенатсатй!"}
- int found = 0;
- int user_id = 0;
- response[COMMAND] = PRIVATE_MSG;
- string errorId = parsed[USER_ID];
- auto valid_user_id = ParseStringToInt(errorId);
- if (!valid_user_id) {
- string noNumberUser = "The user_id must be numeric ! you sent me " + errorId;
- #if defined(BOT_REPLY_JSON)
- response[MESSAGE] = noNumberUser;
- #if defined(USE_PUBLISH)
- response[USER_FROM] = 1;
- ws->publish("userN" + to_string(data->user_id), response.dump());
- #else
- ws->send(response. dump(), opCode, true);
- #endif // end pub
- #else
- #if defined(USE_PUBLISH)
- response[USER_FROM] = 1;
- ws->publish("userN" + to_string(data->user_id), noNumberUser);
- #else
- ws->send(noNumberUser, opCode, true);
- #endif // end pub
- #endif // end json
- return;
- } else {
- user_id = *valid_user_id;
- }
- string user_msg = parsed[MESSAGE];
- response[USER_FROM] = data->user_id;
- response[MESSAGE] = user_msg;
- if (user_id != 1) { /* if we are not sending to the bot look for the user */
- for (auto id : users ) {
- if (id.first == user_id) {
- found = 1;
- break;
- }
- }
- }
- if (user_id == 1) {
- string lower_user_msg = toLower(user_msg);
- for (auto entry : database) { // the user is the bot and we answer with the replies that are listed in the input file
- regex pattern(".*" + entry.first + ".*");
- if (regex_match(lower_user_msg, pattern)) {
- #if defined(BOT_REPLY_JSON)
- response[MESSAGE] = entry.second;
- #if defined(USE_PUBLISH)
- response[USER_FROM] = 1;
- ws->publish("userN" + to_string(data->user_id), response.dump());
- #else
- ws->send(response. dump(), opCode, true);
- #endif // pub end
- #else
- #if defined(USE_PUBLISH)
- ws->publish("userN" + to_string(data->user_id), entry.second);
- #else
- ws->send(entry.second, opCode, true);
- #endif // pub end
- #endif // end of bot reply outer loop
- savePhrases(user_msg, " $ ", entry.second);
- }
- }
- }
- else if (found == 0) /* check if the user exhists must be less in id than the total number of users */
- {
- string error_message = "Error, there is no user with ID = " + to_string(user_id);
- #if defined(BOT_REPLY_JSON)
- response[MESSAGE] = error_message;
- #if defined(USE_PUBLISH)
- response[USER_FROM] = 1;
- ws->publish("userN" + to_string(data->user_id), response.dump()); /* send error back to user */
- #else
- ws->send(response.dump(), opCode, true);
- #endif // pub end
- #else
- #if defined(USE_PUBLISH)
- response[USER_FROM] = 1;
- ws->publish("userN" + to_string(data->user_id), string error_message); /* send error back to user */
- #else
- ws->send(string error_message, opCode, true);
- #endif // pub end
- #endif // json end
- }
- else {
- ws->publish("userN" + to_string(user_id), response.dump()); /* send message to user */
- }
- }
- else if (command == SET_NAME) {
- data->name = parsed[NAME];
- #if defined(REMOVE_CHARS_FROM_NAME)
- string user_name;
- string read_name = data->name;
- char* removeSlash; /* if name has slash- change to a underscore_ */
- removeSlash = &read_name[0];
- char* noSlash = changeSymbStr(removeSlash, '-', '_');
- removeSlash = changeSymbStr(noSlash, ' ', '_'); /* remove spaces in name */
- user_name.append(removeSlash);
- #else
- string user_name = data->name;
- #endif
- response[COMMAND] = SET_NAME;
- response[USER_FROM] = data->user_id;
- regex pattern(".*::.*"); /* exclude a user which has :: */
- if ((!regex_match(user_name, pattern)) && (user_name.length()<=255)) /* exclude :: name or longer than 255 */
- {
- string user_msg = "set new user name to" + user_name;
- response[MESSAGE] = user_msg;
- activeUsers.erase(data->user_id); /* if we got a new name delete the user and add it */
- //activeUsers.insert(make_pair(data->user_id, &data));
- activeUsers[data->user_id] = data;
- }
- else {
- string user_msg = "name is either > 255 chars or contains :: supplied as " + user_name;
- response[MESSAGE] = user_msg;
- }
- #if defined(USE_PUBLISH)
- ws->publish("userN" + to_string(data->user_id), response.dump());
- #else
- ws->send(response. dump(), opCode, true);
- #endif
- }
- }
- string status (PerSocketData *data, bool b) {
- json request;
- request[COMMAND] = STATUS;
- request[NAME] = data->name;
- request[USER_ID] = data->user_id;
- request[ONLINE] = b;
- return request.dump();
- }
- int main() {
- int latest_id = 1; /* ws->getUserData returns one of these */
- int maxUserExceeded = 0;
- g_numberOfuser = 1; /* chatbot is assigned user 1 */
- PerSocketData server = {0, SERVER}; /* add server and bot to user table */
- PerSocketData bot = {1, BOT};
- activeUsers.insert(make_pair(0, &server));
- activeUsers.insert(make_pair(1, &bot));
- int phraseCount = loadPhrases(); /* load the bots data file */
- uWS::App().ws<PerSocketData>("/*", {
- .idleTimeout = 9999,
- /* Handlers */
- .upgrade = nullptr,
- /* .upgrade = [](auto *res, auto *req, auto *context){}, */
- #if defined(USE_PUBLISH)
- .open = [&latest_id,&maxUserExceeded](auto* ws) {
- #else
- .open = [&latest_id,&maxUserExceeded](auto* ws, uWS::OpCode opCode) {
- #endif
- PerSocketData *data = ws->getUserData();
- string user_name;
- user_name = data->name;
- regex pattern(".*::.*"); /* exclude a user which has :: */
- if ((!regex_match(user_name, pattern)) && (user_name.length()<=255)) /* exclude :: name or longer than 255 */
- {
- cout << "User N " << data->user_id << " connected\n";
- /* increment the number of users */
- if ((latest_id == INT16_MAX) || (maxUserExceeded != 0))
- {
- maxUserExceeded = 1;
- for (auto entry : reusableUsers) {
- data->user_id = entry.first;
- reusableUsers.erase(data->user_id);
- maxUserExceeded = 2;
- break;
- }
- if (maxUserExceeded == 1) {
- string errorMessage = "No available users max exceeded";
- #if defined(USE_PUBLISH)
- ws->publish("userN" + to_string(data->user_id), errorMessage); // no user found
- #else
- ws->send(errorMessage, opCode, true);
- #endif
- }
- }
- else
- {
- data->user_id = ++latest_id;
- }
- if (maxUserExceeded != 1) {
- ws->publish(BROADCAST, status(data, true)); // user number has been assigned
- ws->subscribe("broadcast"); // broadcast = сообщения получают все пользователи
- ws->subscribe("userN" + to_string(data->user_id)); // личный канал
- //activeUsers.insert(make_pair(data->user_id, &data));
- activeUsers[data->user_id] = data;
- for (auto entry : activeUsers) {
- ws->send(status(entry.second, true));
- }
- g_numberOfuser = activeUsers.size();
- string messageOfUser;
- messageOfUser = "number of users currently online " + to_string(g_numberOfuser);
- #if defined(USE_PUBLISH)
- ws->publish("userN" + to_string(data->user_id), messageOfUser);
- #else
- ws->send(messageOfUser, opCode, true);
- #endif
- }
- }
- else
- {
- string errorMessage = "Your user ID is invalid couldnt register you";
- #if defined(USE_PUBLISH)
- ws->publish("userN" + to_string(data->user_id), errorMessage); // too long or user name contians bad string
- #else
- ws->send(errorMessage, opCode, true);
- #endif
- }
- },
- /* .message = [&latest_id](auto* ws, std::string_view message, uWS::OpCode opCode) { thats if i wanted to pass this into here we use global */
- #if defined(USE_PUBLISH)
- .message = [](auto* ws, std::string_view message) {
- #else
- .message = [](auto* ws, std::string_view message, uWS::OpCode opCode) {
- #endif
- PerSocketData* data = ws->getUserData();
- cout << "Message from N " << data->user_id << ": " << message << endl;
- #if defined(USE_PUBLISH)
- processMessage(ws, message, activeUsers);
- #else
- processMessage(ws, message, activeUsers, opCode);
- #endif
- },
- .close = [](auto *ws, int /*code*/, std::string_view /*message*/) {
- PerSocketData* data = ws->getUserData();
- cout << "User № " << data->user_id << " exited the chat." << endl; /* user has exited we dont clear the user just count up to max */
- activeUsers.erase(data->user_id);
- //reusableUsers.insert(make_pair(data->user_id, &data));
- reusableUsers[data->user_id] = data;
- g_numberOfuser = activeUsers.size();
- ws->publish(BROADCAST, status(data, false));
- cout << "close\n";
- }
- }). listen(9001, [](auto *listen_socket) {
- if (listen_socket) {
- cout << "Listening on port " << 9001 << ": " << endl;
- }
- }). run();
- //
- //.get("/*", [](auto *res, auto *req) {
- // res->end("Hello world!");
- //})
- //.listen(3000, [](auto *token) {
- // if (token) {
- // std::cout << "Listening on port " << 3000 << std::endl;
- // }
- //})
- cout << "Failed to listen on port 9001" << endl;
- return 0;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement