work on sending emails

This commit is contained in:
Martin Rotter 2020-07-23 12:39:40 +02:00
parent 6068751a52
commit 109ceeb777
37 changed files with 190 additions and 171 deletions

View File

@ -14,7 +14,7 @@
#define ARGUMENTS_LIST_SEPARATOR "\n"
#define ADBLOCK_ADBLOCKED_PAGE "adblockedpage"
#define ADBLOCK_HOWTO_FILTERS "http://adblockplus.org/en/filters"
#define ADBLOCK_HOWTO_FILTERS "https://help.eyeo.com/en/adblockplus/how-to-write-filters"
#define ADBLOCK_UPDATE_DAYS_INTERVAL 5
#define ADBLOCK_ICON_ACTIVE "adblock"
#define ADBLOCK_ICON_DISABLED "adblock-disabled"
@ -78,6 +78,7 @@
#define HTTP_HEADERS_ACCEPT "Accept"
#define HTTP_HEADERS_CONTENT_TYPE "Content-Type"
#define HTTP_HEADERS_CONTENT_LENGTH "Content-Length"
#define HTTP_HEADERS_AUTHORIZATION "Authorization"
#define HTTP_HEADERS_USER_AGENT "User-Agent"

View File

@ -1,19 +1,4 @@
// This file is part of RSS Guard.
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "gui/messagesview.h"

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -1,4 +1,4 @@
// This file is part of RSS Guard.
// For license of this file, see <project-root-folder>/LICENSE.md.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>

View File

@ -7,7 +7,7 @@
#define GMAIL_OAUTH_TOKEN_URL "https://accounts.google.com/o/oauth2/token"
#define GMAIL_OAUTH_SCOPE "https://mail.google.com/"
#define GMAIL_API_SEND_MESSAGE "https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=multipart"
#define GMAIL_API_SEND_MESSAGE "https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=media"
#define GMAIL_API_BATCH_UPD_LABELS "https://www.googleapis.com/gmail/v1/users/me/messages/batchModify"
#define GMAIL_API_GET_ATTACHMENT "https://www.googleapis.com/gmail/v1/users/me/messages/%1/attachments/%2"
#define GMAIL_API_LABELS_LIST "https://www.googleapis.com/gmail/v1/users/me/labels"

View File

@ -8,6 +8,7 @@
#include "services/gmail/definitions.h"
#include <QComboBox>
#include <QCompleter>
#include <QHBoxLayout>
#include <QLineEdit>
@ -20,6 +21,11 @@ EmailRecipientControl::EmailRecipientControl(const QString& recipient, QWidget*
lay->setMargin(0);
lay->setContentsMargins(0, 0, 0, 0);
m_cmbRecipientType->setFocusPolicy(Qt::FocusPolicy::NoFocus);
m_btnCloseMe->setFocusPolicy(Qt::FocusPolicy::NoFocus);
m_txtRecipient->setPlaceholderText(tr("E-mail address"));
m_txtRecipient->setText(recipient);
m_btnCloseMe->setToolTip("Remove this recipient.");
m_btnCloseMe->setIcon(qApp->icons()->fromTheme(QSL("list-remove")));
@ -43,3 +49,17 @@ QString EmailRecipientControl::recipientAddress() const {
RecipientType EmailRecipientControl::recipientType() const {
return RecipientType(m_cmbRecipientType->currentData(Qt::ItemDataRole::UserRole).toInt());
}
void EmailRecipientControl::setPossibleRecipients(const QStringList& rec) {
if (m_txtRecipient->completer() != nullptr) {
auto* old_cmpl = m_txtRecipient->completer();
m_txtRecipient->setCompleter(nullptr);
old_cmpl->deleteLater();
}
QCompleter* cmpl = new QCompleter(rec, m_txtRecipient);
m_txtRecipient->setCompleter(cmpl);
}

View File

@ -21,6 +21,8 @@ class EmailRecipientControl : public QWidget {
QString recipientAddress() const;
RecipientType recipientType() const;
void setPossibleRecipients(const QStringList& rec);
signals:
void removalRequested();

View File

@ -22,6 +22,7 @@ FormAddEditEmail::FormAddEditEmail(GmailServiceRoot* root, QWidget* parent) : QD
m_ui.m_btnAdder->setIcon(qApp->icons()->fromTheme(QSL("list-add")));
m_ui.m_btnAdder->setToolTip(tr("Add new recipient."));
m_ui.m_btnAdder->setFocusPolicy(Qt::FocusPolicy::NoFocus);
connect(m_ui.m_btnAdder, &PlainToolButton::clicked, this, [=]() {
addRecipientRow();
@ -96,8 +97,11 @@ void FormAddEditEmail::onOkClicked() {
msg["Reply-To"] = rec_repl.join(',').toStdString();
}
msg["Subject"] = m_ui.m_txtSubject->text().toStdString();
msg["Subject"] = QString("=?utf-8?B?%1?=")
.arg(QString(m_ui.m_txtSubject->text().toUtf8().toBase64(QByteArray::Base64Option::Base64UrlEncoding)))
.toStdString();
msg.set_plain(m_ui.m_txtMessage->toPlainText().toStdString());
msg.set_header("Content-Type", "text/plain; charset=utf-8");
if (m_originalMessage == nullptr) {
// Send completely new message.
@ -123,6 +127,13 @@ 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) {}
m_ui.m_layout->insertRow(m_ui.m_layout->count() - 5, mail_rec);
}

View File

@ -52,7 +52,7 @@ void GmailNetworkFactory::setBatchSize(int batch_size) {
m_batchSize = batch_size;
}
void GmailNetworkFactory::sendEmail(const Mimesis::Message& msg) {
QString GmailNetworkFactory::sendEmail(const Mimesis::Message& msg) {
QString bearer = m_oauth2->bearer().toLocal8Bit();
if (bearer.isEmpty()) {
@ -60,17 +60,39 @@ void GmailNetworkFactory::sendEmail(const Mimesis::Message& msg) {
}
QString rfc_email = QString::fromStdString(msg.to_string());
QString json = QSL("{\"raw\": \"%1\"}").arg(QString(rfc_email.toUtf8().toBase64(QByteArray::Base64Option::Base64UrlEncoding)));
QByteArray input_data = json.toUtf8();
QByteArray input_data = rfc_email.toUtf8();
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("message/rfc822").toLocal8Bit()));
QByteArray out;
auto xx = NetworkFactory::performNetworkOperation(GMAIL_API_SEND_MESSAGE,
DOWNLOAD_TIMEOUT,
input_data,
out,
QNetworkAccessManager::Operation::PostOperation,
headers);
int a = 5;
auto result = NetworkFactory::performNetworkOperation(GMAIL_API_SEND_MESSAGE,
DOWNLOAD_TIMEOUT,
input_data,
out,
QNetworkAccessManager::Operation::PostOperation,
headers);
if (result.first != QNetworkReply::NetworkError::NoError) {
if (!out.isEmpty()) {
QJsonDocument doc = QJsonDocument::fromJson(out);
auto msg = doc.object()["error"].toObject()["message"].toString();
throw ApplicationException(msg);
}
else {
throw ApplicationException(QString::fromUtf8(out));
}
}
else {
QJsonDocument doc = QJsonDocument::fromJson(out);
auto msg_id = doc.object()["id"].toString();
return msg_id;
}
}
void GmailNetworkFactory::initializeOauth() {
@ -393,6 +415,92 @@ 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"));
}
}
bool GmailNetworkFactory::obtainAndDecodeFullMessages(const QList<Message>& lite_messages,
const QString& feed_id,
QList<Message>& full_messages) {
@ -477,81 +585,3 @@ QList<Message> GmailNetworkFactory::decodeLiteMessages(const QString& messages_j
return messages;
}
/*
RootItem* GmailNetworkFactory::decodeFeedCategoriesData(const QString& categories) {
RootItem* parent = new RootItem();
QJsonArray json = QJsonDocument::fromJson(categories.toUtf8()).object()["labels"].toArray();
QMap<QString, RootItem*> cats;
cats.insert(QString(), parent);
for (const QJsonValue& obj : json) {
auto label = obj.toObject();
QString label_id = label["id"].toString();
QString label_name = label["name"].toString();
QString label_type = label["type"].toString();
if (label_name.contains(QL1C('/'))) {
// We have nested labels.
}
else {
GmailFeed* feed = new GmailFeed();
feed->setTitle(label_name);
feed->setCustomId(label_id);
parent->appendChild(feed);
}
if (label_id.contains(QSL("/label/"))) {
// We have label (not "state").
Category* category = new Category();
category->setDescription(label["htmlUrl"].toString());
category->setTitle(label_id.mid(label_id.lastIndexOf(QL1C('/')) + 1));
category->setCustomId(label_id);
cats.insert(category->customId(), category);
// All categories in Nextcloud are top-level.
parent->appendChild(category);
}
}
json = QJsonDocument::fromJson(feeds.toUtf8()).object()["subscriptions"].toArray();
for (const QJsonValue& obj : json) {
auto subscription = obj.toObject();
QString id = subscription["id"].toString();
QString title = subscription["title"].toString();
QString url = subscription["htmlUrl"].toString();
QString parent_label;
QJsonArray categories = subscription["categories"].toArray();
for (const QJsonValue& cat : categories) {
QString potential_id = cat.toObject()["id"].toString();
if (potential_id.contains(QSL("/label/"))) {
parent_label = potential_id;
break;
}
}
// We have label (not "state").
GmailFeed* feed = new GmailFeed();
feed->setDescription(url);
feed->setUrl(url);
feed->setTitle(title);
feed->setCustomId(id);
if (cats.contains(parent_label)) {
cats[parent_label]->appendChild(feed);
}
}
return parent;
}
*/

View File

@ -35,7 +35,11 @@ class GmailNetworkFactory : public QObject {
int batchSize() const;
void setBatchSize(int batch_size);
void sendEmail(const Mimesis::Message& msg);
// Sends e-mail, returns its ID.
QString sendEmail(const Mimesis::Message& msg);
// Returns all possible recipients.
QStringList getAllRecipients();
Downloader* downloadAttachment(const QString& msg_id, const QString& attachment_id);

View File

@ -1,21 +1,4 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>
// Copyright (C) 2010-2014 by David Rosca <nowrep@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "services/inoreader/inoreaderserviceroot.h"

View File

@ -1,21 +1,4 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>
// Copyright (C) 2010-2014 by David Rosca <nowrep@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef INOREADERSERVICEROOT_H
#define INOREADERSERVICEROOT_H