emailQt: Initial commit
This commit is contained in:
commit
92158f9f84
32
CMakeLists.txt
Normal file
32
CMakeLists.txt
Normal file
@ -0,0 +1,32 @@
|
||||
cmake_minimum_required(VERSION 3.27)
|
||||
project(emailQt)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG -fsanitize=address,undefined")
|
||||
set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=address,undefined")
|
||||
|
||||
find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED)
|
||||
find_package(Qt6Keychain CONFIG REQUIRED)
|
||||
|
||||
add_executable(emailQt main.cpp
|
||||
EmailClient.hpp
|
||||
EmailClient.cpp
|
||||
CurlHandler.hpp
|
||||
CurlHandler.cpp
|
||||
ParametersProvider.cpp
|
||||
ParametersProvider.hpp
|
||||
Email.cpp
|
||||
Email.hpp
|
||||
EmailDetails.cpp
|
||||
EmailDetails.hpp)
|
||||
|
||||
target_link_libraries(emailQt PRIVATE Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Widgets
|
||||
curl
|
||||
Qt6Keychain::Qt6Keychain)
|
101
CurlHandler.cpp
Executable file
101
CurlHandler.cpp
Executable file
@ -0,0 +1,101 @@
|
||||
#include "CurlHandler.hpp"
|
||||
|
||||
CurlHandler::CurlHandler()
|
||||
: initialRes{}, initialCurl(curl_easy_init(), curl_easy_cleanup) {
|
||||
}
|
||||
|
||||
CurlHandler::~CurlHandler() {
|
||||
for (auto& t : v)
|
||||
t.join();
|
||||
}
|
||||
|
||||
void CurlHandler::configure(const ParametersProvider::settings &sett) {
|
||||
url.append("imaps://");
|
||||
url += sett.imapS;
|
||||
url += "/INBOX?ALL";
|
||||
setts = sett;
|
||||
|
||||
setupCurl(initialCurl, initialChunk, url);
|
||||
}
|
||||
|
||||
std::vector<std::string> *CurlHandler::fetch() {
|
||||
initialRes = curl_easy_perform(initialCurl.get());
|
||||
if (initialRes != CURLE_OK) {
|
||||
return &decodedMessages;
|
||||
}
|
||||
|
||||
// We prepare to query the server.
|
||||
url.erase(url.end() - 4, url.end());
|
||||
url += "/;MAILINDEX=";
|
||||
|
||||
std::string temp;
|
||||
std::stringstream s(initialChunk);
|
||||
|
||||
size_t count = 0;
|
||||
while (getline(s, temp, ' ')) {
|
||||
// SELECT and *
|
||||
if (count < 2) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
messagesId.emplace_back(temp);
|
||||
}
|
||||
|
||||
// We will query the server number by number.
|
||||
// The messagesId vector contains all Email GUIDs.
|
||||
v.reserve(messagesId.size());
|
||||
decodedMessages.resize(messagesId.size());
|
||||
count = 0;
|
||||
|
||||
for (auto i{messagesId.rbegin()}; i != messagesId.rbegin() + 5; i++)
|
||||
v.emplace_back(&CurlHandler::query, this, std::ref(*i), count++);
|
||||
|
||||
return &decodedMessages;
|
||||
}
|
||||
|
||||
void CurlHandler::query(std::string &c, size_t count) {
|
||||
std::shared_ptr<CURL> localCurl(curl_easy_init(), curl_easy_cleanup);
|
||||
CURLcode localCode;
|
||||
c.erase(std::remove(c.begin(), c.end(), '\r'), c.end());
|
||||
c.erase(std::remove(c.begin(), c.end(), '\n'), c.end());
|
||||
setupCurl(localCurl, decodedMessages[count], url + c);
|
||||
|
||||
localCode = curl_easy_perform(localCurl.get());
|
||||
if (localCode != CURLE_OK) {
|
||||
// ...
|
||||
}
|
||||
|
||||
emit threadFinished(count, std::stoi(c));
|
||||
}
|
||||
|
||||
void CurlHandler::setupCurl(std::shared_ptr<CURL> ptr, std::string &m, std::string const &ur) const {
|
||||
curl_easy_setopt(ptr.get(), CURLOPT_USERNAME, setts.userS.c_str());
|
||||
curl_easy_setopt(ptr.get(), CURLOPT_PASSWORD, setts.passS.c_str());
|
||||
|
||||
curl_easy_setopt(ptr.get(), CURLOPT_PORT, setts.port);
|
||||
curl_easy_setopt(ptr.get(), CURLOPT_TIMEOUT, curlTimeoutSeconds);
|
||||
|
||||
// The callback function will receive the string ref as its userdata
|
||||
// and therefore save the data there.
|
||||
curl_easy_setopt(ptr.get(), CURLOPT_USE_SSL, (long) CURLUSESSL_ALL);
|
||||
curl_easy_setopt(ptr.get(), CURLOPT_WRITEDATA, (void *) &m);
|
||||
curl_easy_setopt(ptr.get(), CURLOPT_WRITEFUNCTION, cb);
|
||||
|
||||
// Necessary on some servers
|
||||
curl_easy_setopt(ptr.get(), CURLOPT_USERAGENT, "libcurl-agent/1.0");
|
||||
|
||||
curl_easy_setopt(ptr.get(), CURLOPT_URL, ur.c_str());
|
||||
|
||||
#ifdef DEBUG
|
||||
curl_easy_setopt(ptr.get(), CURLOPT_VERBOSE, 1L);
|
||||
#else
|
||||
curl_easy_setopt(ptr.get(), CURLOPT_VERBOSE, 0L);
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t cb(char *data, size_t size, size_t numberOfMembers, void *userdata) {
|
||||
size_t realSize = size * numberOfMembers;
|
||||
auto *mem = (std::string *) userdata;
|
||||
mem->append(data, realSize);
|
||||
return realSize;
|
||||
}
|
49
CurlHandler.hpp
Executable file
49
CurlHandler.hpp
Executable file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "ParametersProvider.hpp"
|
||||
|
||||
constexpr long curlTimeoutSeconds{5};
|
||||
|
||||
// View RFC 3501 and RFC 2192 for more details
|
||||
|
||||
size_t cb(char *data, size_t size, size_t numberOfMembers, void *userdata);
|
||||
|
||||
class CurlHandler : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CurlHandler();
|
||||
~CurlHandler();
|
||||
|
||||
void configure(const ParametersProvider::settings &sett);
|
||||
std::vector<std::string> *fetch();
|
||||
|
||||
void query(std::string &c, size_t count);
|
||||
void setupCurl(std::shared_ptr<CURL> ptr, std::string &m, const std::string &ur) const;
|
||||
|
||||
signals:
|
||||
void threadFinished(size_t threadId, int uid);
|
||||
|
||||
private:
|
||||
std::string url;
|
||||
std::shared_ptr<CURL> initialCurl;
|
||||
CURLcode initialRes;
|
||||
std::string initialChunk;
|
||||
|
||||
std::vector<std::string> decodedMessages;
|
||||
std::vector<std::string> messagesId;
|
||||
std::vector<std::thread> v;
|
||||
|
||||
ParametersProvider::settings setts;
|
||||
};
|
47
Email.cpp
Normal file
47
Email.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include "Email.hpp"
|
||||
|
||||
Email::Email(const std::string &str, size_t uid)
|
||||
: QListWidgetItem(nullptr, QListWidgetItem::UserType), uid{uid} {
|
||||
setString(str);
|
||||
}
|
||||
|
||||
Email::Email(Email const &e)
|
||||
: QListWidgetItem(e), uid{e.uid}, titleS(e.titleS), messageS(e.messageS) {
|
||||
setString(titleS);
|
||||
}
|
||||
|
||||
void Email::setString(const std::string &str) {
|
||||
std::stringstream ss(str);
|
||||
|
||||
std::string line;
|
||||
while (std::getline(ss, line)) {
|
||||
if (line.starts_with("Subject:"))
|
||||
titleS = line.substr(9);
|
||||
else if (line.starts_with("Content-Type:"))
|
||||
parseEmailBody(ss, line);
|
||||
}
|
||||
|
||||
setText(QString::fromStdString(titleS));
|
||||
}
|
||||
|
||||
const std::string &Email::title() const {
|
||||
return titleS;
|
||||
}
|
||||
|
||||
const std::string &Email::message() const {
|
||||
return messageS;
|
||||
}
|
||||
|
||||
bool Email::operator<(QListWidgetItem const &other) const {
|
||||
auto a{dynamic_cast<const Email &>(other)};
|
||||
return this->uid < a.uid;
|
||||
}
|
||||
|
||||
void Email::parseEmailBody(std::stringstream &ss, std::string &line) {
|
||||
if (!line.contains("text/plain"))
|
||||
return;
|
||||
|
||||
while (std::getline(ss, line) && !(line.starts_with("--"))) {
|
||||
messageS.append(line);
|
||||
}
|
||||
}
|
28
Email.hpp
Normal file
28
Email.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QListWidgetItem>
|
||||
#include <QString>
|
||||
|
||||
class Email : public QListWidgetItem {
|
||||
public:
|
||||
explicit Email(const std::string &str, size_t uid);
|
||||
Email(const Email &e);
|
||||
~Email() = default;
|
||||
|
||||
void setString(const std::string &str);
|
||||
[[nodiscard]] const std::string &title() const;
|
||||
[[nodiscard]] const std::string &message() const;
|
||||
|
||||
bool operator<(const QListWidgetItem &other) const;
|
||||
|
||||
void parseEmailBody(std::stringstream &ss, std::string &line);
|
||||
|
||||
private:
|
||||
std::string titleS;
|
||||
std::string messageS;
|
||||
size_t uid;
|
||||
};
|
44
EmailClient.cpp
Executable file
44
EmailClient.cpp
Executable file
@ -0,0 +1,44 @@
|
||||
#include "EmailClient.hpp"
|
||||
|
||||
EmailClient::EmailClient(QWidget *parent)
|
||||
: QMainWindow(parent) {
|
||||
connect(&provider, &ParametersProvider::done, this, &EmailClient::parametersDone);
|
||||
|
||||
auto *layout{new QVBoxLayout};
|
||||
response = new QListWidget;
|
||||
|
||||
layout->addWidget(response);
|
||||
|
||||
auto *centralWidget{new QWidget(this)};
|
||||
centralWidget->setLayout(layout);
|
||||
setCentralWidget(centralWidget);
|
||||
|
||||
setWindowTitle("emailQt");
|
||||
|
||||
connect(response, &QListWidget::itemActivated, this, &EmailClient::itemActivated);
|
||||
connect(&handler, &CurlHandler::threadFinished, this, &EmailClient::updateList);
|
||||
}
|
||||
|
||||
void EmailClient::start() {
|
||||
provider.show();
|
||||
provider.setFocusInternal();
|
||||
}
|
||||
|
||||
void EmailClient::parametersDone() {
|
||||
handler.configure(provider.getSettings());
|
||||
|
||||
emailsString = handler.fetch();
|
||||
|
||||
showMaximized();
|
||||
}
|
||||
|
||||
void EmailClient::itemActivated(QListWidgetItem *item) {
|
||||
auto *p = dynamic_cast<Email *>(item);
|
||||
det.setMail(*p);
|
||||
det.show();
|
||||
}
|
||||
|
||||
void EmailClient::updateList(size_t threadId, int uid) {
|
||||
response->addItem(new Email((*emailsString)[threadId], uid));
|
||||
response->sortItems(Qt::DescendingOrder);
|
||||
}
|
43
EmailClient.hpp
Executable file
43
EmailClient.hpp
Executable file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QListWidget>
|
||||
#include <QList>
|
||||
#include <QMainWindow>
|
||||
#include <QString>
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "CurlHandler.hpp"
|
||||
#include "Email.hpp"
|
||||
#include "EmailDetails.hpp"
|
||||
#include "ParametersProvider.hpp"
|
||||
|
||||
class EmailClient : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EmailClient(QWidget *parent = nullptr);
|
||||
~EmailClient() = default;
|
||||
|
||||
void start();
|
||||
|
||||
public slots:
|
||||
void parametersDone();
|
||||
void itemActivated(QListWidgetItem *item);
|
||||
void updateList(size_t threadId, int uid);
|
||||
|
||||
private:
|
||||
ParametersProvider provider;
|
||||
CurlHandler handler;
|
||||
QListWidget *response;
|
||||
|
||||
std::vector<std::string> *emailsString{};
|
||||
QList<QString> emailTitles;
|
||||
EmailDetails det;
|
||||
};
|
18
EmailDetails.cpp
Normal file
18
EmailDetails.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
#include "EmailDetails.hpp"
|
||||
|
||||
EmailDetails::EmailDetails(QWidget *parent)
|
||||
: QWidget(parent) {
|
||||
t = new QTextEdit;
|
||||
|
||||
auto *lay = new QVBoxLayout;
|
||||
|
||||
lay->addWidget(t);
|
||||
setLayout(lay);
|
||||
|
||||
resize(800, 600);
|
||||
}
|
||||
|
||||
void EmailDetails::setMail(const Email &m) {
|
||||
setWindowTitle(QString::fromStdString(m.title()));
|
||||
t->setPlainText(QString::fromStdString(m.message()));
|
||||
}
|
19
EmailDetails.hpp
Normal file
19
EmailDetails.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "Email.hpp"
|
||||
|
||||
class EmailDetails : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EmailDetails(QWidget *parent = nullptr);
|
||||
|
||||
void setMail(const Email &m);
|
||||
|
||||
private:
|
||||
QTextEdit *t;
|
||||
};
|
57
ParametersProvider.cpp
Normal file
57
ParametersProvider.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
#include "ParametersProvider.hpp"
|
||||
|
||||
ParametersProvider::ParametersProvider(QWidget *parent)
|
||||
: QDialog(parent) {
|
||||
auto *layout = new QVBoxLayout;
|
||||
|
||||
auto *user = new QLabel("Nom d'utilisateur", this);
|
||||
userField = new QLineEdit;
|
||||
|
||||
auto *pass = new QLabel("Mot de passe", this);
|
||||
passField = new QLineEdit;
|
||||
passField->setEchoMode(QLineEdit::Password);
|
||||
|
||||
auto *imap = new QLabel("Serveur IMAP", this);
|
||||
imapField = new QLineEdit("imap.gmail.com");
|
||||
|
||||
auto *port = new QLabel("Port", this);
|
||||
portField = new QLineEdit("993");
|
||||
portField->setValidator(new QIntValidator(1, 1000, this));
|
||||
|
||||
auto *startButton = new QPushButton("OK", this);
|
||||
|
||||
layout->addWidget(user);
|
||||
layout->addWidget(userField);
|
||||
layout->addWidget(pass);
|
||||
layout->addWidget(passField);
|
||||
layout->addWidget(imap);
|
||||
layout->addWidget(imapField);
|
||||
layout->addWidget(port);
|
||||
layout->addWidget(portField);
|
||||
layout->addStretch(1);
|
||||
layout->addWidget(startButton);
|
||||
|
||||
this->setLayout(layout);
|
||||
|
||||
resize(300, 450);
|
||||
|
||||
connect(startButton, &QPushButton::clicked, this, &ParametersProvider::start);
|
||||
}
|
||||
|
||||
void ParametersProvider::start() {
|
||||
sett.port = portField->text().toInt();
|
||||
sett.userS = userField->text().toStdString();
|
||||
sett.passS = passField->text().toStdString();
|
||||
sett.imapS = imapField->text().toStdString();
|
||||
close();
|
||||
emit done();
|
||||
}
|
||||
|
||||
void ParametersProvider::setFocusInternal() {
|
||||
activateWindow();
|
||||
userField->setFocus();
|
||||
}
|
||||
|
||||
const ParametersProvider::settings &ParametersProvider::getSettings() {
|
||||
return sett;
|
||||
}
|
32
ParametersProvider.hpp
Normal file
32
ParametersProvider.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QIntValidator>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
class ParametersProvider : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct settings {
|
||||
std::string userS, passS, imapS;
|
||||
int port;
|
||||
};
|
||||
|
||||
explicit ParametersProvider(QWidget *parent = nullptr);
|
||||
~ParametersProvider() = default;
|
||||
|
||||
void start();
|
||||
void setFocusInternal();
|
||||
const settings &getSettings();
|
||||
|
||||
signals:
|
||||
void done();
|
||||
|
||||
private:
|
||||
settings sett;
|
||||
QLineEdit *userField, *passField, *imapField, *portField;
|
||||
};
|
10
README.md
Normal file
10
README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# emailQt - A small & simple Qt email client
|
||||
|
||||
![](demo.mkv)
|
||||
|
||||
## Usage
|
||||
If you are using gmail, you need to enable 2FA and then generate an application password.
|
||||
This can be done at https://myaccount.google.com/security.
|
||||
|
||||
## Building
|
||||
You will need a C++23 compiler, cmake, Qt6, libcurl and [QtKeychain](https://github.com/frankosterfeld/qtkeychain).
|
Loading…
Reference in New Issue
Block a user