Google sheets with C++

от автора

Привет, пишу тг бота на 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *