Skip to content

Instantly share code, notes, and snippets.

@zardoru
Last active August 29, 2015 14:21
Show Gist options
  • Save zardoru/feddbcfba82403aea776 to your computer and use it in GitHub Desktop.
Save zardoru/feddbcfba82403aea776 to your computer and use it in GitHub Desktop.
rollbot
/* 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