Last active
August 29, 2015 14:21
-
-
Save zardoru/feddbcfba82403aea776 to your computer and use it in GitHub Desktop.
rollbot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* This is free and unencumbered software released into the public domain. | |
Anyone is free to copy, modify, publish, use, compile, sell, or | |
distribute this software, either in source code form or as a compiled | |
binary, for any purpose, commercial or non-commercial, and by any | |
means. | |
In jurisdictions that recognize copyright laws, the author or authors | |
of this software dedicate any and all copyright interest in the | |
software to the public domain. We make this dedication for the benefit | |
of the public at large and to the detriment of our heirs and | |
successors. We intend this dedication to be an overt act of | |
relinquishment in perpetuity of all present and future rights to this | |
software under copyright law. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
OTHER DEALINGS IN THE SOFTWARE. | |
For more information, please refer to <http://unlicense.org/> | |
*/ | |
/* | |
rollbot rolls dice. | |
not much else to it. | |
requires boost to compile. | |
*/ | |
#define _SCL_SECURE_NO_WARNINGS | |
#include <iostream> | |
#include <map> | |
#include <random> | |
#include <thread> | |
#include <queue> | |
#include <regex> | |
#include <boost/array.hpp> | |
#include <boost/asio.hpp> | |
#include <boost/function.hpp> | |
#include <boost/bind.hpp> | |
#include <boost/algorithm/string.hpp> | |
using namespace boost::asio; | |
using namespace boost::asio::ip; | |
using boost::function; | |
using boost::bind; | |
#ifndef snprintf | |
#define snprintf _snprintf | |
#pragma warning (disable: 4996) | |
#endif | |
#define BOT_SERVER "irc.badnik.net" | |
#define BOT_PORT "6667" | |
#define BOT_USERNAME "rollbot" | |
#define BOT_AUTOJOIN "#rp" | |
template <int bufSize = 1024> | |
class NetClient | |
{ | |
public: | |
typedef function<void(tcp::socket&, boost::array<char, bufSize>, size_t)> DataCallback; | |
typedef function<void(tcp::socket&)> SendCallback; | |
private: | |
io_service ios; | |
tcp::socket socket; | |
std::ostream &out; | |
DataCallback onRecData; | |
SendCallback onSend; | |
public: | |
NetClient(const char* server, const char* port, std::ostream &o = std::cout) | |
: socket(ios), out(o) | |
{ | |
tcp::resolver resolver(ios); | |
out << "resolving server: " << server << std::endl; | |
tcp::resolver::iterator endpoint = resolver.resolve({ server, port }); | |
out << "query result: " << endpoint->host_name().c_str() << std::endl; | |
out << "connecting... "; | |
boost::asio::connect(socket, endpoint); | |
out << "connected.\n"; | |
} | |
void registerCallbacks(DataCallback callback, SendCallback scallback) | |
{ | |
onRecData = callback; | |
onSend = scallback; | |
} | |
void runLoop() | |
{ | |
boost::array<char, bufSize> buf; | |
boost::system::error_code ec; | |
size_t len; | |
ls: // loop body | |
len = socket.read_some(buffer(buf), ec); | |
if (ec == error::eof) | |
{ | |
out << "connection closed by peer\n"; | |
return; | |
} | |
else if (ec) { | |
out << "error: " << boost::system::system_error(ec).what(); | |
return; | |
} | |
onRecData(socket, buf, len); | |
onSend(socket); | |
goto ls; | |
} | |
tcp::socket& getSocket() | |
{ | |
return socket; | |
} | |
}; | |
static const struct { | |
const char* name; | |
uint16_t id; | |
} IRCcommands[] = { | |
{ "WELCOME", 1 }, | |
{ "INFO", 371 }, | |
{ "MOTD", 372 }, | |
{ "NOTOPIC", 331 }, | |
{ "TOPIC", 332 }, | |
{ "MOTDSTART", 375 }, | |
{ "ENDOFMOTD", 376 } | |
}; | |
class IRCClient | |
{ | |
public: | |
class CommandParam | |
{ | |
private: | |
size_t curridx; | |
std::string fullcmd; | |
void parsePrefix() | |
{ | |
std::string *ref = &prefix.name; | |
prefix.name = ""; | |
prefix.user = ""; | |
prefix.host = ""; | |
while (fullcmd[curridx] != ' ') | |
{ | |
if (fullcmd[curridx] != '!' && fullcmd[curridx] != '@') | |
(*ref) += fullcmd[curridx]; | |
else { | |
if (fullcmd[curridx] == '!') | |
ref = &prefix.user; | |
else if (fullcmd[curridx] == '@') | |
ref = &prefix.host; | |
} | |
curridx++; | |
} | |
} | |
void skipSpace() | |
{ | |
while (fullcmd[curridx] == ' ') | |
curridx++; | |
} | |
void parseCommand() | |
{ | |
while (fullcmd[curridx] != ' ') | |
{ | |
command += fullcmd[curridx]; | |
curridx++; | |
} | |
} | |
bool readParam(bool trailing) | |
{ | |
char curr = fullcmd[curridx]; | |
std::string cparam = ""; | |
while (curr != '\r' && curr != '\n' && curr != 0 && (curr != ' ' || trailing)) | |
{ | |
cparam += curr; | |
curridx++; | |
curr = fullcmd[curridx]; | |
} | |
params.push_back(cparam); | |
if (!cparam.length()) | |
return false; | |
return true; | |
} | |
void parseParams() | |
{ | |
skipSpace(); | |
if (fullcmd[curridx] == ':') // read trailing | |
{ | |
curridx++; | |
readParam(true); | |
} | |
else { // read middle + params | |
if (readParam(false)) | |
parseParams(); | |
} | |
} | |
public: | |
struct | |
{ | |
std::string name; | |
std::string user; | |
std::string host; | |
} prefix; | |
std::string command; | |
std::vector<std::string> params; | |
void parse(const std::string &fullcommand) | |
{ | |
curridx = 0; | |
fullcmd = fullcommand; | |
if (fullcommand[0] == ':') // okay, parse prefix | |
{ | |
curridx = 1; | |
parsePrefix(); | |
skipSpace(); | |
} | |
parseCommand(); | |
// now parse parameters | |
params.clear(); | |
parseParams(); | |
} | |
}; | |
typedef function<void(const CommandParam&)> CommandCallback; | |
private: | |
NetClient<1024> net; | |
std::map<std::string, CommandCallback> cmds; | |
std::string cmdbuffer; | |
bool debug; | |
bool clientEnabled; | |
std::queue<std::string> pendingCommands; | |
void onPing(std::string fullcmd) | |
{ | |
// return a PONG. | |
fullcmd[1] = 'O'; | |
do_send(fullcmd); | |
clientEnabled = true; | |
} | |
// the command this sends does include the \r\n sequence. | |
void ircCallback(tcp::socket &sock, boost::array<char, 1024> data, size_t len) | |
{ | |
for (size_t i = 0; i < len; i++) | |
{ | |
if (data[i] == '\r' && i + 1 < len && data[i + 1] == '\n') | |
{ | |
// execute a command | |
cmdbuffer += "\r\n"; | |
cmdbuffer = cmdbuffer.substr(cmdbuffer.find_first_not_of('\n')); | |
const char* bf = cmdbuffer.c_str(); | |
if (debug) | |
std::cout << bf; | |
if (!strncmp(bf, "PING", 4)) | |
{ | |
onPing(cmdbuffer); | |
} | |
else | |
{ | |
CommandParam cmd; | |
cmd.parse(cmdbuffer); | |
if (cmd.command == "001") // welcome | |
clientEnabled = true; | |
if (cmds.find(cmd.command) != cmds.end()) | |
{ | |
// call callback associated to command | |
cmds[cmd.command](cmd); | |
} | |
} | |
// empty the command buffer | |
cmdbuffer = ""; | |
} | |
else | |
cmdbuffer += data[i]; | |
} | |
} | |
void ircSendCallback(tcp::socket& sock) | |
{ | |
if (clientEnabled && pendingCommands.size()) // there are pending commands | |
{ | |
do_send(pendingCommands.front()); | |
pendingCommands.pop(); | |
} | |
} | |
protected: | |
void queue_send(const std::string &cmd) | |
{ | |
pendingCommands.push(cmd); | |
} | |
void do_send(const std::string &cmd) | |
{ | |
std::string tosend = (cmd + "\r\n"); | |
net.getSocket().send(buffer(tosend.c_str(), tosend.length())); | |
} | |
void enableDebug(bool debug) | |
{ | |
this->debug = debug; | |
} | |
public: | |
IRCClient(const char* name, const char* server, const char* port) : net(server, port) | |
{ | |
debug = false; | |
clientEnabled = false; | |
net.registerCallbacks(bind(&IRCClient::ircCallback, this, _1, _2, _3), | |
bind(&IRCClient::ircSendCallback, this, _1)); | |
tcp::socket &sck = net.getSocket(); | |
char buf[1024] = { 0 }; | |
snprintf(buf, 1024, "USER %s 0 0: %s", name, name); | |
do_send(buf); | |
setNick(name); | |
} | |
void registerCommand(const char* command, CommandCallback callback) | |
{ | |
cmds[command] = callback; | |
} | |
void setNick(const char* nick) | |
{ | |
char buf[1024] = { 0 }; | |
snprintf(buf, 1024, "NICK %s", nick); | |
do_send(buf); | |
} | |
void joinChannel(const char* channels, const char* keys, bool quit) | |
{ | |
char buf[1024] = { 0 }; | |
snprintf(buf, 1024, "JOIN %s %s %s", channels, keys, quit? "0" : " "); | |
queue_send(buf); | |
} | |
// "who" is comma separated | |
void sendMessage(const char* who, const char* message) | |
{ | |
std::string bf; | |
bf += "PRIVMSG "; | |
bf += who; | |
bf += " :"; | |
bf += message; | |
queue_send(bf.c_str()); | |
} | |
void Run() | |
{ | |
net.runLoop(); | |
} | |
}; | |
class BotClient : public IRCClient | |
{ | |
std::mt19937 randeng; | |
public: | |
void motd(const CommandParam &cmd) | |
{ | |
for (auto i : cmd.params) | |
std::cout << i << std::endl; | |
} | |
char fateroll() | |
{ | |
std::uniform_int_distribution<int> ds(1, 3); | |
switch (ds(randeng)) | |
{ | |
case 1: | |
return '+'; | |
case 2: | |
return '~'; | |
case 3: | |
return '-'; | |
} | |
} | |
int stoi(std::string s) | |
{ | |
std::stringstream ss; | |
int ret; | |
ss << s; | |
ss >> ret; | |
return ret; | |
} | |
std::string roll(std::string command) | |
{ | |
std::regex rollex("(\\d+)d(\\d+)"); | |
std::smatch match; | |
std::regex_match(command, match, rollex); | |
if (match.size() < 3) | |
return "Invalid roll."; | |
int dicen = stoi(match[1]); | |
int dicek = stoi(match[2]); | |
if (dicek <= 1) | |
return "Not enough faces."; | |
int sum = 0; | |
std::stringstream roll; | |
roll << "("; | |
std::uniform_int_distribution<int> ds(1, dicek); | |
for (int i = 0; i < dicen; i++) | |
{ | |
int localroll = ds(randeng); | |
sum += localroll; | |
roll << localroll; | |
if (i + 1 != dicen) | |
roll << ","; | |
} | |
roll << ") = " << sum; | |
return roll.str(); | |
} | |
std::string gurpsroll(int difficulty) | |
{ | |
char buf[1024] = { 0 }; | |
// roll the dice | |
std::uniform_int_distribution<int> ds(1, 6); | |
int d1 = ds(randeng), d2 = ds(randeng), d3 = ds(randeng); | |
int result = d1 + d2 + d3; | |
std::stringstream msg; | |
if (result == 3) | |
msg << "Critical success!"; | |
if (result == 18) | |
msg << "Critical failure!"; | |
else { | |
if (difficulty && result >= difficulty) | |
msg << "Success! Margin: " << result - difficulty; | |
else if (difficulty && result < difficulty) | |
msg << "Failure! Margin: " << difficulty - result; | |
} | |
// build and send the message | |
snprintf(buf, 1024, "(%d+%d+%d) = %d. %s", d1, d2, d3, result, msg.str().c_str()); | |
return buf; | |
} | |
void privmsg(const CommandParam &cmd) | |
{ | |
char buf[1024] = { 0 }; | |
std::vector<std::string> botcmd; | |
if (cmd.params[1][0] == '!') | |
{ | |
boost::split(botcmd, cmd.params[1], boost::is_any_of(" ")); | |
} | |
else return; | |
if (botcmd[0] == "!gurps") | |
{ | |
std::string rs; | |
if (botcmd.size() > 1) | |
rs = gurpsroll(stoi(botcmd[1])); | |
else | |
rs = gurpsroll(0); | |
sendMessage(cmd.params[0].c_str(), rs.c_str()); | |
} | |
else if (botcmd[0] == "!d20") | |
{ | |
std::uniform_int_distribution<int> ds(1, 20); | |
int roll = ds(randeng); | |
snprintf(buf, 1024, "d20: %d/20", roll); | |
sendMessage(cmd.params[0].c_str(), buf); | |
} | |
else if (botcmd[0] == "!froll") | |
{ | |
int roll = 0, d[4] = { fateroll(), fateroll(), fateroll(), fateroll() }; | |
for (int i = 0; i < 4; i++) if (d[i] == '+') roll++; else if (d[i] == '-') roll--; | |
snprintf(buf, 1024, "FATE roll: %c, %c, %c, %c = %d", d[0], d[1], d[2], d[3], roll); | |
sendMessage(cmd.params[0].c_str(), buf); | |
} | |
else if (botcmd[0] == "!droll") | |
{ | |
if (botcmd.size() > 1) | |
{ | |
sendMessage(cmd.params[0].c_str(), roll(botcmd[1]).c_str()); | |
} | |
else sendMessage(cmd.params[0].c_str(), "Missing parameter."); | |
} | |
} | |
BotClient(const char* name = BOT_USERNAME, const char* server = BOT_SERVER, const char* port = BOT_PORT) : | |
IRCClient(name, server, port) | |
{ | |
randeng.seed(time(NULL)); | |
enableDebug(true); | |
registerCommand("372", bind(&BotClient::motd, this, _1)); | |
registerCommand("251", bind(&BotClient::motd, this, _1)); | |
registerCommand("NOTICE", bind(&BotClient::motd, this, _1)); | |
registerCommand("PRIVMSG", bind(&BotClient::privmsg, this, _1)); | |
} | |
}; | |
int main() | |
{ | |
try { | |
BotClient bot; | |
bot.joinChannel(BOT_AUTOJOIN, NULL, false); | |
bot.Run(); | |
} | |
catch (std::exception &e) | |
{ | |
std::cout << e.what(); | |
} | |
std::cin.get(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment