A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at records-reader 201 lines 5.8 kB view raw
1#include "KOReaderSyncClient.h" 2 3#include <ArduinoJson.h> 4#include <HTTPClient.h> 5#include <Logging.h> 6#include <WiFi.h> 7#include <WiFiClientSecure.h> 8 9#include <ctime> 10 11#include "KOReaderCredentialStore.h" 12 13namespace { 14// Device identifier for CrossPoint reader 15constexpr char DEVICE_NAME[] = "CrossPoint"; 16constexpr char DEVICE_ID[] = "crosspoint-reader"; 17 18void addAuthHeaders(HTTPClient& http) { 19 http.addHeader("Accept", "application/vnd.koreader.v1+json"); 20 http.addHeader("x-auth-user", KOREADER_STORE.getUsername().c_str()); 21 http.addHeader("x-auth-key", KOREADER_STORE.getMd5Password().c_str()); 22 23 // HTTP Basic Auth (RFC 7617) header. This is needed to support koreader sync server embedded in Calibre Web Automated 24 // (https://github.com/crocodilestick/Calibre-Web-Automated/blob/main/cps/progress_syncing/protocols/kosync.py) 25 http.setAuthorization(KOREADER_STORE.getUsername().c_str(), KOREADER_STORE.getPassword().c_str()); 26} 27 28bool isHttpsUrl(const std::string& url) { return url.rfind("https://", 0) == 0; } 29} // namespace 30 31KOReaderSyncClient::Error KOReaderSyncClient::authenticate() { 32 if (!KOREADER_STORE.hasCredentials()) { 33 LOG_DBG("KOSync", "No credentials configured"); 34 return NO_CREDENTIALS; 35 } 36 37 std::string url = KOREADER_STORE.getBaseUrl() + "/users/auth"; 38 LOG_DBG("KOSync", "Authenticating: %s", url.c_str()); 39 40 HTTPClient http; 41 std::unique_ptr<WiFiClientSecure> secureClient; 42 WiFiClient plainClient; 43 44 if (isHttpsUrl(url)) { 45 secureClient.reset(new WiFiClientSecure); 46 secureClient->setInsecure(); 47 http.begin(*secureClient, url.c_str()); 48 } else { 49 http.begin(plainClient, url.c_str()); 50 } 51 addAuthHeaders(http); 52 53 const int httpCode = http.GET(); 54 http.end(); 55 56 LOG_DBG("KOSync", "Auth response: %d", httpCode); 57 58 if (httpCode == 200) { 59 return OK; 60 } else if (httpCode == 401) { 61 return AUTH_FAILED; 62 } else if (httpCode < 0) { 63 return NETWORK_ERROR; 64 } 65 return SERVER_ERROR; 66} 67 68KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& documentHash, 69 KOReaderProgress& outProgress) { 70 if (!KOREADER_STORE.hasCredentials()) { 71 LOG_DBG("KOSync", "No credentials configured"); 72 return NO_CREDENTIALS; 73 } 74 75 std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress/" + documentHash; 76 LOG_DBG("KOSync", "Getting progress: %s", url.c_str()); 77 78 HTTPClient http; 79 std::unique_ptr<WiFiClientSecure> secureClient; 80 WiFiClient plainClient; 81 82 if (isHttpsUrl(url)) { 83 secureClient.reset(new WiFiClientSecure); 84 secureClient->setInsecure(); 85 http.begin(*secureClient, url.c_str()); 86 } else { 87 http.begin(plainClient, url.c_str()); 88 } 89 addAuthHeaders(http); 90 91 const int httpCode = http.GET(); 92 93 if (httpCode == 200) { 94 // Parse JSON response from response string 95 String responseBody = http.getString(); 96 http.end(); 97 98 JsonDocument doc; 99 const DeserializationError error = deserializeJson(doc, responseBody); 100 101 if (error) { 102 LOG_ERR("KOSync", "JSON parse failed: %s", error.c_str()); 103 return JSON_ERROR; 104 } 105 106 outProgress.document = documentHash; 107 outProgress.progress = doc["progress"].as<std::string>(); 108 outProgress.percentage = doc["percentage"].as<float>(); 109 outProgress.device = doc["device"].as<std::string>(); 110 outProgress.deviceId = doc["device_id"].as<std::string>(); 111 outProgress.timestamp = doc["timestamp"].as<int64_t>(); 112 113 LOG_DBG("KOSync", "Got progress: %.2f%% at %s", outProgress.percentage * 100, outProgress.progress.c_str()); 114 return OK; 115 } 116 117 http.end(); 118 119 LOG_DBG("KOSync", "Get progress response: %d", httpCode); 120 121 if (httpCode == 401) { 122 return AUTH_FAILED; 123 } else if (httpCode == 404) { 124 return NOT_FOUND; 125 } else if (httpCode < 0) { 126 return NETWORK_ERROR; 127 } 128 return SERVER_ERROR; 129} 130 131KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgress& progress) { 132 if (!KOREADER_STORE.hasCredentials()) { 133 LOG_DBG("KOSync", "No credentials configured"); 134 return NO_CREDENTIALS; 135 } 136 137 std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress"; 138 LOG_DBG("KOSync", "Updating progress: %s", url.c_str()); 139 140 HTTPClient http; 141 std::unique_ptr<WiFiClientSecure> secureClient; 142 WiFiClient plainClient; 143 144 if (isHttpsUrl(url)) { 145 secureClient.reset(new WiFiClientSecure); 146 secureClient->setInsecure(); 147 http.begin(*secureClient, url.c_str()); 148 } else { 149 http.begin(plainClient, url.c_str()); 150 } 151 addAuthHeaders(http); 152 http.addHeader("Content-Type", "application/json"); 153 154 // Build JSON body (timestamp not required per API spec) 155 JsonDocument doc; 156 doc["document"] = progress.document; 157 doc["progress"] = progress.progress; 158 doc["percentage"] = progress.percentage; 159 doc["device"] = DEVICE_NAME; 160 doc["device_id"] = DEVICE_ID; 161 162 std::string body; 163 serializeJson(doc, body); 164 165 LOG_DBG("KOSync", "Request body: %s", body.c_str()); 166 167 const int httpCode = http.PUT(body.c_str()); 168 http.end(); 169 170 LOG_DBG("KOSync", "Update progress response: %d", httpCode); 171 172 if (httpCode == 200 || httpCode == 202) { 173 return OK; 174 } else if (httpCode == 401) { 175 return AUTH_FAILED; 176 } else if (httpCode < 0) { 177 return NETWORK_ERROR; 178 } 179 return SERVER_ERROR; 180} 181 182const char* KOReaderSyncClient::errorString(Error error) { 183 switch (error) { 184 case OK: 185 return "Success"; 186 case NO_CREDENTIALS: 187 return "No credentials configured"; 188 case NETWORK_ERROR: 189 return "Network error"; 190 case AUTH_FAILED: 191 return "Authentication failed"; 192 case SERVER_ERROR: 193 return "Server error (try again later)"; 194 case JSON_ERROR: 195 return "JSON parse error"; 196 case NOT_FOUND: 197 return "No progress found"; 198 default: 199 return "Unknown error"; 200 } 201}