#include "vk.h" #include "curl/curl.h" #include "http.h" #include "spdlog/sinks/stdout_color_sinks.h" #include #include #include #include using namespace vk; using namespace nlohmann; using std::chrono::duration_cast, std::chrono::milliseconds, std::chrono::steady_clock; const char *API_BASE_URL = "https://api.vk.com/method/"; const char *API_VERSION = "5.199"; const char *LOGGER_TAG = "vk"; const long API_CALL_INTERVAL = 250; VKClient::VKClient(uv_loop_t *eventLoop) : m_eventLoop(eventLoop), m_httpClient(eventLoop) { m_logger = spdlog::get(LOGGER_TAG); if (!m_logger) { m_logger = spdlog::stdout_color_mt(LOGGER_TAG); m_logger->set_level(spdlog::level::debug); } }; VKClient::VKClient(uv_loop_t *eventLoop, std::string serviceKey) : VKClient(eventLoop) { set_service_api_key(serviceKey); } void VKClient::set_service_api_key(std::string key) { m_serviceApiKey = {key}; } void VKClient::get_posts(std::variant wall, int offset, int count, std::function, int)> callback) { long delay = API_CALL_INTERVAL - duration_cast(steady_clock::now() - m_lastCallTime).count(); if (delay <= 0) { get_posts_now(wall, offset, count, callback); } else { m_logger->debug("delaying request by {} ms", delay); struct params { VKClient *self; std::variant wall; int offset, count; std::function, int)> callback; }; uv_timer_t *timer = new uv_timer_t; params *p = new params{ this, wall, offset, count, callback }; timer->data = p; uv_timer_init(m_eventLoop, timer); uv_timer_start(timer, [](uv_timer_t *h){ uv_timer_stop(h); uv_close((uv_handle_t*)h, [](uv_handle_t *h){ delete h; }); params *p = (params*)h->data; p->self->get_posts(p->wall, p->offset, p->count, p->callback); delete p; }, delay, 0); } } void VKClient::get_posts_now(std::variant wall, int offset, int count, std::function, int)> callback) { if (!m_serviceApiKey) { m_logger->error("get_posts called without authorization"); return; } m_lastCallTime = std::chrono::steady_clock::now(); std::string url(API_BASE_URL); url += "wall.get?access_token="; url += *m_serviceApiKey; url += "&v="; url += API_VERSION; url += "&offset="; url += std::to_string(offset); url += "&count="; // + 1 here to handle the pinned post int increasedCount = count < std::numeric_limits::max() ? count + 1 : count; url += std::to_string(increasedCount); if (wall.index() == 0) { url += "&owner_id="; url += std::to_string(std::get(wall)); } else { url += "&domain="; url += std::get(wall); } m_logger->debug("using URL: {}", url); m_httpClient.send_request("GET", url, {}, [this, callback, requestedCount = count](std::unique_ptr resp, CURLcode r){ if (r == 0) { auto parsedResponse = json::parse(resp->body); if (parsedResponse.contains("error")) { auto err = parsedResponse["error"]; m_logger->error("get_posts error {} {}", (int)err["error_code"], (std::string)err["error_msg"]); callback({}, -1); return; } auto responsePayload = parsedResponse["response"]; int count = responsePayload["count"]; std::vector posts; int checkedPosts = 0; for (auto post : responsePayload["items"]) { if (checkedPosts == requestedCount) continue; ++checkedPosts; if (post.contains("is_pinned") && post["is_pinned"] == 1) continue; if (!post.contains("id") || !post.contains("date") || !post.contains("from_id") || !post.contains("type") || !post.contains("text")) { m_logger->warn("strange post: {}", post.dump()); continue; } posts.emplace_back(post["id"], post["date"], post.contains("edited") ? post["edited"].get() : 0, post["from_id"], post["type"], post["text"]); } callback({{count, std::move(posts)}}, 0); } else { m_logger->error("get_posts network error"); callback({}, r); } }); }