gmail plugin can send/reply to messages
This commit is contained in:
parent
a3d142034f
commit
64df91a891
17
src/librssguard/3rd-party/boolinq/boolinq.h
vendored
17
src/librssguard/3rd-party/boolinq/boolinq.h
vendored
@ -64,7 +64,7 @@ namespace boolinq {
|
||||
|
||||
void for_each(std::function<void(T)> apply) const
|
||||
{
|
||||
return for_each_i([apply](T value, int index) { return apply(value); });
|
||||
return for_each_i([apply](T value, int) { return apply(value); });
|
||||
}
|
||||
|
||||
Linq<std::tuple<Linq<S, T>, int>, T> where_i(std::function<bool(T, int)> filter) const
|
||||
@ -87,7 +87,7 @@ namespace boolinq {
|
||||
|
||||
Linq<std::tuple<Linq<S, T>, int>, T> where(std::function<bool(T)> filter) const
|
||||
{
|
||||
return where_i([filter](T value, int index) { return filter(value); });
|
||||
return where_i([filter](T value, int) { return filter(value); });
|
||||
}
|
||||
|
||||
Linq<std::tuple<Linq<S, T>, int>, T> take(int count) const
|
||||
@ -296,13 +296,14 @@ namespace boolinq {
|
||||
Linq<S, T> &linqCopy = std::get<1>(tuple);
|
||||
std::unordered_set<_TKey> &set = std::get<2>(tuple);
|
||||
|
||||
_TKey key = apply(linq.next());
|
||||
if (set.insert(key).second) {
|
||||
return std::make_pair(key, linqCopy.where([apply, key](T v){
|
||||
return apply(v) == key;
|
||||
}));
|
||||
while (true) {
|
||||
_TKey key = apply(linq.next());
|
||||
if (set.insert(key).second) {
|
||||
return std::make_pair(key, linqCopy.where([apply, key](T v){
|
||||
return apply(v) == key;
|
||||
}));
|
||||
}
|
||||
}
|
||||
throw LinqEndException();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -141,6 +141,26 @@
|
||||
#define APP_NO_THEME ""
|
||||
#define APP_THEME_SUFFIX ".png"
|
||||
|
||||
#ifndef qDebugNN
|
||||
#define qDebugNN qDebug().noquote().nospace()
|
||||
#endif
|
||||
|
||||
#ifndef qWarningNN
|
||||
#define qWarningNN qWarning().noquote().nospace()
|
||||
#endif
|
||||
|
||||
#ifndef qCriticalNN
|
||||
#define qCriticalNN qCritical().noquote().nospace()
|
||||
#endif
|
||||
|
||||
#ifndef qFatalNN
|
||||
#define qFatalNN qFatal().noquote().nospace()
|
||||
#endif
|
||||
|
||||
#ifndef qInfoNN
|
||||
#define qInfoNN qInfo().noquote().nospace()
|
||||
#endif
|
||||
|
||||
#ifndef QSL
|
||||
|
||||
// Thin macro wrapper for literal strings.
|
||||
|
@ -417,6 +417,9 @@ else {
|
||||
SOURCES += $$files(3rd-party/mimesis/*.cpp, false)
|
||||
HEADERS += $$files(3rd-party/mimesis/*.hpp, false)
|
||||
|
||||
# Add boolinq.
|
||||
HEADERS += $$files(3rd-party/boolinq/*.h, false)
|
||||
|
||||
INCLUDEPATH += $$PWD/. \
|
||||
$$PWD/gui \
|
||||
$$PWD/gui/dialogs \
|
||||
|
@ -1720,6 +1720,28 @@ bool DatabaseQueries::createTtRssAccount(const QSqlDatabase& db, int id_to_assig
|
||||
}
|
||||
}
|
||||
|
||||
QStringList DatabaseQueries::getAllRecipients(const QSqlDatabase& db, int account_id) {
|
||||
QSqlQuery query(db);
|
||||
QStringList rec;
|
||||
|
||||
query.prepare(QSL("SELECT DISTINCT author "
|
||||
"FROM Messages "
|
||||
"WHERE account_id = :account_id AND author IS NOT NULL AND author != '' "
|
||||
"ORDER BY lower(author) ASC;"));
|
||||
query.bindValue(QSL(":account_id"), account_id);
|
||||
|
||||
if (query.exec()) {
|
||||
while (query.next()) {
|
||||
rec.append(query.value(0).toString());
|
||||
}
|
||||
}
|
||||
else {
|
||||
qWarningNN << "Query for all recipients failed: '" << query.lastError().text() << "'.";
|
||||
}
|
||||
|
||||
return rec;
|
||||
}
|
||||
|
||||
QList<ServiceRoot*> DatabaseQueries::getGmailAccounts(const QSqlDatabase& db, bool* ok) {
|
||||
QSqlQuery query(db);
|
||||
QList<ServiceRoot*> roots;
|
||||
|
@ -145,6 +145,7 @@ class DatabaseQueries {
|
||||
bool force_server_side_feed_update, bool download_only_unread_messages);
|
||||
|
||||
// Gmail account.
|
||||
static QStringList getAllRecipients(const QSqlDatabase& db, int account_id);
|
||||
static bool deleteGmailAccount(const QSqlDatabase& db, int account_id);
|
||||
static QList<ServiceRoot*> getGmailAccounts(const QSqlDatabase& db, bool* ok = nullptr);
|
||||
static bool overwriteGmailAccount(const QSqlDatabase& db, const QString& username, const QString& app_id,
|
||||
|
@ -135,10 +135,10 @@ bool GmailServiceRoot::downloadAttachmentOnMyOwn(const QUrl& url) const {
|
||||
|
||||
QList<QAction*> GmailServiceRoot::contextMenuMessagesList(const QList<Message>& messages) {
|
||||
if (messages.size() == 1) {
|
||||
if (m_actionReply == nullptr) {
|
||||
m_actionReply = new QAction(qApp->icons()->fromTheme(QSL("mail-reply-sender")), tr("Reply"), this);
|
||||
m_replyToMessage = messages.at(0);
|
||||
|
||||
m_replyToMessage = messages.at(0);
|
||||
if (m_actionReply == nullptr) {
|
||||
m_actionReply = new QAction(qApp->icons()->fromTheme(QSL("mail-reply-sender")), tr("Reply to this message"), this);
|
||||
connect(m_actionReply, &QAction::triggered, this, &GmailServiceRoot::replyToEmail);
|
||||
}
|
||||
|
||||
|
@ -61,5 +61,9 @@ void EmailRecipientControl::setPossibleRecipients(const QStringList& rec) {
|
||||
|
||||
QCompleter* cmpl = new QCompleter(rec, m_txtRecipient);
|
||||
|
||||
cmpl->setFilterMode(Qt::MatchFlag::MatchContains);
|
||||
cmpl->setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
|
||||
cmpl->setCompletionMode(QCompleter::CompletionMode::UnfilteredPopupCompletion);
|
||||
|
||||
m_txtRecipient->setCompleter(cmpl);
|
||||
}
|
||||
|
@ -7,13 +7,18 @@
|
||||
#include "gui/guiutilities.h"
|
||||
#include "gui/messagebox.h"
|
||||
#include "miscellaneous/application.h"
|
||||
#include "miscellaneous/databasequeries.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "services/gmail/gmailserviceroot.h"
|
||||
#include "services/gmail/gui/emailrecipientcontrol.h"
|
||||
#include "services/gmail/network/gmailnetworkfactory.h"
|
||||
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
|
||||
#include <QCloseEvent>
|
||||
|
||||
FormAddEditEmail::FormAddEditEmail(GmailServiceRoot* root, QWidget* parent)
|
||||
: QDialog(parent), m_root(root), m_originalMessage(nullptr) {
|
||||
: QDialog(parent), m_root(root), m_originalMessage(nullptr), m_possibleRecipients({}) {
|
||||
m_ui.setupUi(this);
|
||||
|
||||
GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("mail-message-new")));
|
||||
@ -33,6 +38,14 @@ FormAddEditEmail::FormAddEditEmail(GmailServiceRoot* root, QWidget* parent)
|
||||
&QPushButton::clicked,
|
||||
this,
|
||||
&FormAddEditEmail::onOkClicked);
|
||||
|
||||
QSqlDatabase db = qApp->database()->connection(metaObject()->className());
|
||||
|
||||
m_possibleRecipients = DatabaseQueries::getAllRecipients(db, m_root->accountId());
|
||||
|
||||
for (auto* rec: recipientControls()) {
|
||||
rec->setPossibleRecipients(m_possibleRecipients);
|
||||
}
|
||||
}
|
||||
|
||||
void FormAddEditEmail::execForAdd() {
|
||||
@ -44,7 +57,7 @@ void FormAddEditEmail::execForReply(Message* original_message) {
|
||||
m_originalMessage = original_message;
|
||||
|
||||
addRecipientRow(m_originalMessage->m_author);
|
||||
m_ui.m_txtSubject->setText(QSL("Re:%1").arg(m_originalMessage->m_title));
|
||||
m_ui.m_txtSubject->setText(QSL("Re: %1").arg(m_originalMessage->m_title));
|
||||
exec();
|
||||
}
|
||||
|
||||
@ -129,18 +142,14 @@ void FormAddEditEmail::addRecipientRow(const QString& recipient) {
|
||||
|
||||
connect(mail_rec, &EmailRecipientControl::removalRequested, this, &FormAddEditEmail::removeRecipientRow);
|
||||
|
||||
try {
|
||||
QStringList rec = m_root->network()->getAllRecipients();
|
||||
|
||||
mail_rec->setPossibleRecipients(rec);
|
||||
}
|
||||
catch (const ApplicationException& ex) {
|
||||
qWarning("Failed to get recipients: '%s'.", qPrintable(ex.message()));
|
||||
}
|
||||
|
||||
mail_rec->setPossibleRecipients(m_possibleRecipients);
|
||||
m_ui.m_layout->insertRow(m_ui.m_layout->count() - 5, mail_rec);
|
||||
}
|
||||
|
||||
void FormAddEditEmail::closeEvent(QCloseEvent* event) {
|
||||
// event->ignore();
|
||||
}
|
||||
|
||||
QList<EmailRecipientControl*> FormAddEditEmail::recipientControls() const {
|
||||
QList<EmailRecipientControl*> list;
|
||||
|
||||
|
@ -31,6 +31,7 @@ class FormAddEditEmail : public QDialog {
|
||||
void addRecipientRow(const QString& recipient = QString());
|
||||
|
||||
private:
|
||||
void closeEvent(QCloseEvent* event);
|
||||
QList<EmailRecipientControl*> recipientControls() const;
|
||||
|
||||
private:
|
||||
@ -39,6 +40,7 @@ class FormAddEditEmail : public QDialog {
|
||||
Ui::FormAddEditEmail m_ui;
|
||||
QList<EmailRecipientControl*> m_recipientControls;
|
||||
Message* m_originalMessage;
|
||||
QStringList m_possibleRecipients;
|
||||
};
|
||||
|
||||
#endif // FORMADDEDITEMAIL_H
|
||||
|
@ -56,7 +56,7 @@ QString GmailNetworkFactory::sendEmail(Mimesis::Message msg, Message* reply_to_m
|
||||
QString bearer = m_oauth2->bearer().toLocal8Bit();
|
||||
|
||||
if (bearer.isEmpty()) {
|
||||
throw ApplicationException(tr("you aren't logged in"));
|
||||
//throw ApplicationException(tr("you aren't logged in"));
|
||||
}
|
||||
|
||||
if (reply_to_message != nullptr) {
|
||||
@ -72,8 +72,8 @@ QString GmailNetworkFactory::sendEmail(Mimesis::Message msg, Message* reply_to_m
|
||||
}*/
|
||||
|
||||
if (metadata.contains(QSL("Message-ID"))) {
|
||||
msg["References"] = metadata.value(QSL("Message-ID")).toString().toStdString();
|
||||
msg["In-Reply-To"] = metadata.value(QSL("Message-ID")).toString().toStdString();
|
||||
msg["References"] = metadata.value(QSL("Message-ID")).toStdString();
|
||||
msg["In-Reply-To"] = metadata.value(QSL("Message-ID")).toStdString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,93 +433,7 @@ bool GmailNetworkFactory::fillFullMessage(Message& msg, const QJsonObject& json,
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList GmailNetworkFactory::getAllRecipients() {
|
||||
QString bearer = m_oauth2->bearer().toLocal8Bit();
|
||||
|
||||
if (bearer.isEmpty()) {
|
||||
throw ApplicationException(tr("not logged-in"));
|
||||
}
|
||||
|
||||
QStringList recipients;
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
|
||||
headers.append(QPair<QByteArray, QByteArray>(QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
|
||||
m_oauth2->bearer().toLocal8Bit()));
|
||||
headers.append(QPair<QByteArray, QByteArray>(QString(HTTP_HEADERS_CONTENT_TYPE).toLocal8Bit(),
|
||||
QString(GMAIL_CONTENT_TYPE_JSON).toLocal8Bit()));
|
||||
|
||||
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
|
||||
QByteArray msg_list_data;
|
||||
|
||||
// TODO: Cyklicky!!
|
||||
auto list_res = NetworkFactory::performNetworkOperation(GMAIL_API_MSGS_LIST,
|
||||
timeout,
|
||||
QByteArray(),
|
||||
msg_list_data,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers);
|
||||
|
||||
if (list_res.first != QNetworkReply::NetworkError::NoError) {
|
||||
throw ApplicationException(tr("comm error when asking for recipients"));
|
||||
}
|
||||
|
||||
QJsonDocument json_list = QJsonDocument::fromJson(msg_list_data);
|
||||
QStringList message_ids;
|
||||
|
||||
for (const auto& msg_nod : json_list.object()["messages"].toArray()) {
|
||||
message_ids.append(msg_nod.toObject()["id"].toString());
|
||||
}
|
||||
|
||||
auto* multi = new QHttpMultiPart();
|
||||
|
||||
multi->setContentType(QHttpMultiPart::ContentType::MixedType);
|
||||
|
||||
for (const QString& msg : message_ids) {
|
||||
QHttpPart part;
|
||||
|
||||
part.setRawHeader(HTTP_HEADERS_CONTENT_TYPE, GMAIL_CONTENT_TYPE_HTTP);
|
||||
QString full_msg_endpoint = QString("GET /gmail/v1/users/me/messages/%1?metadataHeaders=From&metadataHeaders=To&format=metadata\r\n").arg(msg);
|
||||
|
||||
part.setBody(full_msg_endpoint.toUtf8());
|
||||
multi->append(part);
|
||||
}
|
||||
|
||||
QList<HttpResponse> output;
|
||||
|
||||
headers.removeLast();
|
||||
|
||||
NetworkResult res = NetworkFactory::performNetworkOperation(GMAIL_API_BATCH,
|
||||
timeout,
|
||||
multi,
|
||||
output,
|
||||
QNetworkAccessManager::Operation::PostOperation,
|
||||
headers);
|
||||
|
||||
if (res.first == QNetworkReply::NetworkError::NoError) {
|
||||
// We parse each part of HTTP response (it contains HTTP headers and payload with msg full data).
|
||||
for (const HttpResponse& part : output) {
|
||||
QJsonObject msg_doc = QJsonDocument::fromJson(part.body().toUtf8()).object();
|
||||
auto headers = msg_doc["payload"].toObject()["headers"].toArray();
|
||||
|
||||
if (headers.size() >= 2) {
|
||||
for (const auto& head : headers) {
|
||||
auto val = head.toObject()["value"].toString();
|
||||
|
||||
if (!recipients.contains(val)) {
|
||||
recipients.append(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return recipients;
|
||||
}
|
||||
else {
|
||||
throw ApplicationException(tr("comm error when asking for recipients"));
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap GmailNetworkFactory::getMessageMetadata(const QString& msg_id, const QStringList& metadata) {
|
||||
QMap<QString, QString> GmailNetworkFactory::getMessageMetadata(const QString& msg_id, const QStringList& metadata) {
|
||||
QString bearer = m_oauth2->bearer();
|
||||
|
||||
if (bearer.isEmpty()) {
|
||||
@ -543,7 +457,19 @@ QVariantMap GmailNetworkFactory::getMessageMetadata(const QString& msg_id, const
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers);
|
||||
|
||||
if (res.first == QNetworkReply::NetworkError::NoError) {}
|
||||
if (res.first == QNetworkReply::NetworkError::NoError) {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(output);
|
||||
QMap<QString, QString> result;
|
||||
auto headers = doc.object()["payload"].toObject()["headers"].toArray();
|
||||
|
||||
for (const auto& header : headers) {
|
||||
QJsonObject obj_header = header.toObject();
|
||||
|
||||
result.insert(obj_header["name"].toString(), obj_header["value"].toString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
throw ApplicationException(tr("failed to get metadata"));
|
||||
}
|
||||
|
@ -38,9 +38,6 @@ class GmailNetworkFactory : public QObject {
|
||||
// Sends e-mail, returns its ID.
|
||||
QString sendEmail(Mimesis::Message msg, Message* reply_to_message = nullptr);
|
||||
|
||||
// Returns all possible recipients.
|
||||
QStringList getAllRecipients();
|
||||
|
||||
Downloader* downloadAttachment(const QString& msg_id, const QString& attachment_id);
|
||||
|
||||
QList<Message> messages(const QString& stream_id, Feed::Status& error);
|
||||
@ -53,7 +50,7 @@ class GmailNetworkFactory : public QObject {
|
||||
|
||||
private:
|
||||
bool fillFullMessage(Message& msg, const QJsonObject& json, const QString& feed_id);
|
||||
QVariantMap getMessageMetadata(const QString& msg_id, const QStringList& metadata);
|
||||
QMap<QString, QString> getMessageMetadata(const QString& msg_id, const QStringList& metadata);
|
||||
bool obtainAndDecodeFullMessages(const QList<Message>& lite_messages, const QString& feed_id, QList<Message>& full_messages);
|
||||
QList<Message> decodeLiteMessages(const QString& messages_json_data, const QString& stream_id, QString& next_page_token);
|
||||
|
||||
|
@ -120,7 +120,7 @@ OwnCloudStatusResponse OwnCloudNetworkFactory::status() {
|
||||
headers);
|
||||
OwnCloudStatusResponse status_response(QString::fromUtf8(result_raw));
|
||||
|
||||
qDebug().noquote().nospace() << "Raw status data is:" << result_raw;
|
||||
qDebugNN << "Raw status data is:" << result_raw;
|
||||
|
||||
if (network_reply.first != QNetworkReply::NoError) {
|
||||
qWarning("Nextcloud: Obtaining status info failed with error %d.", network_reply.first);
|
||||
|
Loading…
x
Reference in New Issue
Block a user