Привет, пишу тг бота на C++. Недавно мне требовалось подключить выгрузку данных из бд в гугл таблицы, но годного гайда как это сделать я не нашел. Поэтому будет простенький гайд. Расписываю как читать и записывать данные в гугл таблицы. Использовал библиотеки rapidjson, libcurl и jwt-cpp.
1. Создание сервисного аккаунта
Переходим в Google cloud console открываем выпадающее меню слева и переходим наводимся на вкладку APIs & Services и переходим в Credentials.

Дальше нажимаем Create credentials выбираем Service account. После этого переходим в настройки этого аккаунта на вкладу Keys. Нажимаем Add key -> Create new key -> JSON. JSON файл загрузиться на компьютер.
Этот файлик выглядит примерно так:
{ "type": "service_account", "project_id": "inxbot", "private_key_id": "60c19ce53e578c63a679a300024ae29678053a2a", "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBPiM67EIHEV+p\nnxSn2cfIUUocbeptKzaF+6HWjIer1+ijhdwEPnYXOAJFJoSQlAJkQeYdTHAHrWHo\n/r+M/QV3hnBk/EljkpEHYTezROFETG02x4LR1PWy0JZceRUYs8EKx0Z1IMXO7JLL\n2n3ePagX3QKMhYn3YDVvenMCvTQZswg1sRxQzZdIIu6Zc/8x8wIp7AZraSxBokPU\nfjACOr9OgyO2reft8fSU2rPUFAfhVXkeW+tbbGqasDXao08koV7Ng0B4sO5PHFik\nA6PMgFftXpSAybBiyVg205UauUhQ6GX8tlegfxSMvK4HITcWuLCQF1N1XFKrsihJ\nUqay+AJ5AgMBAAECggEAEmXXldMLgD/Ds1L9dPOThTtUCnvef1FHah8oa9RKUG6I\nbBUg+th1hsVi1fGSWm91ANWTUoPGAL0dnsCuPP6T6CrJBZxNYmkAXMsBMhSjO8zo\n8vewuUiEsecRAbHXc9gdT2jkiN87ZfV5DmZ5aFDHcaothXJrTb1/qOLoE0EvNij+\nJISdNV4VvCKyh44SYRAeUCUx6Tk071QNLapWxURCVuJZxLAZdatVztuxgSyMjqVc\n/u6ZMDdBwrJX7POPZItQZAYjNiz+5ytK/+a3pb3A4sij7cwfkqrEjy9Vnt6K567c\n7aZ77Zf7bMbPAQaEqs7sc1GgSZidt8hT+lpGSIO6WQKBgQD/ROQ+aQEBNYEY+Iza\nh9w2QvZzl3pS6dohLXeq1tSS4Pn7hddhaMniY2gBUC/UdLxaUSHncYneCWZj17wL\nqUxp8tNuYbmFx7SwN0hf4gRVRs9VHliot0ShJehatXIUdQtZGem6ZeBCMiU6Ahl6\nw0DoeSmXmXKMjA/0nqa7V8uEbQKBgQDBy8gZIaiPAbskfwzE+rtEGysSrWbp5YmL\novaAQyIU6ZQ1QcSRuwPyLsK1mQokpVLp/D3tuX5bf7QEywXGZpqQkNAkTIAszfd1\nFu5dZYjD3/U8nh2vXSOhoWLV3cs+MFn/1pOE6okd22RxVszKKPAMGwdFHLjqhTgm\n4PaGX0F2vQKBgQD7QdkTIRHPGdXvOT6THuT/N9wTHMdXczy0KWe/bWOhCvajvzKn\ne5gzUDsgB8h8a0a182PWxme4eDIjFkbOh55iLpQz8tbEOjS8uj1vuy0erusG0i7c\nKj/QClrxZeVbA3XfBuWLBhm9rlP8C/L+Zc+uwwvQX0Zj1r0hv0NH5Xu55QKBgGEy\nuguQzB/duGUXq+nn76C7F6FogyNw0snMVGIEScW/WWxUriiBafs/5Lx8I5A5sxpU\nPyNKU8vEgx+cTCs0LvWvDBYIoIjYTPLXK3qAgFXtr+xVSYI47rQUY6PseBGa4qjk\nkFyr3VWyQfBunowOeEc6Dy1kuII3ivBFCKBgik7pAoGAUnhxLBrSuBh4dlwKgTZp\n9qsnRnVRyrHvXipHpeizZAyvEGn1+5jHHFa6OYgX/7k90hRGmY0v27HcFgKwiqZX\nGJ4LovYF5kYuZ6EYs1kdLP5Ha8f6snMr3N1f9aKawBE5Lg3OhDJeNJAxaOkOcJTR\nUoZfBrgvw6u6H/Kr0KMo/Ws=\n-----END PRIVATE KEY-----\n", "client_email": "ura-ia-na-habre@inxbot.iam.gserviceaccount.com", "client_id": "116600111704528514606", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/ura-ia-na-habre%40inxbot.iam.gserviceaccount.com", "universe_domain": "googleapis.com" }
Далее переходим в свою гугл таблицу -> Настройки доступа -> Вписываем email нашего аккаунта (поле client_email в json) -> Права редактор или читатель в зависимости от ваших целей. За гугл таблицу можно не бояться, гугл автоматически сохраняет все версии таблицы во вкладке Файл -> История версий -> Смотреть историю версий.
2. C++ код
Код пояснять практически не буду, если что-то непонятно chat про всё спокойно расскажет.
Авторизация по протоколу OAuth 2.0 с помощью JWT
Метод для парсинга JSON файла. В SERVICE_ACCOUNT_FILE лежит путь к файлу.
rapidjson::Document loadServiceAccountKey() { std::ifstream file(SERVICE_ACCOUNT_FILE); std::stringstream buffer; buffer << file.rdbuf(); rapidjson::Document doc; doc.Parse(buffer.str().c_str()); return doc; }
Метод для получения jwt токена. jwt::create не будет работать без трэитов, я взял их из репозитория библиотеки на гитхабе. Так же тут очень важная строчка «.set_payload_claim(«scope», jwt::claim(std::string(«https://www.googleapis.com/auth/spreadsheets«)))» — указывает сервис в который мы получаем аутентификацию.
std::string getJwtToken() { rapidjson::Document doc = loadServiceAccountKey(); std::string privateKey = doc["private_key"].GetString(); std::string clientEmail = doc["client_email"].GetString(); auto now = std::chrono::system_clock::now(); auto exp = now + std::chrono::minutes(60); auto rsa = jwt::algorithm::rs256("", privateKey, "", "RS256"); std::string jwt = jwt::create() .set_issuer(clientEmail) .set_audience("https://oauth2.googleapis.com/token") .set_issued_at(now) .set_expires_at(exp) .set_payload_claim("scope", jwt::claim(std::string("https://www.googleapis.com/auth/spreadsheets"))) .set_type("JWT") .set_algorithm("RS256") .sign(rsa); return jwt; }
Пример трэйта. Я использовал этот трейт для библиотеки nlohmann-json. Свой писать на rapid времени не было.
#ifndef JWT_CPP_NLOHMANN_JSON_TRAITS_H #define JWT_CPP_NLOHMANN_JSON_TRAITS_H #include "jwt-cpp/jwt.h" #include "nlohmann/json.hpp" namespace jwt { /** * \brief Namespace containing all the json_trait implementations for a jwt::basic_claim. */ namespace traits { /// basic_claim's JSON trait implementation for Modern C++ JSON struct nlohmann_json { using json = nlohmann::json; using value_type = json; using object_type = json::object_t; using array_type = json::array_t; using string_type = std::string; // current limitation of traits implementation using number_type = json::number_float_t; using integer_type = json::number_integer_t; using boolean_type = json::boolean_t; static jwt::json::type get_type(const json& val) { using jwt::json::type; if (val.type() == json::value_t::boolean) return type::boolean; // nlohmann internally tracks two types of integers if (val.type() == json::value_t::number_integer) return type::integer; if (val.type() == json::value_t::number_unsigned) return type::integer; if (val.type() == json::value_t::number_float) return type::number; if (val.type() == json::value_t::string) return type::string; if (val.type() == json::value_t::array) return type::array; if (val.type() == json::value_t::object) return type::object; throw std::logic_error("invalid type"); } static json::object_t as_object(const json& val) { if (val.type() != json::value_t::object) throw std::bad_cast(); return val.get<json::object_t>(); } static std::string as_string(const json& val) { if (val.type() != json::value_t::string) throw std::bad_cast(); return val.get<std::string>(); } static json::array_t as_array(const json& val) { if (val.type() != json::value_t::array) throw std::bad_cast(); return val.get<json::array_t>(); } static int64_t as_integer(const json& val) { switch (val.type()) { case json::value_t::number_integer: case json::value_t::number_unsigned: return val.get<int64_t>(); default: throw std::bad_cast(); } } static bool as_boolean(const json& val) { if (val.type() != json::value_t::boolean) throw std::bad_cast(); return val.get<bool>(); } static double as_number(const json& val) { if (val.type() != json::value_t::number_float) throw std::bad_cast(); return val.get<double>(); } static bool parse(json& val, std::string str) { val = json::parse(str.begin(), str.end()); return true; } static std::string serialize(const json& val) { return val.dump(); } }; } // namespace traits } // namespace jwt #endif
Метод для получения Acess токена. При написании запросов на curl обращаю внимание на строчку «curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);» логирование очень помогает, если есть какие-то проблемы с запросом. Acess токен нужно всегда получают через post запросы.
std::string getAcessToken() { CURL* curl = curl_easy_init(); //TODO: Возможно нужно как-то обработать этот случай if(!curl) return ""; std::string postData = "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=" + getJwtToken(); std::string readBuffer; curl_easy_setopt(curl, CURLOPT_URL, "https://oauth2.googleapis.com/token"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); curl_easy_setopt(curl, CURLOPT_POST, 1L); //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // для логирования curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { std::cerr << "Ошибка CURL: " << curl_easy_strerror(res) << std::endl; curl_easy_cleanup(curl); return ""; } curl_easy_cleanup(curl); rapidjson::Document doc_res; doc_res.Parse(readBuffer.c_str()); if (!(doc_res.HasMember("access_token") && doc_res["access_token"].IsString())) { std::cerr << "Ошибка: Не найден access_token в ответе!" << std::endl; return ""; } return doc_res["access_token"].GetString(); }
Запись данных.
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* output) { output->append((char*)contents, size * nmemb); return size * nmemb; }
Получение данных из таблицы. Простой пример в этом случае получим данные из D столбца.
void updateDbWithSheets() { std::string accessToken = getAcessToken(); std::string range = "clients!D:D"; std::string url = "https://sheets.googleapis.com/v4/spreadsheets/" + SPREADSHEET_ID + "/values:batchGet?ranges=" + range; // Настройка cURL CURL* curl = curl_easy_init(); if (curl) { curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); struct curl_slist* headers = NULL; headers = curl_slist_append(headers, ("Authorization: Bearer " + accessToken).c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); std::string readBuffer; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { std::cerr << "Ошибка CURL: " << curl_easy_strerror(res) << std::endl; } else { std::cout << "Ответ от Google Sheets API:\n" << readBuffer << std::endl; } curl_easy_cleanup(curl); curl_slist_free_all(headers); } }
Запись данных в таблицу. В гугл таблицы данные отправляются put запросом. Передаете range , если нужно несколько ячеек из разных участков таблицы, спросите у гпт или посмотрите как прописывается в google sheets api. Это не сложно изменить.
void updateSheetsWithDb() { CURL* curl = curl_easy_init(); if(curl) { const std::string accessToken = getAcessToken(); const std::string range = "clients!A3:AD3"; std::string url = "https://sheets.googleapis.com/v4/spreadsheets/" + SPREADSHEET_ID + "/values/" + range + "?valueInputOption=RAW"; const std::string jsonData = R"({"range":"clients!A3:AD3","majorDimension":"ROWS","values":[["01.03.25","Артем Дадерко", "Tg", "id", "90", null, null, null, "80"]]})"; struct curl_slist* headers = NULL; headers = curl_slist_append(headers, ("Authorization: Bearer " + accessToken).c_str()); headers = curl_slist_append(headers, "Content-Type: application/json"); headers = curl_slist_append(headers, "Accept: application/json"); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData.c_str()); //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); CURLcode res = curl_easy_perform(curl); if(res != CURLE_OK) { std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl; } curl_easy_cleanup(curl); curl_slist_free_all(headers); } }
3. Заключение
По сути этих методов хватает чтобы получить и отправить данные без обработки. Этот гайд написал лишь для быстрого старта. Я несколько дней не мог понять в чем моя ошибка при аутентификации и надеюсь с помощью этих наработок вам не придется тратить время на решение таких проблем. Спасибо.
ссылка на оригинал статьи https://habr.com/ru/articles/902538/
Добавить комментарий