#include "bot.h" #include Bot::Bot(TelegramAPI::Manager* tapi, Conf& conf) : tapi(tapi), conf(conf) {} Bot::~Bot() {} bool Bot::handleStart(TelegramAPI::Manager* api, const TelegramAPI::Message& msg) { if(msg.chat.id > 0) { api->sendMessage(msg.chat.id, "This bot should be used in group chats"); return true; } // create a empty game Game g; std::vector entities; const std::string text = gameToMessage(g, entities); auto sendmsg = api->sendMessage(msg.chat.id, text, gameToKeyboard(g), entities); return true; } bool Bot::handleMessage(TelegramAPI::Manager* api, TelegramAPI::Message& msg) { return true; } static Game::UserInfo userToUserInfo(const TelegramAPI::User& u) { // + (u.last_name.empty() ? "" : " " + u.last_name) // problems with spaces and unicode in last name return {u.id, u.first_name, u.username}; } #define delret delete g; \ return true bool Bot::handleCallback(TelegramAPI::Manager* api, TelegramAPI::CallbackQuery& clbq) { int64_t chatid = clbq.message.chat.id; uint64_t msgid = clbq.message.messageId; const std::string& data = clbq.data; Game* g = messageToGame(clbq.message); if(!g) { api->answerCallbackQuery(clbq.id, "Invalid game"); return true; } if(data == "join") { Game::UserInfo newuser = userToUserInfo(clbq.from); if(g->addPlayer(newuser)) { api->answerCallbackQuery(clbq.id, "You joined the game"); updateGame(chatid, msgid, *g); delret; } api->answerCallbackQuery(clbq.id, "You can not join"); delret; } // try to parse "x y" std::istringstream datastream(data); int x = -1, y = -1; datastream >> x >> y; if(x != -1 && y != -1) { Game::UserInfo player = userToUserInfo(clbq.from); if(g->done()) { api->answerCallbackQuery(clbq.id, "Error - game is over"); delret; } if(g->turn(x, y, player)) { api->answerCallbackQuery(clbq.id, "ok"); updateGame(chatid, msgid, *g); delret; } api->answerCallbackQuery(clbq.id, "Error - this is not your turn"); delret; } api->answerCallbackQuery(clbq.id, "Error - invalid button"); delret; } #undef delret void Bot::stop() { } void Bot::updateGame(int64_t chatid, uint64_t msgid, const Game& g) { std::vector entities; const std::string text = gameToMessage(g, entities); tapi->editMessageText(chatid, msgid, text, gameToKeyboard(g), entities); } TelegramAPI::InlineKeyboard Bot::gameToKeyboard(const Game& g) const { static const std::string text[3] = {" ", "X", "O"}; TelegramAPI::InlineKeyboard kb; for(uint_fast8_t row = 0; row < g.getSize(); row++) { for(uint_fast8_t col = 0; col < g.getSize(); col++) { Game::SYM s = g.getPos(col, row); const std::string clb = std::to_string(col) + " " + std::to_string(row); auto btn = TelegramAPI::InlineButton::createCallback(text[(int) s], clb); kb.addButton(btn, row); } } if(!g.ready() && !g.done()) { // missing player kb.addButton(TelegramAPI::InlineButton::createCallback("Join Game", "join"), g.getSize()); } return kb; } static TelegramAPI::MessageEntity userInfoToEntity(const Game::UserInfo& userinfo) { TelegramAPI::MessageEntity ent; if(userinfo.username.empty()) { // id bassed mention only works with users with no username ent.type = TelegramAPI::MessageEntity::Type::TEXT_MENTION; ent.user.id = userinfo.id; ent.length = userinfo.name.size(); } else { ent.type = TelegramAPI::MessageEntity::Type::MENTION; ent.length = userinfo.username.size()+1; } return ent; } std::string Bot::gameToMessage(const Game& g, std::vector& entites) const { std::ostringstream out; out << "X: " << g.getPlayerA().getUsernameOrName() << std::endl; out << "O: " << g.getPlayerB().getUsernameOrName() << std::endl; if(g.done()) { if(g.getWinner().empty()) { out << "Draw" << std::endl; } else { out << "Winner: " << g.getWinner() << std::endl; } } else { out << "nextturn: " << (g.getNextTurn() ? "X" : "O") << std::endl; } //create entities if(g.getPlayerA()) { TelegramAPI::MessageEntity a = userInfoToEntity(g.getPlayerA()); a.offset = 3; entites.push_back(a); if(g.getPlayerB()) { TelegramAPI::MessageEntity b = userInfoToEntity(g.getPlayerB()); b.offset = 7 + a.length; // ("x: " + "\n" + "O: ").size() = 7 entites.push_back(b); } } return out.str(); } static Game::UserInfo userinfoFromEntity(const TelegramAPI::MessageEntity& ent, const std::string& messageText) { if(ent.type == TelegramAPI::MessageEntity::Type::TEXT_MENTION) { uint64_t userid = ent.user.id; std::string name = ent.user.first_name; std::string username = ent.user.username; return {userid, name, username}; } if(ent.type == TelegramAPI::MessageEntity::Type::MENTION) { return {0, "", messageText.substr(ent.offset+1, ent.length-1)}; // without the "@" } // invlaid user entity return {0, "", ""}; } Game* Bot::messageToGame(const TelegramAPI::Message& msg) const { if(msg.entities.size() > 2) { Log::warn << "To many entities: " << msg.entities.size(); return nullptr; } if(msg.entities.size() == 0) { // no user (empty game) return new Game(); } Game* g = new Game(); // read the userinfo from the entities for(uint_fast8_t i = 0; i < msg.entities.size(); ++i) { const TelegramAPI::MessageEntity& ent = msg.entities.at(i); Game::UserInfo info = userinfoFromEntity(ent, msg.text); if(!info) { // invalid Log::warn << "invalid entity " << (int) i; delete g; return nullptr; } g->addPlayer(info); } // read the next turn value char nextturn = msg.text.at(msg.text.size()-1); g->setNextTurn(nextturn == 'X'); // get the game for(uint_fast8_t i = 0; i < msg.replyMarkup.size(); ++i) { const std::vector& row = msg.replyMarkup.at(i); for(uint_fast8_t j = 0; j < row.size(); ++j) { const TelegramAPI::MarkupButton& btn = row.at(j); // button is part of game if(i < g->getSize()) { Game::SYM s = Game::SYM::NONE; if(btn.text == "X") { s = Game::SYM::A; } else if(btn.text == "O") { s = Game::SYM::B; } g->setPos(j, i, s); } } } return g; }