diff --git a/README.md b/README.md index aad8465..5df5b79 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,63 @@ -# tgbot - -A minimal C Telegram API Framework. - -## Requirements - -- libcurl -- json-c - -> Note: This project is purely educational. It does not aim to cover the entire Telegram Bot API, but only a selected subset of methods. - -## How to build - -
- -Linux - -```bash -$ meson setup build -$ cd build -$ meson compile -$ meson install -``` - -
- -
- -Windows - -Install all the required library with `vcpkg` and then copy the DLL file. - -```powershell -$ meson setup build --native-file meson-vcpkg.txt -$ cd build -$ meson compile -$ meson install -``` - -
- -## Examples - -You can find some examples [here](./examples/). - -### Supported Types - -- **InlineKeyboardMarkup** - - Note: Standard `KeyboardMarkup` is intentionally not supported. - -#### Supported Methods - -- `getMe` -- `sendMessage` -- `editMessageText` -- `sendDice` - -## Roadmap - -- `sendPhoto` -- `sendAudio` -- `sendDocument` -- `sendVideo` +# tgbot + +A minimal C Telegram API library. + +## Requirements + +- libcurl +- json-c + +> Note: This project is purely educational. It does not aim to cover the entire Telegram Bot API, but only a selected subset of methods. + +## How to build + +
+ +Linux + +```bash +$ meson setup build +$ cd build +$ meson compile +$ meson install +``` + +
+ +
+ +Windows + +Install all the required library with `vcpkg` and then copy the DLL file. + +```powershell +$ meson setup build --native-file meson-vcpkg.txt +$ cd build +$ meson compile +$ meson install +``` + +
+ +## Examples + +You can find some examples [here](./examples/). + +### Supported Types + +- **InlineKeyboardMarkup** + - Note: Standard `KeyboardMarkup` is intentionally not supported. + +#### Supported Methods + +- `getMe` +- `sendMessage` +- `editMessageText` +- `sendDice` +- `sendPhoto` + +## Roadmap + +- `sendAudio` +- `sendDocument` +- `sendVideo` diff --git a/examples/sender/sendpic.c b/examples/sender/sendpic.c new file mode 100644 index 0000000..7a51682 --- /dev/null +++ b/examples/sender/sendpic.c @@ -0,0 +1,39 @@ +#include +#include +#include + +#include +#include +#include + +#define START_MESSAGE "Send /photo to receive a nice landscape!" +#define PHOTO_PATH "my_photo.jpg" + +int main(void) { + FILE *tok = fopen(".token", "r"); + if (!tok) { + exit(EXIT_FAILURE); + } + + char token[512]; + fscanf(tok, "%s", token); + fprintf(stdout, "Token: %s\n", token); + + tgbot_s *bot = tgbot_new(token); + if (!bot) { + return -1; + } + + tgbot_update_s update; + while (1) { + tgbot_get_update(bot, &update, NULL); + + if (!strcmp(update.text, "/start")) { + tgbot_send_message(bot, update.chat_id, START_MESSAGE, "MARKDOWN", NULL); + } else if (!strcmp(update.text, "/photo")) { + tgbot_send_photo(bot, update.chat_id, PHOTO_PATH, "Mountains!"); + } + } + + return 0; +} diff --git a/include/methods.h b/include/methods.h index 8acda64..190d632 100644 --- a/include/methods.h +++ b/include/methods.h @@ -11,6 +11,7 @@ int tgbot_get_me(const tgbot_s *bot, tgbot_me_s *me); int tgbot_send_message(const tgbot_s *bot, int64_t chat_id, const char *text, const char *parse_mode, tgbot_inlinekeyboard_s *reply_markup); int tgbot_send_dice(const tgbot_s *bot, int64_t chat_id, const char *emoji); +int tgbot_send_photo(const tgbot_s *bot, int64_t chat_id, const char *path, const char *caption); /* Updating Methods */ int tgbot_edit_message_text(const tgbot_s *bot, int64_t chat_id, long message_id, const char *text, diff --git a/meson.build b/meson.build index 4569c6a..f523a6a 100644 --- a/meson.build +++ b/meson.build @@ -58,11 +58,9 @@ if cppcheck.found() endif # Example -example = executable( +executable( 'example', - 'examples/inlinekeyboard/inlinekeyboard.c', + 'examples/sender/sendpic.c', dependencies: tgbot_dep, build_by_default: false, -) - -test('example_inlinekeyboard', example) \ No newline at end of file +) \ No newline at end of file diff --git a/src/methods.c b/src/methods.c index fbdfb6d..3eacb4f 100644 --- a/src/methods.c +++ b/src/methods.c @@ -10,7 +10,7 @@ #define opt_size(arr) (sizeof(arr) / sizeof(arr[0])) -static size_t write_callback(const void *ptr, size_t size, size_t nmemb, char *userdata) { +static size_t write_callback(void *ptr, size_t size, size_t nmemb, char *userdata) { size_t real_size = size * nmemb; struct memory_buffer *mem = (struct memory_buffer *)userdata; @@ -53,16 +53,12 @@ static int tgbot_request(const char *url, struct memory_buffer **mb, json_object curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, *mb); } else { - struct memory_buffer *mb_f = malloc(sizeof *mb_f); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, discard_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, mb_f); - if (mb_f) { - free(mb_f); - } + curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL); } curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); - curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 30L); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); if (json != NULL) { json_string = json_object_to_json_string_ext(json, JSON_C_TO_STRING_PLAIN); @@ -100,6 +96,60 @@ static int tgbot_execute_method(const tgbot_s *bot, const char *method, tgbot_op return ret; } +static int tgbot_execute_method_multipart(const tgbot_s *bot, const char *method, int64_t chat_id, const char *path, + const char *caption) { + CURL *curl = curl_easy_init(); + if (!curl) { + return -1; + } + + char url[URL_LEN] = {0}; + int chars = snprintf(url, sizeof(url), "%s%s", bot->api, method); + if (chars < 0 || (size_t)chars >= sizeof(url)) { + curl_easy_cleanup(curl); + return -1; + } + + curl_mime *mime = curl_mime_init(curl); + curl_mimepart *part; + + char chat_id_str[512]; + snprintf(chat_id_str, sizeof chat_id_str, "%ld", chat_id); + + part = curl_mime_addpart(mime); + curl_mime_data(part, chat_id_str, CURL_ZERO_TERMINATED); + curl_mime_name(part, "chat_id"); + + part = curl_mime_addpart(mime); + curl_mime_filedata(part, path); + curl_mime_name(part, "photo"); + + part = curl_mime_addpart(mime); + curl_mime_data(part, caption, CURL_ZERO_TERMINATED); + curl_mime_name(part, "caption"); + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); + + /* Do not print response to output */ + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, discard_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL); + + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform: %s\n", curl_easy_strerror(res)); + curl_easy_cleanup(curl); + curl_mime_free(mime); + + return -1; + } + + curl_easy_cleanup(curl); + curl_mime_free(mime); + + return 0; +} + int tgbot_get_update(tgbot_s *bot, tgbot_update_s *update, Callback cbq_handler) { char url[URL_LEN]; @@ -191,10 +241,14 @@ int tgbot_get_me(const tgbot_s *bot, tgbot_me_s *me) { const json_object *result = json_object_object_get(json, "result"); json_object *first_name = json_object_object_get(result, "first_name"); - snprintf(me->first_name, sizeof(me->first_name), "%s", json_object_get_string(first_name)); + if (first_name) { + snprintf(me->first_name, sizeof(me->first_name), "%s", json_object_get_string(first_name)); + } json_object *username = json_object_object_get(result, "username"); - snprintf(me->username, sizeof(me->username), "%s", json_object_get_string(username)); + if (username) { + snprintf(me->username, sizeof(me->username), "%s", json_object_get_string(username)); + } json_object_put(json); @@ -233,3 +287,7 @@ int tgbot_send_dice(const tgbot_s *bot, int64_t chat_id, const char *emoji) { return tgbot_execute_method(bot, "sendDice", options, opt_size(options)); } + +int tgbot_send_photo(const tgbot_s *bot, int64_t chat_id, const char *path, const char *caption) { + return tgbot_execute_method_multipart(bot, "sendPhoto", chat_id, path, caption); +}