Cppgram  1.0.0
Easy and modern C++14 Telegram Bot API wrapper
basic_bot_impl.hpp
1 // This is an open source non-commercial project. Dear PVS-Studio, please check it.
2 // PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3 
4 #include <cpr/cpr.h>
5 #include <json/json.h>
6 
7 #include "cppgram/basic_bot.hpp"
8 #include "cppgram/commands/command.hpp"
9 #include "cppgram/exception.hpp"
10 #include "cppgram/types/update.hpp"
11 
12 template <class T>
13 BasicBot<T>::BasicBot( const std::string &token, std::string name, T *obj_ptr )
14  : api_url( "https://api.telegram.org/bot" + token + "/" )
15  , bot_name( name )
16  , logger_ptr( nullptr )
17  , command_handler( obj_ptr )
18 {
19 }
20 
21 template <class T> BasicBot<T>::BasicBot( const BasicBot &b, T *base_ptr )
22 {
23  api_url = b.api_url;
24 
30 
31  bot_name = b.bot_name;
32 
33  logger_ptr = b.logger_ptr;
34 
35  command_handler = std::move( CommandHandler<T>( base_ptr, b.command_handler ) );
36 }
37 
38 template <class T>
41 {
42  api_url = b.api_url;
43 
49 
50  bot_name = b.bot_name;
51 
52  logger_ptr = b.logger_ptr;
53 
54  // Create a new CommandHandler object, giving this object as owner and the commands of the
55  // rvalue, then std::move it in command_handler (to not lose owner ptr)
56  command_handler
57  = std::move( CommandHandler<T>( dynamic_cast<T *>( this ), b.command_handler ) );
58 
59  return *this;
60 }
61 
62 template <class T>
63 std::shared_ptr<spdlog::logger>
64 BasicBot<T>::setLogger( spdlog::sink_ptr sink )
65 {
66  // Create a vector containing just a single sink
67  std::vector<spdlog::sink_ptr> sinks;
68  sinks.push_back( sink );
69  return setLogger( sinks );
70 }
71 
72 template <class T>
73 std::shared_ptr<spdlog::logger>
74 BasicBot<T>::setLogger( std::vector<spdlog::sink_ptr> &sinks )
75 {
76  try
77  {
78  // Create a logger to the given sinks
79  logger_ptr = std::make_shared<spdlog::logger>( bot_name, sinks.begin(), sinks.end() );
80 
81  // Flush on error or severe messages
82  logger_ptr->flush_on( spdlog::level::err );
83 
84  // Return created logger
85  return logger_ptr;
86  }
87  catch ( const spdlog::spdlog_ex &ex )
88  {
89  // catch the error
90  auto console = spdlog::get( "error_console" );
91  console->error( ex.what() );
92  }
93 
94  // We could not create a logger, return a nullptr
95  return nullptr;
96 }
97 
98 template <class T>
99 void
100 BasicBot<T>::setLogger( std::shared_ptr<spdlog::logger> new_logger )
101 {
102  logger_ptr = new_logger;
103 }
104 
105 template <class T>
106 void
108 {
109  command_handler.init( dynamic_cast<T *>( this ) );
110 }
111 
112 template <class T>
113 const cpr::Response
114 BasicBot<T>::executeRequest( const std::string &method, const cpr::Parameters &params )
115 {
116  connection.SetUrl( api_url + method );
117  connection.SetParameters( params );
118  return connection.Get();
119 }
120 
121 template <class T>
122 bool
123 BasicBot<T>::checkMethodError( const cpr::Response &response, Json::Value &val )
124 {
125  // If there was an error in the connection print it
126  if ( response.error.code != cpr::ErrorCode::OK )
127  {
128  logger_ptr->error( "HTTP Error:" + response.error.message );
129  return false;
130  }
131 
132  if ( !reader.parse( response.text, val ) )
133  {
134  logger_ptr->error( "JSON Parser: Error while parsing JSON document!" );
135  throw JsonParseError();
136  }
137 
138  // Print method error
139  if ( response.status_code != 200 )
140  {
141  logger_ptr->error( "Telegram Error: " + val["error_code"].asString() + ", Description: "
142  + val["description"].asString() );
143  return false;
144  }
145 
146  return true;
147 }
148 
149 template <class T>
150 bool
151 BasicBot<T>::getUpdates( std::vector<Update> &updates,
152  const uint_fast32_t offset,
153  const uint_fast32_t limit,
154  const uint_fast32_t timeout )
155 {
156  // If there are items in the vector
157  if ( updates.size() != 0 )
158  {
159  // clear it
160  updates.clear();
161  }
162 
163  auto response = executeRequest( "getUpdates",
164  cpr::Parameters{{"timeout", std::to_string( timeout )},
165  {"limit", std::to_string( limit )},
166  {"offset", std::to_string( offset + 1 )}} );
167 
168  Json::Value json_updates;
169  if ( !checkMethodError( response, json_updates ) || json_updates["result"].empty() )
170  {
171  return false;
172  }
173 
174  // For each update
175  for ( auto &json_single_update : json_updates["result"] )
176  {
177  // Construct them in place
178  updates.emplace_back( json_single_update );
179  }
180 
181  // We found updates
182  return true;
183 }
184 
185 template <class T>
186 std::experimental::optional<const cppgram::types::Message>
187 BasicBot<T>::sendMessage( const int_fast64_t chat_id,
188  const std::string &text,
189  const std::string &reply_markup,
190  const EParseMode parse_mode,
191  const bool disable_web_page_preview,
192  const bool disable_notification,
193  const int_fast32_t reply_to_message_id )
194 {
195  return sendMessage( std::to_string( chat_id ),
196  text,
197  reply_markup,
198  parse_mode,
199  disable_web_page_preview,
200  disable_notification,
201  reply_to_message_id );
202 }
203 
204 template <class T>
205 std::experimental::optional<const cppgram::types::Message>
206 BasicBot<T>::sendMessage( const std::string &chat_id,
207  const std::string &text,
208  const std::string &reply_markup,
209  const EParseMode parse_mode,
210  const bool disable_web_page_preview,
211  const bool disable_notification,
212  const int_fast32_t reply_to_message_id )
213 {
214  std::string parse_mode_string = "";
215 
216  if ( parse_mode == EParseMode::HTML )
217  {
218  parse_mode_string = "HTML";
219  }
220  else if ( parse_mode == EParseMode::Markdown )
221  {
222  parse_mode_string = "Markdown";
223  }
224 
225  auto response = executeRequest(
226  "sendMessage",
227  cpr::Parameters{
228  {"chat_id", chat_id},
229  {"text", text},
230  {"parse_mode", parse_mode_string},
231  {"disable_web_page_preview", std::to_string( disable_web_page_preview )},
232  {"disable_notification", std::to_string( disable_notification )},
233  {"reply_to_message_id", std::to_string( reply_to_message_id )},
234  {"reply_markup", reply_markup}} );
235 
236  Json::Value valroot;
237  if ( !checkMethodError( response, valroot ) )
238  {
239  return cppgram::types::Message();
240  }
241 
242  return cppgram::types::Message( valroot["result"] );
243 }
244 
245 template <class T>
246 std::experimental::optional<const cppgram::types::Message>
247 BasicBot<T>::sendMessage( const std::string &text,
248  const std::string &reply_markup,
249  const EParseMode parse_mode,
250  const bool disable_web_page_preview,
251  const bool disable_notification,
252  const int_fast32_t reply_to_message_id )
253 {
254  return sendMessage( std::to_string( chat_id ),
255  text,
256  reply_markup,
257  parse_mode,
258  disable_web_page_preview,
259  disable_notification,
260  reply_to_message_id );
261 }
262 
263 template <class T>
264 bool
265 BasicBot<T>::answerCallbackQuery( const std::string &text,
266  bool show_alert,
267  uint32_t cache_time,
268  const std::string &url )
269 {
270  auto response = executeRequest( "answerCallbackQuery",
271  cpr::Parameters{{"callback_query", callback_query_id},
272  {"text", text},
273  {"show_alert", std::to_string( show_alert )},
274  {"cache_time", std::to_string( cache_time )},
275  {"url", url}} );
276 
277  Json::Value valroot;
278  if ( !checkMethodError( response, valroot ) )
279  {
280  return false;
281  }
282 
283  return true;
284 }
285 
286 template <class T>
287 std::experimental::optional<const cppgram::types::Message>
288 BasicBot<T>::editMessageText( const int_fast64_t chat_id,
289  const uint_fast32_t message_id,
290  const std::string & text,
291  const std::string & reply_markup,
292  const EParseMode parse_mode,
293  const bool disable_web_page_preview )
294 {
295  return sendMessage( std::to_string( chat_id ),
296  message_id,
297  text,
298  reply_markup,
299  parse_mode,
300  disable_web_page_preview );
301 }
302 
303 template <class T>
304 std::experimental::optional<const cppgram::types::Message>
305 BasicBot<T>::editMessageText( const std::string & chat_id,
306  const uint_fast32_t message_id,
307  const std::string & text,
308  const std::string & reply_markup,
309  const EParseMode parse_mode,
310  const bool disable_web_page_preview )
311 {
312  std::string parseMode = "";
313 
314  if ( parse_mode == EParseMode::HTML )
315  {
316  parseMode = "HTML";
317  }
318  else if ( parse_mode == EParseMode::Markdown )
319  {
320  parseMode = "Markdown";
321  }
322 
323  auto response = executeRequest( "editMessageText",
324  cpr::Parameters{{"chat_id", chat_id},
325  {"message_id", std::to_string( message_id )},
326  {"text", text},
327  {"parse_mode", parseMode},
328  {"disable_web_page_preview",
329  std::to_string( disable_web_page_preview )},
330  {"reply_markup", reply_markup}} );
331 
332  Json::Value valroot;
333  if ( !checkMethodError( response, valroot ) )
334  {
335  return cppgram::types::Message();
336  }
337 
338  return cppgram::types::Message( valroot["result"] );
339 }
340 
341 template <class T>
342 std::experimental::optional<const cppgram::types::Message>
343 BasicBot<T>::editMessageText( const uint_fast32_t message_id,
344  const std::string & text,
345  const std::string & reply_markup,
346  const EParseMode parse_mode,
347  const bool disable_web_page_preview )
348 {
349  return editMessageText( std::to_string( chat_id ),
350  message_id,
351  text,
352  reply_markup,
353  parse_mode,
354  disable_web_page_preview );
355 }
356 
357 template <class T>
358 bool
359 BasicBot<T>::editMessageText( const std::string &inline_message_id,
360  const std::string &text,
361  const std::string &reply_markup,
362  const EParseMode parse_mode,
363  const bool disable_web_page_preview )
364 {
365  std::string parseMode = "";
366 
367  if ( parse_mode == EParseMode::HTML )
368  {
369  parseMode = "HTML";
370  }
371  else if ( parse_mode == EParseMode::Markdown )
372  {
373  parseMode = "Markdown";
374  }
375 
376  auto response = executeRequest(
377  "editMessageText",
378  cpr::Parameters{{"inline_message_id", inline_message_id},
379  {"text", text},
380  {"parse_mode", parseMode},
381  {"disable_web_page_preview", disable_web_page_preview},
382  {"reply_markup", reply_markup}} );
383 
384  Json::Value valroot;
385  if ( !checkMethodError( response, valroot ) )
386  {
387  return false;
388  }
389 
390  return valroot["result"].asBool();
391 }
392 
393 template <class T>
394 std::experimental::optional<const cppgram::types::Message>
395 BasicBot<T>::editMessageCaption( const int_fast64_t chat_id,
396  const uint_fast32_t message_id,
397  const std::string & caption,
398  const std::string & reply_markup )
399 {
400  return editMessageCaption( std::to_string( chat_id ), message_id, caption, reply_markup );
401 }
402 
403 template <class T>
404 std::experimental::optional<const cppgram::types::Message>
405 BasicBot<T>::editMessageCaption( const std::string & chat_id,
406  const uint_fast32_t message_id,
407  const std::string & caption,
408  const std::string & reply_markup )
409 {
410  auto response = executeRequest( "editMessageCaption",
411  cpr::Parameters{{"chat_id", chat_id},
412  {"message_id", std::to_string( message_id )},
413  {"caption", caption},
414  {"reply_markup", reply_markup}} );
415 
416  Json::Value valroot;
417  if ( !checkMethodError( response, valroot ) )
418  {
419  return cppgram::types::Message();
420  }
421 
422  return cppgram::types::Message( valroot["result"] );
423 }
424 
425 template <class T>
426 std::experimental::optional<const cppgram::types::Message>
427 BasicBot<T>::editMessageCaption( const uint_fast32_t message_id,
428  const std::string & caption,
429  const std::string & reply_markup )
430 {
431  return editMessageCaption( std::to_string( chat_id ), message_id, caption, reply_markup );
432 }
433 
434 template <class T>
435 bool
436 BasicBot<T>::editMessageCaption( const std::string &inline_message_id,
437  const std::string &caption,
438  const std::string &reply_markup )
439 {
440  auto response = executeRequest( "editMessageCaption",
441  cpr::Parameters{{"inline_message_id", inline_message_id},
442  {"caption", caption},
443  {"reply_markup", reply_markup}} );
444 
445  Json::Value valroot;
446  if ( !checkMethodError( response, valroot ) )
447  {
448  return false;
449  }
450 
451  return valroot["result"].asBool();
452 }
453 
454 template <class T>
455 std::experimental::optional<const cppgram::types::Message>
456 BasicBot<T>::editMessageReplyMarkup( const int_fast64_t chat_id,
457  const uint_fast32_t message_id,
458  const std::string & reply_markup )
459 {
460  return editMessageReplyMarkup( std::to_string( chat_id ), message_id, reply_markup );
461 }
462 
463 template <class T>
464 std::experimental::optional<const cppgram::types::Message>
465 BasicBot<T>::editMessageReplyMarkup( const std::string & chat_id,
466  const uint_fast32_t message_id,
467  const std::string & reply_markup )
468 {
469  auto response = executeRequest( "editMessageReplyMarkup",
470  cpr::Parameters{{"chat_id", chat_id},
471  {"message_id", std::to_string( message_id )},
472  {"reply_markup", reply_markup}} );
473 
474  Json::Value valroot;
475  if ( !checkMethodError( response, valroot ) )
476  {
477  return cppgram::types::Message();
478  }
479 
480  return cppgram::types::Message( valroot["result"] );
481 }
482 
483 template <class T>
484 std::experimental::optional<const cppgram::types::Message>
485 BasicBot<T>::editMessageReplyMarkup( const uint_fast32_t message_id,
486  const std::string & reply_markup )
487 {
488  return editMessageReplyMarkup( std::to_string( chat_id ), message_id, reply_markup );
489 }
490 
491 template <class T>
492 bool
493 BasicBot<T>::editMessageReplyMarkup( const std::string &inline_message_id,
494  const std::string &reply_markup )
495 {
496  auto response = executeRequest( "editMessageReplyMarkup",
497  cpr::Parameters{{"inline_message_id", inline_message_id},
498  {"reply_markup", reply_markup}} );
499 
500  Json::Value valroot;
501  if ( !checkMethodError( response, valroot ) )
502  {
503  return false;
504  }
505 
506  return valroot["result"].asBool();
507 }
508 
509 template <class T>
510 bool
511 BasicBot<T>::answerInlineQuery( const Json::Value & results,
512  const uint_fast16_t cache_time,
513  const bool is_personal,
514  const std::string & next_offset,
515  const std::string & switch_pm_text,
516  const std::string & switch_pm_paramter )
517 {
518  Json::FastWriter writer;
519 
520  auto response = executeRequest( "answerInlineQuery",
521  cpr::Parameters{{"inline_query_id", inline_query_id},
522  {"results", writer.write( results )},
523  {"cache_time", std::to_string( cache_time )},
524  {"is_personal", is_personal},
525  {"next_offset", next_offset},
526  {"switch_pm_text", switch_pm_paramter},
527  {"switch_pm_parameter", switch_pm_paramter}} );
528 
529  Json::Value valroot;
530  if ( !checkMethodError( response, valroot ) )
531  {
532  return false;
533  }
534 
535  return valroot["result"].asBool();
536 }
537 
538 template <class T>
539 void
540 BasicBot<T>::processUpdate( const Update &update )
541 {
542  if ( command_handler.processCommands( update ) )
543  {
544  return;
545  }
546 
547  T &bot = dynamic_cast<T &>( *this );
548  switch ( update.type )
549  {
550  case EUpdate::eMessage:
551  {
552  if ( processMessage )
553  {
554  chat_id = update.message->chat.id;
555  processMessage( bot, std::move( update.message.value() ) );
556  }
557  }
558  break;
559  case EUpdate::eCallbackQuery:
560  {
561  chat_id = update.callback_query->message->chat.id;
562  callback_query_id = update.callback_query->id;
563  processCallbackQuery( bot, std::move( update.callback_query.value() ) );
564  callback_query_id = "";
565  }
566  break;
567  case EUpdate::eEditedMessage:
568  {
569  if ( processEditedMessage )
570  {
571  chat_id = update.message->chat.id;
572  processEditedMessage( bot, std::move( update.message.value() ) );
573  }
574  }
575  break;
576  case EUpdate::eInlineQuery:
577  {
578  chat_id = update.inline_query->from.id;
579  inline_query_id = update.inline_query->id;
580  processInlineQuery( bot, std::move( update.inline_query.value() ) );
581  inline_query_id = "";
582  }
583  break;
584  case EUpdate::eChosenInlineResult:
585  {
586  chat_id = update.chosen_inline_result->from.id;
587  processChosenInlineResult( bot, std::move( update.chosen_inline_result.value() ) );
588  }
589  break;
590  case EUpdate::eChannelPost:
591  {
592  chat_id = update.message->chat.id;
593  processChannelPost( bot, std::move( update.message.value() ) );
594  }
595  break;
596  case EUpdate::eEditedChannelPost:
597  {
598  chat_id = update.message->chat.id;
599  processEditedChannelPost( bot, std::move( update.message.value() ) );
600  }
601  break;
602  }
603 }
void(* processInlineQuery)(T &, const types::InlineQuery &)
Pointer to the function that will be called on each inline query.
Definition: basic_bot.hpp:626
void(* processEditedMessage)(T &, const types::Message &)
Pointer to the function that will be called on each edited message by the user.
Definition: basic_bot.hpp:611
bool getUpdates(std::vector< types::Update > &updates, const uint_fast32_t offset=0, const uint_fast32_t limit=100, const uint_fast32_t timeout=60)
Receive incoming updates using polling (short or long polling based on timeout)
Definition: basic_bot_impl.hpp:151
A message send by user.
Definition: message.hpp:36
BasicBot(const std::string &token, std::string name="Bot", T *obj_ptr=nullptr)
Constuctor.
Definition: enums.hpp:75
std::shared_ptr< spdlog::logger > setLogger(spdlog::sink_ptr sink)
Set the bot&#39;s logger by passing a sink.
Definition: basic_bot_impl.hpp:64
Handle bot commands.
Definition: command_handler.hpp:26
void(* processEditedChannelPost)(T &, const types::Message &)
Pointer to the function that will be called on each message modified in a channel.
Definition: basic_bot.hpp:621
std::function< void(T &, const types::Message &)> processMessage
Pointer to the function that will be called on each message sent by the user.
Definition: basic_bot.hpp:606
virtual void init()
Init bot object.
Definition: basic_bot_impl.hpp:107
const cpr::Response executeRequest(const std::string &method, const cpr::Parameters &params)
Execute an API method by passing method name and parameters.
Definition: basic_bot_impl.hpp:114
std::experimental::optional< const class types::Message > editMessageCaption(const int_fast64_t chat_id, const uint_fast32_t message_id, const std::string &caption="", const std::string &reply_markup="")
Edit captions of messages sent by the bot.
If jsoncpp can&#39;t parse the JSON document (critical).
Definition: exception.hpp:24
std::experimental::optional< const class types::Message > editMessageText(const int_fast64_t chat_id, const uint_fast32_t message_id, const std::string &text, const std::string &reply_markup="", const EParseMode parse_mode=static_cast< EParseMode >(1), const bool disable_web_page_preview=true)
Edit text (and reply markup) of a message sent by the bot.
void(* processCallbackQuery)(T &, const types::CallbackQuery &)
Pointer to the funciton that will be called on each callback query.
Definition: basic_bot.hpp:636
contains api methods, update handlers and listener.
Definition: basic_bot.hpp:199
EParseMode
Formattation options.
Definition: enums.hpp:66
bool answerInlineQuery(const Json::Value &results, const uint_fast16_t cache_time=300, const bool is_personal=false, const std::string &next_offset="", const std::string &switch_pm_text="", const std::string &switch_pm_paramter="")
Answer an inline query. (https://core.telegram.org/bots/api#answerinlinequery)
Definition: basic_bot_impl.hpp:511
bool answerCallbackQuery(const std::string &text, bool show_alert=false, uint32_t cache_time=0, const std::string &url="")
Use this method to send answers to callback queries sent from inline keyboards. The answer will be di...
Definition: basic_bot_impl.hpp:265
Definition: enums.hpp:72
std::experimental::optional< const class types::Message > editMessageReplyMarkup(const int_fast64_t chat_id, const uint_fast32_t message_id, const std::string &reply_markup="")
Edit only the reply markup of a message sent by the the bot.
std::experimental::optional< const class types::Message > sendMessage(const int_fast64_t chat_id, const std::string &text, const std::string &reply_markup="", const EParseMode parse_mode=EParseMode::HTML, const bool disable_web_page_preview=true, const bool disable_notification=false, const int_fast32_t reply_to_message_id=0)
Send a message to a specified chat. (https://core.telegram.org/bots/api#sendmessage) ...
void(* processChannelPost)(T &, const types::Message &)
Pointer to the function that will be called on each message received in a channel.
Definition: basic_bot.hpp:616
Definition: enums.hpp:40
void(* processChosenInlineResult)(T &, const types::ChosenInlineResult &)
Pointer to the function that will be called on each inline query choosed by the user.
Definition: basic_bot.hpp:631