Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Created April 20, 2026 11:28
Show Gist options
  • Select an option

  • Save sunmeat/79f909ac344a6219b4dc1695d6b9e77b to your computer and use it in GitHub Desktop.

Select an option

Save sunmeat/79f909ac344a6219b4dc1695d6b9e77b to your computer and use it in GitHub Desktop.
C++ IMAP example MacOS version
#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