237 lines
6.3 KiB
C++
237 lines
6.3 KiB
C++
#include "bot.h"
|
|
|
|
#include <sstream>
|
|
|
|
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};
|
|
}
|
|
|
|
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;
|
|
|
|
// @GroupAnonymousBot
|
|
if(msg.from.id != 1087968824 && !msg.from.is_bot) {
|
|
g.addPlayer(userToUserInfo(msg.from));
|
|
}
|
|
|
|
std::vector<TelegramAPI::MessageEntity> 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;
|
|
}
|
|
|
|
#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<TelegramAPI::MessageEntity> 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<TelegramAPI::MessageEntity>& 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 if(!g.ready()) {
|
|
out << "Waiting for players";
|
|
} 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);
|
|
}
|
|
|
|
// game not ready, just stop
|
|
if(!g->getPlayerB()) {
|
|
return g;
|
|
}
|
|
|
|
// 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<TelegramAPI::MarkupButton>& 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;
|
|
} |