Created
April 20, 2026 11:28
-
-
Save sunmeat/79f909ac344a6219b4dc1695d6b9e77b to your computer and use it in GitHub Desktop.
C++ IMAP example MacOS version
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <iostream> | |
| #include <fstream> | |
| #include <string> | |
| #include <curl/curl.h> | |
| #include <vector> | |
| #include <sstream> | |
| #include <regex> | |
| using namespace std; | |
| // ====================== ANSI COLORS for macOS / Linux ====================== | |
| enum Color { | |
| CGRAY = 90, | |
| CCYAN = 36, | |
| CGREEN = 32, | |
| CYELLOW = 33, | |
| CWHITE = 37, | |
| CBLUE = 34, | |
| CRED = 31 | |
| }; | |
| void set_color(int c) { | |
| cout << "\033[" << c << "m"; | |
| } | |
| void reset_color() { | |
| cout << "\033[0m"; | |
| } | |
| // ====================== DECODING FUNCTIONS | |
| string base64_decode(const string& in) { | |
| string out; | |
| vector<int> T(256, -1); | |
| for (int i = 0; i < 64; i++) | |
| T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; | |
| int val = 0, valb = -8; | |
| for (unsigned char c : in) { | |
| if (T[c] == -1) break; | |
| val = (val << 6) + T[c]; | |
| valb += 6; | |
| if (valb >= 0) { | |
| out.push_back(char((val >> valb) & 0xFF)); | |
| valb -= 8; | |
| } | |
| } | |
| return out; | |
| } | |
| string decode_qp(const string& input) { | |
| string result; | |
| for (size_t i = 0; i < input.size(); ++i) { | |
| if (input[i] == '=' && i + 2 < input.size() && | |
| isxdigit(static_cast<unsigned char>(input[i + 1])) && | |
| isxdigit(static_cast<unsigned char>(input[i + 2]))) { | |
| result += static_cast<char>(stoi(input.substr(i + 1, 2), nullptr, 16)); | |
| i += 2; | |
| } | |
| else if (input[i] == '=' && i + 1 < input.size() && (input[i + 1] == '\r' || input[i + 1] == '\n')) { | |
| if (input[i + 1] == '\r' && i + 2 < input.size() && input[i + 2] == '\n') i += 2; | |
| else i++; | |
| } | |
| else { | |
| result += input[i]; | |
| } | |
| } | |
| return result; | |
| } | |
| string decode_mime_header(string h) { | |
| string out; | |
| size_t pos = 0; | |
| while ((pos = h.find("=?UTF-8?B?")) != string::npos) { | |
| out += h.substr(0, pos); | |
| size_t e = h.find("?=", pos); | |
| if (e == string::npos) break; | |
| out += base64_decode(h.substr(pos + 10, e - pos - 10)); | |
| h = h.substr(e + 2); | |
| } | |
| while ((pos = out.find("=?UTF-8?Q?")) != string::npos) { | |
| string before = out.substr(0, pos); | |
| size_t e = out.find("?=", pos); | |
| if (e == string::npos) break; | |
| string qp = out.substr(pos + 10, e - pos - 10); | |
| for (char& c : qp) if (c == '_') c = ' '; | |
| before += decode_qp(qp); | |
| out = before + out.substr(e + 2); | |
| } | |
| out += h; | |
| return out; | |
| } | |
| string extract_plain(const string& raw) { | |
| if (raw.empty()) return ""; | |
| size_t alt_pos = raw.find("multipart/alternative"); | |
| if (alt_pos == string::npos) return ""; | |
| size_t b_pos = raw.find("boundary=\"", alt_pos); | |
| if (b_pos == string::npos) return ""; | |
| b_pos += 10; | |
| string boundary = raw.substr(b_pos, raw.find('"', b_pos) - b_pos); | |
| string delim = "--" + boundary; | |
| size_t pos = 0; | |
| while ((pos = raw.find(delim, pos)) != string::npos) { | |
| pos += delim.length(); | |
| if (pos + 2 < raw.size() && raw[pos] == '-' && raw[pos + 1] == '-') break; | |
| if (raw[pos] == '\r') pos++; | |
| if (raw[pos] == '\n') pos++; | |
| size_t next = raw.find(delim, pos); | |
| if (next == string::npos) break; | |
| string part = raw.substr(pos, next - pos); | |
| if (part.find("text/plain") != string::npos) { | |
| size_t body_start = part.find("\r\n\r\n"); | |
| if (body_start == string::npos) body_start = part.find("\n\n"); | |
| if (body_start != string::npos) { | |
| body_start += (part[body_start] == '\r' ? 4 : 2); | |
| string body = part.substr(body_start); | |
| if (part.find("quoted-printable") != string::npos) | |
| body = decode_qp(body); | |
| return body; | |
| } | |
| } | |
| } | |
| return ""; | |
| } | |
| string clean_and_replace_links(const string& input) { | |
| string s = input; | |
| s = regex_replace(s, regex(R"(\r)"), ""); | |
| regex url_re(R"((https?://[^\s>\]\)]+))"); | |
| string result; | |
| auto it = sregex_iterator(s.begin(), s.end(), url_re); | |
| auto end_it = sregex_iterator(); | |
| size_t last_pos = 0; | |
| for (; it != end_it; ++it) { | |
| const smatch& m = *it; | |
| result += s.substr(last_pos, m.position() - last_pos); | |
| string url = m.str(1); | |
| if (url.length() > 50) { | |
| result += "[посилання: " + url.substr(0, 47) + "...]"; | |
| } else { | |
| result += "[посилання: " + url + "]"; | |
| } | |
| last_pos = m.position() + m.length(); | |
| } | |
| result += s.substr(last_pos); | |
| s = regex_replace(result, regex("(\n){3,}"), "\n\n"); | |
| return s; | |
| } | |
| // ====================== UI FUNCTIONS ====================== | |
| void print_line() { | |
| set_color(CGRAY); | |
| cout << " ==========================================\n"; | |
| reset_color(); | |
| } | |
| void print_header_ui() { | |
| cout << "\n"; | |
| print_line(); | |
| set_color(CCYAN); | |
| cout << " >> Email Reader v1.5 (100 листів) — macOS версія\n"; | |
| reset_color(); | |
| print_line(); | |
| cout << "\n"; | |
| } | |
| void print_field(const string& label, const string& value, int vc = CWHITE) { | |
| set_color(CYELLOW); cout << label; | |
| set_color(vc); cout << value << "\n"; | |
| reset_color(); | |
| } | |
| void print_body(const string& body) { | |
| cout << "\n"; | |
| set_color(CCYAN); cout << " тіло листа:\n\n"; | |
| reset_color(); | |
| istringstream iss(body); | |
| string line; | |
| while (getline(iss, line)) { | |
| if (line.empty()) { | |
| cout << "\n"; | |
| continue; | |
| } | |
| set_color((line.find("[посилання:") != string::npos) ? CBLUE : CWHITE); | |
| cout << " " << line << "\n"; | |
| reset_color(); | |
| } | |
| } | |
| void print_meta(size_t bytes, int msg_num) { | |
| cout << "\n"; | |
| print_line(); | |
| set_color(CGRAY); | |
| cout << " лист #" << msg_num << " | розмір: " << bytes << " байт\n"; | |
| reset_color(); | |
| print_line(); | |
| cout << "\n"; | |
| } | |
| // ====================== CURL CALLBACK ====================== | |
| static size_t write_callback(void* ptr, size_t size, size_t nmemb, void* data) { | |
| ((string*)data)->append((char*)ptr, size * nmemb); | |
| return size * nmemb; | |
| } | |
| // ====================== MAIN ====================== | |
| int main() { | |
| const string email = "your_email@gmail.com"; | |
| const string password = "**** **** **** ****"; // ваш пароль додатка !!! https://support.google.com/accounts/answer/185833?dark=1&sjid=3195842429112725011-EU | |
| CURL* curl = curl_easy_init(); | |
| if (!curl) { | |
| cerr << "помилка ініціалізації curl\n"; | |
| return 1; | |
| } | |
| string response; | |
| curl_easy_setopt(curl, CURLOPT_URL, "imaps://imap.gmail.com/INBOX"); | |
| curl_easy_setopt(curl, CURLOPT_USERNAME, email.c_str()); | |
| curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str()); | |
| curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); | |
| curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); | |
| curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL); | |
| curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L); | |
| // SELECT INBOX | |
| curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "SELECT INBOX"); | |
| curl_easy_perform(curl); | |
| response.clear(); | |
| print_header_ui(); | |
| for (int i = 1; i <= 100; ++i) { | |
| string fetch_cmd = "FETCH " + to_string(i) + " (BODY[HEADER.FIELDS (FROM TO SUBJECT)] BODY[])"; | |
| curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, fetch_cmd.c_str()); | |
| response.clear(); | |
| CURLcode res = curl_easy_perform(curl); | |
| if (res != CURLE_OK || response.empty()) { | |
| cout << "помилка при завантаженні листа #" << i << "\n"; | |
| continue; | |
| } | |
| // дебаг останнього листа | |
| if (i == 100) { | |
| ofstream("debug_response.txt") << response; | |
| } | |
| // парсинг заголовків | |
| auto find_header = [&](const string& name) -> string { | |
| size_t p = response.find(name + ":"); | |
| if (p == string::npos) return ""; | |
| size_t start = p + name.size() + 1; | |
| size_t end = response.find('\n', start); | |
| string val = response.substr(start, end - start); | |
| if (!val.empty() && val.back() == '\r') val.pop_back(); | |
| return val; | |
| }; | |
| string from = find_header("From"); | |
| string to = find_header("To"); | |
| string subject = find_header("Subject"); | |
| // витягуємо BODY[] | |
| size_t body_tag = response.find("BODY[] {"); | |
| if (body_tag == string::npos) continue; | |
| size_t open_br = response.find('{', body_tag); | |
| size_t close_br = response.find('}', open_br); | |
| size_t body_size = stoul(response.substr(open_br + 1, close_br - open_br - 1)); | |
| size_t body_start = response.find('\n', close_br) + 1; | |
| string full_body = response.substr(body_start, body_size); | |
| string text = extract_plain(full_body); | |
| if (!text.empty()) { | |
| text = clean_and_replace_links(text); | |
| set_color(CCYAN); | |
| cout << "=== лист #" << i << " ===\n"; | |
| reset_color(); | |
| print_field(" від: ", decode_mime_header(from)); | |
| print_field(" кому: ", decode_mime_header(to)); | |
| print_field(" тема: ", decode_mime_header(subject), CGREEN); | |
| print_body(text); | |
| print_meta(full_body.size(), i); | |
| } | |
| } | |
| curl_easy_cleanup(curl); | |
| cout << "\nготово! натисніть будь-яку клавішу...\n"; | |
| cin.get(); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment