先日、github で crow という、python の flask からインスパイアされた C++ 製 micro web framework を見つけました。
ipkn/crow - GitHub
https://github.com/ipkn/crow
ルーティングの書き方が flask ぽく、かつモダンな C++ な書き方だったの気に入りました。
crow::Crow app;
CROW_ROUTE(app, "/")
.name("hello")
([]{
return "Hello World!";
});
app.port(18080)
.multithreaded()
.run();
何か作ってみたくなったので、lingr の bot を書いてみました。crow には JSON パーサ(シリアライザ)が同梱されているので API サーバを作るのも簡単そうです。
#include "crow.h"
#include "json.h"
#include <iostream>
#include <sstream>
#include <regex>
static std::vector<std::string>
str_split(const string s, const string d) {
std::vector<string> r;
std::string::size_type n;
std::string b = s;
while ((n = b.find_first_of(d)) != b.npos) {
if(n > 0) r.push_back(b.substr(0, n));
b = b.substr(n + 1);
}
if(b.length() > 0) r.push_back(b);
return r;
}
static std::string&
str_replace(std::string& s, const std::string p, const std::string r) {
std::string::size_type n(s.find(p));
while (n != std::string::npos) {
s.replace(n, p.length(), r);
n = s.find(p, n + r.length());
}
return s;
}
class logger : public crow::ILogHandler {
public:
void log(string message, crow::LogLevel level) override {
cerr << message;
}
};
int
main() {
std::vector<std::pair<const std::regex, const std::string>> resp;
std::ifstream ifs("pattern.txt");
if (ifs.fail()) return 1;
string buf;
while(ifs && getline(ifs, buf)) {
auto t = str_split(buf, "\t");
if (t.size() != 2) continue;
std::cout << "Loading pattern: " << t[0] << "=" << t[1] << std::endl;
resp.push_back(make_pair(std::regex(t[0]), t[1]));
}
ifs.close();
crow::Crow app;
CROW_ROUTE(app, "/")
([]{
return "lingr-cppbot";
});
CROW_ROUTE(app, "/lingr")
([&](const crow::request& req){
auto x = crow::json::load(req.body);
if (!x) return crow::response(400);
std::stringstream os;
for (const auto& e : x["events"]) {
std::string t = e["message"]["text"].s();
for (const auto& r : resp) {
std::smatch matches;
if (!regex_match(t, matches, r.first)) continue;
std::string output = r.second;
for (std::smatch::size_type i = 0; i < matches.size(); ++i) {
std::stringstream ss;
ss << "$" << i;
str_replace(output, ss.str(), matches[i]);
}
os << output;
break;
}
}
return crow::response{os.str()};
});
crow::logger::setLogLevel(crow::LogLevel::INFO);
crow::logger::setHandler(std::make_shared<logger>());
uint16_t port;
std::stringstream ss;
ss << getenv("PORT");
ss >> port;
if (port == 0) port = 8080;
app.port(port).multithreaded().run();
}
仕組みは至って簡単で、カレントディレクトリにある pattern.txt
という、以下の様なファイルを読み込み、正規表現(タブ文字で区切られた左側)に対する置換文字列(右側)を設定します。
^あー$ いー
^いー$ うー
^うー$ えー
^えー$ おー
^おー$ もうええやろ!
^(.+)(大好き|だいすき) $1は俺の物だ。勝手に呼び捨てにするな。
起動して、正規表現にマッチする発言を受信すると、置換文字列をサブマッチなどで入れ替えて返答します。
サブマッチ置換が使えるので、ある程度人間っぽい発言も可能ですし、gh:mattn
という文字列から https://github.com/mattn
という返答をさせてアンカーを作る等の応用例もあります。(参考資料)
crow は内部でスレッドを使っており、複数同時リクエストにも応答出来る様です。色々遊べそうなので試してみてはいかがでしょうか。