aboutsummaryrefslogtreecommitdiffstats
path: root/src/net
diff options
context:
space:
mode:
Diffstat (limited to 'src/net')
-rw-r--r--src/net/instance.cpp24
-rw-r--r--src/net/instance.h24
-rw-r--r--src/net/mastodon_instance.cpp134
-rw-r--r--src/net/mastodon_instance.h19
4 files changed, 201 insertions, 0 deletions
diff --git a/src/net/instance.cpp b/src/net/instance.cpp
new file mode 100644
index 0000000..c3af86c
--- /dev/null
+++ b/src/net/instance.cpp
@@ -0,0 +1,24 @@
+#include "instance.h"
+#include "mastodon_instance.h"
+#include "src/settings_interface.h"
+
+Instance* Instance::create_instance() {
+ return create_instance(SettingsInterface::quick_read_setting<AppSettingsTypes::InstanceType>("net/instance/type"));
+}
+
+Instance* Instance::create_instance(AppSettingsTypes::InstanceType type) {
+ switch (type) {
+ case AppSettingsTypes::InstanceType::MASTODON:
+ return new MastodonInstance;
+ default:
+ return nullptr;
+ }
+}
+
+QString Instance::oauth2_step1() {
+ return "";
+}
+
+Instance::OAuth2Step2 Instance::oauth2_step2(const QString &auth_code) {
+ return {false};
+}
diff --git a/src/net/instance.h b/src/net/instance.h
new file mode 100644
index 0000000..2b7eec7
--- /dev/null
+++ b/src/net/instance.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "src/activitypub/appost.h"
+#include "src/settings_interface.h"
+
+class Instance {
+public:
+ struct OAuth2Step2 {
+ bool ok;
+ QString token;
+ };
+
+ virtual ~Instance() {};
+ virtual APPost* get_post_from_url(const QString &url) = 0;
+ // Returns the URI the user has to visit in their browser
+ virtual QString oauth2_step1();
+ // auth_code is the code given by the user through the URI
+ // Returns true if authenfication was successful.
+ virtual OAuth2Step2 oauth2_step2(const QString &auth_code);
+ bool supports_oauth2 = false;
+ // Creates an instance by reading the type from settings
+ static Instance* create_instance();
+ // Creates a specific type of instance, for use with SettingsDialog
+ static Instance* create_instance(AppSettingsTypes::InstanceType type);
+};
diff --git a/src/net/mastodon_instance.cpp b/src/net/mastodon_instance.cpp
new file mode 100644
index 0000000..8b88221
--- /dev/null
+++ b/src/net/mastodon_instance.cpp
@@ -0,0 +1,134 @@
+#include "mastodon_instance.h"
+#include "src/activitypub/appost.h"
+#include "src/activitypub/apquestion.h"
+#include "src/settings_interface.h"
+#include "src/types.h"
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QUrl>
+#include <QDebug>
+
+APPost* post_from_json(const QJsonObject &status);
+
+MastodonInstance::MastodonInstance() : instance(SettingsInterface::quick_read_setting<QString>("net/instance/address").toStdString(), SettingsInterface::quick_read_setting<QString>("net/instance/token").toStdString()), connection(instance) {
+
+}
+
+MastodonInstance::~MastodonInstance() {
+ if (obtain_token)
+ delete obtain_token;
+}
+
+QString MastodonInstance::oauth2_step1() {
+ obtain_token = new mastodonpp::Instance::ObtainToken{instance};
+ auto answer{obtain_token->step_1("ActorViewer", "read", "https://twilightsparkle.space")};
+ if (answer)
+ return answer.body.c_str();
+ else
+ return "";
+}
+
+Instance::OAuth2Step2 MastodonInstance::oauth2_step2(const QString &auth_code) {
+ if (!obtain_token) return {false};
+ auto answer{obtain_token->step_2(auth_code.toStdString())};
+ QString token;
+
+ if (answer) {
+ token = answer.body.c_str();
+ }
+
+ delete obtain_token; obtain_token = nullptr;
+ return {(bool)answer, token};
+}
+
+APPost* MastodonInstance::get_post_from_url(const QString &url) {
+ auto answer{connection.get(mastodonpp::API::v2::search, {
+ {"q", QUrl::toPercentEncoding(url).toStdString()},
+ {"type", "statuses"},
+ {"limit", "1"},
+ {"resolve", "true"}
+ })};
+
+ if (answer) {
+ QJsonDocument doc = QJsonDocument::fromJson(answer.body.c_str());
+ QJsonObject obj = doc.object();
+ qDebug() << doc;
+ if (obj.contains("statuses")) {
+ QJsonObject status = obj["statuses"].toArray().first().toObject();
+ if (status.isEmpty()) return new APPost({.content="invalid"}); // Invalid
+ return post_from_json(status);
+ }
+ }
+
+ return new APPost({.content="connection error"});
+}
+
+// Create a filled APPost object from a Status entity (https://docs.joinmastodon.org/entities/Status/)
+// We have to return a pointer as else I can't get derived classes of APPost to render properly: the additional methods are ignored.
+// Furthermore, using pointers removes unecessary copying, even if that can be reduced by return value optimization.
+APPost* post_from_json(const QJsonObject &status) {
+ APObjectFields fields;
+ bool is_question = status.contains("poll");
+
+ fields.by_actor = status["account"]["url"].toString();
+ fields.reply_to_url = status["in_reply_to_id"].toString(); // id ≠ url
+
+ fields.languages.append(status["language"].toString());
+ fields.published = status["created_at"].toString();
+
+ fields.object_url = status["uri"].toString();
+ fields.web_url = status["url"].toString();
+
+ if (status["sensitive"].toBool(true))
+ fields.summary = status["spoiler_text"].toString();
+ if (status.contains("content"))
+ fields.content = status["content"].toString();
+
+ if (status.contains("visibility")) {
+ QString visibility = status["visibility"].toString();
+ if (visibility == "public") fields.visibility = StatusType::PUBLIC;
+ else if (visibility == "unlisted") fields.visibility = StatusType::UNLISTED;
+ else if (visibility == "private") fields.visibility = StatusType::PRIVATE;
+ else if (visibility == "direct") fields.visibility = StatusType::DIRECT;
+ else fields.visibility = StatusType::UNKNOWN;
+ }
+
+ if (QJsonArray medias = status["media_attachments"].toArray(); not medias.isEmpty()) {
+ for (QJsonValueRef media : medias) {
+ if (not media.isObject()) continue;
+ QJsonObject attrib = media.toObject();
+ fields.attachments.push_back({
+ // FIXME: we use the preview to lower bandwidth but it would be nicer to have APAttachment fields for differencing between qualities.
+ .path = attrib["preview_url"].toString(),
+ // FIXME: we don't have any better attribute to work with
+ .filename = attrib["id"].toString(),
+ .media_type = attrib["type"].toString(),
+ .name = attrib["description"].toString()
+ // TODO: we don't use the blurhash yet…
+ });
+ }
+ }
+
+ if (is_question) {
+ QJsonObject poll = status["poll"].toObject();
+
+ fields.question.end_time = poll["expires_at"].toString();
+ fields.question.closed_time = poll["expires_at"].toString();
+ fields.question.total_votes = poll["votes_count"].toInt();
+
+ for (QJsonValueRef option : poll["options"].toArray()) {
+ QJsonObject element = option.toObject();
+ fields.question.poll_options.push_back({element["title"].toString(), element["votes_count"].toInt()});
+ }
+
+
+ // TODO: have an boolean attribute for defining if the poll is multiple choice or not, int for the voters_count and bool/int for `voted` and `own_votes`
+ }
+
+ if (is_question)
+ return new APQuestion(fields);
+ else
+ return new APPost(fields);
+}
diff --git a/src/net/mastodon_instance.h b/src/net/mastodon_instance.h
new file mode 100644
index 0000000..983bef6
--- /dev/null
+++ b/src/net/mastodon_instance.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "instance.h"
+#include "mastodonpp/instance.hpp"
+#include "mastodonpp/connection.hpp"
+
+class MastodonInstance : public Instance {
+public:
+ MastodonInstance();
+ ~MastodonInstance();
+ APPost* get_post_from_url(const QString &url);
+ QString oauth2_step1();
+ OAuth2Step2 oauth2_step2(const QString &auth_code);
+ bool supports_oauth2 = true;
+
+private:
+ mastodonpp::Instance instance;
+ mastodonpp::Connection connection;
+ mastodonpp::Instance::ObtainToken* obtain_token = nullptr;
+};