From db0b1da0060f5eb4b2af040c22ea9373d36970af Mon Sep 17 00:00:00 2001 From: "Hello=)" Date: Thu, 28 Mar 2024 03:58:45 +0300 Subject: Initial commit of Guild Bot, as seen in: $ sha256sum guildsrc.zip ef61469ab59ce3f5dd3289505e89bd5b84e3a82b89cda90d25ae3c41f7464beb guildsrc.zip Verification shown its source 100% match to currently running version on TMWA. The following changes were made VS version found in guildsrc.zip: 1) src/build and src/dist content completely removed. Rationale: build-time artifacts; binaries contained login/password. 2) Executable flag (+x) removed from src/automation.cpp and src/main.cpp. Rationale: executable CPP sources are weird thing. No change to file content. 3) src/main.cpp changes: 3 lines: std::string main_username = "test"; std::string main_pw = "test"; std::string main_host = "127.0.0.1"; Rationale: avoid leaking real production credentials and ensure experimenters dont hammer real TMW prod server by experiments. Effort been made to preserve original file timestamps, etc. However toplevel src/ dir date adjusted to date of mentioned changes. --- src/automation.cpp | 855 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 855 insertions(+) create mode 100644 src/automation.cpp (limited to 'src/automation.cpp') diff --git a/src/automation.cpp b/src/automation.cpp new file mode 100644 index 0000000..48babd7 --- /dev/null +++ b/src/automation.cpp @@ -0,0 +1,855 @@ +/* + * + * Automation.cpp: The Ruined Place Chat Bot. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "automation.h" +#include "game.h" +#include "main.h" +#include "utils/stringutils.h" +#include "utils/specialfolder.h" +#include "net/nethandler.h" + +static Automation *automationHandler = NULL; + +std::set guildMembers; +std::deque whispers; +std::deque information; +std::set onlineList; +std::set inviteList; +std::map guildMOTD; + +bool automation = true; +bool repeatfix = false; +bool load = true; +bool hideOnLoad = true; // Hide all online/offline notifications on startup - creates a lot of spam! + +int message_timer = 0; + +Automation::Automation() +{ + automationHandler = this; +} + +Automation::~Automation() +{ + delete automationHandler; +} + +Automation *Automation::getAutomationHandler() +{ + return automationHandler; +} + +/* Commands/chat recieved through whisper. */ +void Automation::commandHandler(std::string message, std::string nick) +{ + + std::string::size_type pos = message.find(' '); + std::string type(message, 0, pos); + std::string args(message, pos == std::string::npos ? message.size() : pos + 1); + + /*start of code that deals with invitations - could do with a rewrite*/ + std::set::iterator it3; + bool invited = false; + memberinvite invitedMember; + + for (it3=inviteList.begin(); it3 != inviteList.end(); it3++) + { + if (it3->name == nick) + { + invited = true; + invitedMember = *it3; + } + } + + if (invited) + { + if (removeColors(type) == "yes") + { + inviteList.erase(invitedMember); + addMember(nick, invitedMember.guildName, 0); + addMessage(nick+ " has joined the Guild.", + invitedMember.guildName, "Info", 1); + return; + } + else if (removeColors(type) == "no") + { + inviteList.erase(invitedMember); + return; + } + } + + /* end */ + + if (!automation || !inGuild(nick)) + { + whisper(nick, colorText("You are currently not in a guild." + " For more information or to discuss the possibility" + " of adding you own guild please contact Jero."), 1); + return; + } + + if (type[0] == '!') + { + debugMsg("("+findByName(nick).guildName+") Command: "+ + nick+": "+type +" "+args); + //note incorrect commands will be logged twice. + } + + if (accessLevel(args) != -1) + { + member tmp = findByName(nick); + if (tmp.status == 0) + { + guildMembers.erase(tmp); + tmp.status = 1; + addMessage(tmp.name+ " is now Online.", tmp.guildName, "Info", 1); + guildMembers.insert(tmp); + whisper(tmp.name, colorText("Welcome to the "+tmp.guildName+"! ("+ + toString(countOnline(tmp.guildName)) +" Members are Online)"), 1); + } + } + + if (type == "!invite" && accessLevel(nick) >= 5) /* This command is only available to admin*/ + { + if (!args.empty() && accessLevel(args) != -1) + { + if (inGuild(args)) + { + whisper(nick, colorText("The user is in another guild."), 1); + } + else + { + if (onlineList.find(args) != onlineList.end()) + { + memberinvite tmp= {args, findByName(nick).guildName}; + inviteList.insert(tmp); + whisper(args, colorText("You have been invited to the "+ + findByName(nick).guildName+ " guild chat. If you " + "would like to accept this invitation" + " please reply \"yes\" and if not then \"no\" ." ), 1); + } + else + { + whisper(nick, colorText("The user is offline or hidden."), 1); + } + + } + } + } + else if (type == "!test" && accessLevel(nick) == 20) /* This command is only available to admin*/ + { + addMessage("A test will be running shortly.", + findByName(nick).guildName, "Info", 0); + testMsg(); + } + else if (type == "!disband" && accessLevel(nick) >= 10) /* This command is only available to guild masters*/ + { + std::string guild = findByName(nick).guildName; + debugMsg(guild + " disbanded."); + addMessage("The "+guild +" guild has been disbanded.","global", "Info", 1); + + std::set::iterator it2; + for (it2=guildMembers.begin(); it2 != guildMembers.end(); it2++) + { + if (it2->guildName == guild) + guildMembers.erase(it2); + } + + saveMembers(); + } + else if (type == "!removeguild" && accessLevel(nick) == 20) /* This command is only available to admin*/ + { + + if (!args.empty()) + { + debugMsg(args + "removed."); + std::set::iterator it2; + for (it2=guildMembers.begin(); it2 != guildMembers.end(); it2++) + { + if (it2->guildName == args) + guildMembers.erase(it2); + } + + addMessage("The "+args +" guild has been removed.","global", "Info", 1); + saveMembers(); + } + + } + else if (type == "!addguild" && accessLevel(nick) == 20) + { + if (!args.empty()) + { + std::string::size_type pos2 = args.find(','); + std::string name = args.substr(0, pos2); + std::string guildName = args.substr(pos2+1, args.size()); + + addMember(name, guildName, 10); + addMessage("The "+guildName+" guild has been created.","global", "Info", 1); + } + } + else if (type == "!info") + { + std::stringstream message; + message << "Player name: "+nick+", Access Level: "<< accessLevel(nick) + << ", Guild:" << findByName(nick).guildName + << ", No. Of Online Players: " << + countOnline(findByName(nick).guildName); + + whisper(nick, colorText(message.str()), 1); + + std::string MOTD = getMOTD(findByName(nick).guildName); + if (MOTD != "") + whisper(nick, colorText("Guild MOTD: "+MOTD), 1); + + } + else if (type == "!ping") /* A simple response to check if the bot is active. */ + { + whisper(nick, colorText("Pong."), 0); + } + else if (type == "!remove") + { + if (!args.empty()) + { + if (hasPermission(nick, args, 0) && inGuild(args)) + { + removeMember(args); + whisper(args, "You have been removed from the Guild", 0); + addMessage(args+ " has been removed from the Guild.", + findByName(nick).guildName, "Info", 1); + saveMembers(); + } + } + } + else if (type == "!setmotd" && accessLevel(nick) >= 5) + { + std::map::iterator it; + it=guildMOTD.find(findByName(nick).guildName); + + if (it != guildMOTD.end()) + { + guildMOTD.erase(it); + guildMOTD.insert(std::pair(findByName(nick).guildName, args)); + } + else + { + guildMOTD.insert(std::pair(findByName(nick).guildName, args)); + } + + whisper(nick, colorText("MOTD Set:" + args), 1); + } + else if (type == "!removemotd" && accessLevel(nick) >= 5) + { + std::map::iterator it; + it=guildMOTD.find(findByName(nick).guildName); + + if (it != guildMOTD.end()) + { + guildMOTD.erase(it); + whisper(nick, colorText("The MOTD has been Removed."), 1); + } + else + { + whisper(nick, colorText("No MOTD set."), 1); + } + + } + else if (type == "!setaccess") /* This command is only available to admin*/ + { + if (!args.empty()) + { + std::string::size_type pos = args.find(' '); + std::string level(args, 0, pos); + std::string name(args, pos == std::string::npos ? args.size() : pos + 1); + int access = toInt(level); + + if (hasPermission(nick, name, access) && inGuild(name) + && !name.empty() && !level.empty()) + { + member update = findByName(name); + guildMembers.erase(update); + update.accesslevel = access; + guildMembers.insert(update); + whisper(nick, colorText("Player name:" + name + + ", Access Level: "+level), 1); + } + saveMembers(); + } + } + else if (type == "!leave") + { + std::string guild = findByName(nick).guildName; + whisper(nick, "You have left the Guild", 0); + removeMember(nick); + addMessage(nick+ " has left the Guild.", guild, "Info", 1); + saveMembers(); + } + else if (type == "!help") + { + whisper(nick, colorText("Guild ChatBot. The " + "commands are: !invite , !leave, !listonline, !listmembers, !info. " + "If you would like to hide information messages use !hideinfo, " + "and to make them visible again use !showinfo."), 1); + + if (accessLevel(nick) >= 5) + { + whisper(nick, colorText( + "You are also able to add a guild MOTD using !setmotd, this can be removed using !removemotd."), 1); + } + + if (accessLevel(nick) >= 10) + { + whisper(nick, colorText( + "You also have access to the guild master commands these are" + " !remove , !disband, !setaccess " + "(the access level must be less than or equal to your own)."), 1); + } + else if (accessLevel(nick) <= 10 && accessLevel(nick) > 0) + { + whisper(nick, colorText( + "You also have access to the guild moderator commands these are" + " !remove , !setaccess " + "(the access level must be less than or equal to your own)."), 1); + } + + if (accessLevel(nick) == 20) + { + whisper(nick, colorText( + "You also have access to the admin commands these are !test," + " !addguild , (the comma is important)" + " and !removeguild ."), 1); + whisper(nick, colorText( + " To send a message to all guilds use !global , " + "!listallonline, !joinguild (lets you join another guilds chat)"), 1); + } + } + else if (type == "!hideinfo") + { + whisper(nick, colorText("Information messages hidden."), 1); + member tmp = findByName(nick); + guildMembers.erase(tmp); + tmp.info = 0; + guildMembers.insert(tmp); + } + else if (type == "!online") + { + whisper(nick, colorText("You are online."), 1); + member tmp = findByName(nick); + guildMembers.erase(tmp); + tmp.status = 1; + guildMembers.insert(tmp); + } + else if (type == "!offline") + { + whisper(nick, colorText("You have gone offline."), 1); + member tmp = findByName(nick); + guildMembers.erase(tmp); + tmp.status = 0; + guildMembers.insert(tmp); + } + else if (type == "!joinguild" && accessLevel(nick) == 20) + { + whisper(nick, colorText("You've joined "+args+"."), 1); + member tmp = findByName(nick); + guildMembers.erase(tmp); + tmp.guildName = args; + guildMembers.insert(tmp); + } + else if (type == "!showinfo") + { + whisper(nick, colorText("Information messages vissible."), 1); + member tmp = findByName(nick); + guildMembers.erase(tmp); + tmp.info = 1; + guildMembers.insert(tmp); + } + else if (type == "!global" && accessLevel(nick) == 20) /* This command is only available to admin*/ + { + if (!args.empty()) + { + addMessage(args, "global", "Info", 0); + } + } + else if (type == "!listonline") + { + std::set::iterator it; + std::string message; + std::stringstream list; + list << "List of online Guild Members: "; + + for (it=guildMembers.begin(); it != guildMembers.end(); it++) + { + + if (list.str().length() + it->name.length() > 250 && it->status == 1 + && it->guildName == findByName(nick).guildName) + { + message = list.str(); + message[message.length()-2] = '.'; + whisper(nick,colorText(message), 1); + list.str(""); + list << it->name + ", "; + } + else if (it->status == 1 && it->guildName == findByName(nick).guildName) + { + list << it->name + ", "; + } + + } + message = list.str(); + message[message.length()-2] = '.'; + whisper(nick,colorText(message), 1); + } + else if (type == "!getonlineinfo") + { + std::set::iterator it; + std::string message; + std::stringstream list; + list << "OL"; + + for (it=guildMembers.begin(); it != guildMembers.end(); it++) + { + int status = it->status; + if (it->accesslevel >= 10) + status += 2; + + if (list.str().length() + it->name.length()+2 > 400 + && it->guildName == findByName(nick).guildName) + { + list << "#"; + message = list.str(); + whisper(nick,colorText(message), 1); + list.str(""); + list << "oL#"; + list << it->name; + list << status; + } + else if (it->guildName == findByName(nick).guildName) + { + list << "#"; + list << it->name; + list << status; + } + + } + message = list.str(); + whisper(nick,colorText(message), 1); + } + else if (type == "!listallonline" && accessLevel(nick) == 20) + { + std::set::iterator it; + std::string message; + std::stringstream list; + list << "List of online Players: "; + + for (it=guildMembers.begin(); it != guildMembers.end(); it++) + { + + if (list.str().length() + it->name.length() > 250 && it->status == 1) + { + message = list.str(); + message[message.length()-2] = '.'; + whisper(nick,colorText(message), 1); + list.str(""); + list << it->name + ", "; + } + else if (it->status == 1) + { + list << it->name + ", "; + } + + } + message = list.str(); + message[message.length()-2] = '.'; + whisper(nick,colorText(message), 1); + } + else if (type == "!listmembers") + { + std::set::iterator it; + std::string message; + std::stringstream list; + list << "List of Guild Members: "; + + for (it=guildMembers.begin(); it != guildMembers.end(); it++) + { + + if (list.str().length() + it->name.length() > 250 + && it->guildName == findByName(nick).guildName) + { + message = list.str(); + message[message.length()-2] = '.'; + whisper(nick,colorText(message), 1); + list.str(""); + list << it->name + ", "; + } + else if (it->guildName == findByName(nick).guildName) + { + list << it->name + ", "; + } + + } + message = list.str(); + message[message.length()-2] = '.'; + whisper(nick,colorText(message), 1); + } + else + { + std::size_t found; + found=message.find("*AFK*"); + + if (found == std::string::npos) + addMessage(removeColors(message), findByName(nick).guildName, nick, 0); + } +} + +/* hooks directly into the main game logic. */ +void Automation::logic() +{ + if (message_timer == 0) + message_timer = cur_time; + + if (get_elapsed_time(message_timer) > 10 && hideOnLoad) + { + hideOnLoad = false; + debugMsg("Online/Offline messages unhidden."); + } + + if (!whispers.empty())// with SDL_Delay(10) this should send around 100 messages per second. + { + NetHandler::getNetInstance()->privateMessage(whispers.front().name, + whispers.front().message); + whispers.pop_front(); + } + else if (!information.empty()) + { + NetHandler::getNetInstance()->privateMessage(information.front().name, + information.front().message); + information.pop_front(); + } +} + +void Automation::addMember(std::string name, std::string guildName, int access) +{ + member addme = {name, guildName, 0, access, 1}; + guildMembers.insert(addme); + + if (!load) + { + debugMsg("Member Added: "+ name+", Access Level: "+ + toString(access) + " Guild Name: " + guildName); + saveMembers(); + } +} + +void Automation::removeMember(std::string name) +{ + debugMsg("Member Removed: "+ name); + guildMembers.erase(findByName(name)); + saveMembers(); +} + +/*Checks to see whether a player is on the guildbot.*/ +bool Automation::inGuild(std::string name) +{ + std::set::iterator it; + + for (it=guildMembers.begin() ; it != guildMembers.end(); it++) + if (it->name == name) + return true; + + return false; +} + +/* Updates member online status, this uses 4144's online list.*/ +void Automation::updateOnline(std::set &onlinePlayers) +{ + debugMsg("Update Online List."); + onlineList = onlinePlayers; + std::set::iterator it; + std::set::iterator it2; + std::set updateStatus; //used for applying changes + + //figure out status changes + for (it=guildMembers.begin(); it != guildMembers.end(); it++ ) + { + if (it->accesslevel < 0) + continue; + + member old = *it; + + if (onlinePlayers.find(it->name) != onlinePlayers.end() && it->status == 0) + { + old.status = 1; + updateStatus.insert(old); + } + else if (onlinePlayers.find(it->name) == onlinePlayers.end() && it->status == 1) + { + old.status = 0; + updateStatus.insert(old); + } + } + + //apply status changes + for (it2=updateStatus.begin(); it2 != updateStatus.end(); it2++) + { + member tmp = *it2; + if (it2->status == 1) + { + guildMembers.erase(findByName(it2->name)); + + /*if (hideOnLoad != true)*/ + addMessage(tmp.name+ " is now Online.", tmp.guildName, "Info", 1); + + guildMembers.insert(tmp); + whisper(tmp.name, colorText("Welcome to the "+tmp.guildName+"! ("+ + toString(countOnline(tmp.guildName)) +" Members are Online)"), 1); + + std::string MOTD = getMOTD(tmp.guildName); + if (MOTD != "") + whisper(tmp.name, colorText(MOTD), 1); + + } + else if (it2->status == 0) + { + guildMembers.erase(findByName(it2->name)); + guildMembers.insert(tmp); + addMessage(tmp.name+ " is now Offline.", tmp.guildName, "Info", 1); + } + + } +} + +/* Sends messages to all those in the specified guild. */ +void Automation::addMessage(std::string msg, std::string guildName, + std::string sender, int p) +{ + std::set::iterator it; + for (it=guildMembers.begin(); it != guildMembers.end(); it++) + { + if (sender == "Info" && it->info == 0) + continue; + + if (it->name != sender && it->status == 1 && (it->guildName == guildName + || guildName == "global")) + { + if (sender == "Info") + whisper(it->name,"##0"+msg, p); + else + whisper(it->name, colorText(sender) +": "+"##0"+msg, p); + } + } +} + +void Automation::debugMsg(std::string msg) +{ + std::string message = removeColors("GuildBot: "+msg); + //logger->log(message.c_str()); +} + +/* Adds the whispers to the required queue*/ +void Automation::whisper(std::string name, std::string msg, int priority) +{ + whispermsg newmsg = {name, msg}; + + if (priority == 0) + whispers.push_back(newmsg); + else + information.push_back(newmsg); +} + +void Automation::showOptions() +{ + debugMsg("List of Guild Members:"); + std::set::iterator it; + + for ( it=guildMembers.begin() ; it != guildMembers.end(); it++ ) + { + std::stringstream status; + status << " Status: " << it->status; + status << " Access Level: " << it->accesslevel; + status << " Guild: " << it->guildName; + debugMsg("Character name: "+ it->name + status.str()); + } +} + +/* Saves the member file */ +void Automation::saveMembers() +{ + mLocalDataDir += "."; +#if defined WIN32 + mLocalDataDir = getSpecialFolderLocation(CSIDL_LOCAL_APPDATA); + mLocalDataDir += "/ManaGuild"; + std::string namelist = mLocalDataDir + "/guildmembers.txt"; +#else + std::string namelist = "guildmembers.txt"; +#endif + std::ofstream guildMemberFile(namelist.c_str()); + + if (guildMemberFile.is_open()) + { + debugMsg("Save Guild Member File."); + std::set::iterator it; + + for (it=guildMembers.begin(); it != guildMembers.end(); it++) + guildMemberFile << it->name.c_str() << "," + << it->accesslevel << "," + << it->guildName << std::endl; + + guildMemberFile.close(); + } + else + { + debugMsg("Guild Member File save failed."); + } +} + +/* Loads the member file */ +void Automation::loadMembers() +{ + mLocalDataDir += "."; +#if defined WIN32 + mLocalDataDir = getSpecialFolderLocation(CSIDL_LOCAL_APPDATA); + mLocalDataDir += "/ManaGuild"; + std::string namelist = mLocalDataDir + "/guildmembers.txt"; +#else + std::string namelist = "guildmembers.txt"; +#endif + std::string line; + std::ifstream guildMemberFile(namelist.c_str()); + + if (guildMemberFile.is_open()) + { + debugMsg("Load Guild Member File."); + while (!guildMemberFile.eof() ) + { + getline (guildMemberFile,line); + if (!line.empty()) + { + /* Messy pretend tokenizer*/ + std::string::size_type pos = line.find(','); + std::string::size_type pos2 = line.find(',', pos+1); + std::string guildName = line.substr(pos2+1, line.size()); + std::string name = line.substr(0, pos); + int access = toInt(line.substr(pos+1, pos2)); + addMember(name, guildName, access); + } + } + + guildMemberFile.close(); + } + else + { + debugMsg("Guild Member File open failed. If this is your " + "first time runing the chat bot please ignore this message."); + } + + load = false; +} + +/* Color parts of the text to avoid bans from chat spam, also looks pretty. */ +std::string Automation::colorText(std::string text) +{ + if (repeatfix) + text = "##3"+text; + else + text = "##2"+text; + + repeatfix = !repeatfix; + + return text; +} + +/* Returns the number of members online in a specified guild. */ +int Automation::countOnline(std::string guildName) +{ + int count = 0; + std::set::iterator it; + + for (it=guildMembers.begin(); it != guildMembers.end(); it++) + if (it->status == 1 && (it->guildName == guildName || guildName == "global")) + count++; + + return count; +} + +void Automation::clearMembers() +{ + debugMsg("Clear Members."); + guildMembers.clear(); +} + +int Automation::accessLevel(std::string name) +{ + int accesslevel = findByName(name).accesslevel; + + return accesslevel; +} + +/* Checks to see whether the user has the required permissions, + * for either changing the someones accesslevel or removing another player. */ +bool Automation::hasPermission(std::string user, + std::string player, int changeLevel) +{ + // level 5 players should not be able to remove others. + if (accessLevel(user) != 50 && accessLevel(player) < accessLevel(user) + && changeLevel <= accessLevel(user) && accessLevel(user) > 5 && + findByName(user).guildName == findByName(player).guildName) + { + return true; + } + else + { + whisper(user, colorText("You do not have the correct " + "access level, for this operation."), 1); + return false; + } + + return false; +} + +/* Finds and a member by name. */ +member Automation::findByName(std::string name) +{ + member findme; + std::set::iterator it; + + for (it=guildMembers.begin(); it != guildMembers.end(); it++) + if (it->name == name) + findme = *it; + + return findme; +} + +/* Outputs a test message for testing purposes.*/ +void Automation::testMsg() +{ + for (int i=0;i < 20; i++){ + std::string message = "testing " + toString(i); + addMessage(message,"global", "Info", 0); + } +} + +std::string Automation::getMOTD(std::string guild) +{ + std::map::iterator it; + it=guildMOTD.find(guild); + + if (it != guildMOTD.end()) + return it->second; + else + return ""; +} + + + + -- cgit v1.2.3-70-g09d2