Отправка фото через тг бота с++

Пишу тг бота на с++, используя эту библиотеку для бота и эту для работы с изображениями. Бот должен принимать фото, преобразовывать его в негатив и отправлять обратно пользователю. Вот мой код:

#include <tgbot/tgbot.h>
#include <curl/curl.h>
#include "CImg.h"

using namespace cimg_library;
using namespace TgBot;
using namespace std;

const string token = "token";

TgBot::Bot bot(token);

void download_file(const string& url, const string& output_filename){
    CURL* curl = curl_easy_init();
    if(curl){
        FILE* fp = fopen(output_filename.c_str(), "wb");
        if(fp){
            curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, 0);
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);

            CURLcode res = curl_easy_perform(curl);
            fclose(fp); // Закрываем файл

            if(res != CURLE_OK)
                fprintf(stderr, "curl_easy_perform() failed: %s\n",
                curl_easy_strerror(res));
        }

        curl_easy_cleanup(curl);
    }
}

int main(){
    bot.getEvents().onAnyMessage([&bot](Message::Ptr m){
        static uint8_t wherePhoto;
        if(m->chat->type != TgBot::Chat::Type::Private) return;

        if(m->text == "/start"){
            wherePhoto = 0;
            bot.getApi().sendMessage(m->chat->id, "Добро пожаловать в бота - ретушера.\n"            "отправьте фото и выберите эффект для наложения");
            return;
        }
        if(m->text == "/negative"){
            if(!wherePhoto) return;

            const CImg<unsigned char> img1("save.jpg"),
            img2 = -img1;
            img2.save_jpeg("save_n.jpg");
            bot.getApi().sendPhoto(m->chat->id, "save_n.jpg");
            wherePhoto = 0;
        }

        if(m->photo.size()){
            string Url = "https://api.telegram.org/file/bot" + token + "/" + bot.getApi().getFile(m->photo[0]->fileId)->filePath;
            download_file(Url,"save.jpg");
            bot.getApi().sendMessage(m->chat->id, "Список доступных преобразований:\n"
                "/negative"
            );
            wherePhoto = 1;
            return;
        } else {
            bot.getApi().sendMessage(m->chat->id, "Отправьте фото для наложения эффектов");
            return;
        }
    });

    try {
        TgLongPoll longPoll(bot);
        while (true) {
            longPoll.start();
            cout << "LP started\n";
        }
    } catch (exception& e) {
        cerr << e.what() << endl;
    }

    return 0;
}

По задумке отправленное фото должно сохраняться на компе, преобразовываться в негатив и отправляться обратно пользователю (а потом само собой удаляться с компа). Проблема возникает при отправке: здесь функция bot.getApi().sendPhoto(m->chat->id, "save_n.jpg"); не срабатывает, тк нужен либо url либо id файла либо отправка с помощью multipart/form-data (так написано в документации), но я не понимаю, как перейти от локального файла "save_n.jpg" к чему-то, что имеет ссылку. Подскажите, каким образом это лучше реализовать? Можно ли обойтись силами curl?


Ответы (2 шт):

Автор решения: Ivan Shatsky

Обновление

Ещё раз перечитав ваш вопрос, я прихожу к выводу, что мой ответ не вполне отвечает на собственно вопрос, как превратить бинарный файл в формат multipart/form-data и интегрировать полученные данные с вызовом bot.getApi().sendPhoto(). Наверное, для этого надо покопаться в документации к самому боту, но у меня к сожалению нет для этого ни времени, ни желания. Поскольку сам бот, похоже, использует для работы с HTTP именно библиотеку libcurl, сам исходный ответ я на всякий случай оставлю, вдруг он чем-нибудь да поможет. Но использовать библиотеку libcurl именно для генерации данных в формате multipart/form-data из бинарного файла, в отрыве от собственно вызова curl_easy_perform(), насколько мне известно, нельзя.


Конечно можно, libcurl - это очень могучая библиотека. Для этого вам понадобятся функции curl_mime_init() и curl_mime_filedata() (по ссылкам можно также посмотреть примеры использования). Код для отправки файла будет выглядеть примерно так (не проверял!):

void upload_file(const string& url, const string& output_filename){
    curl_mime *mime;
    curl_mimepart *part;
    CURL* curl = curl_easy_init();
    if(curl){
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());

        mime = curl_mime_init(curl);
        part = curl_mime_addpart(mime);
        curl_mime_filedata(part, output_filename.c_str());
        // Во всех примерах задают какое-то имя для mime part, наверное это необходимо :)
        curl_mime_name(part, "image");
        curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);

        CURLcode res = curl_easy_perform(curl);
        if(res != CURLE_OK)
            fprintf(stderr, "curl_easy_perform() failed: %s\n",
            curl_easy_strerror(res));

        curl_mime_free(mime);
        curl_easy_cleanup(curl);
    }
}

Весь файловый ввод/вывод происходит уже внутри libcurl, и код ошибки, если таковая вдруг произойдёт, вернётся при вызове curl_easy_perform().

P.S. Использованные функции доступны в libcurl начиная с версии 7.56, ранее для получения подобной функциональности надо было использовать функцию curl_formadd().

→ Ссылка
Автор решения: Тим Муранов

Проблему решил. Необходимо использовать функцию TgBot::InputFile::fromFile(). Она конвертирует фото в формат, пригодный к отправке через sendPhoto().

auto input_f = TgBot::InputFile::fromFile("path_to_photo.jpg", "image/jpg");
bot.getApi().sendPhoto(m->chat->id, input_f);
→ Ссылка