aboutsummaryrefslogtreecommitdiffstats
path: root/src/activitypub
diff options
context:
space:
mode:
Diffstat (limited to 'src/activitypub')
-rw-r--r--src/activitypub/apactivity.cpp46
-rw-r--r--src/activitypub/apactivity.h45
-rw-r--r--src/activitypub/apactor.cpp35
-rw-r--r--src/activitypub/apactor.h25
-rw-r--r--src/activitypub/apattachment.cpp45
-rw-r--r--src/activitypub/apattachment.h33
-rw-r--r--src/activitypub/apbase.cpp21
-rw-r--r--src/activitypub/apbase.h22
-rw-r--r--src/activitypub/apobject.cpp23
-rw-r--r--src/activitypub/apobject.h36
-rw-r--r--src/activitypub/appost.cpp71
-rw-r--r--src/activitypub/appost.h43
-rw-r--r--src/activitypub/apreblog.cpp13
-rw-r--r--src/activitypub/apreblog.h27
14 files changed, 485 insertions, 0 deletions
diff --git a/src/activitypub/apactivity.cpp b/src/activitypub/apactivity.cpp
new file mode 100644
index 0000000..5f1c36d
--- /dev/null
+++ b/src/activitypub/apactivity.cpp
@@ -0,0 +1,46 @@
+#include "apactivity.h"
+#include "apactor.h"
+
+#include <QDebug>
+
+APActivity::APActivity() {}
+
+APActivity::APActivity(APActivityFields fields) {
+ for (QString actor_url : fields.to_actors)
+ to_actors.push_back(APActor(actor_url));
+ for (QString actor_url : fields.cc_actors)
+ cc_actors.push_back(APActor(actor_url));
+ by_actor = fields.by_actor;
+
+ object_url = fields.object_url;
+ published = QDateTime::fromString(fields.published, Qt::ISODate);
+
+ visibility = fields.visibility;
+ type = fields.type;
+
+ object = fields.object;
+}
+
+APActivity::~APActivity() {
+ delete object;
+}
+
+QString APActivity::get_html_render(HtmlRenderDetails render_info) {
+ QString html(get_html_template("apactivity"));
+
+ switch (type) {
+ case APActivityType::CREATE:
+ html.replace("{{type}}", "Create"); break;
+ case APActivityType::ANNOUNCE:
+ html.replace("{{type}}", "Announce"); break;
+ default:
+ html.replace("{{type}}", "<i>Unknown</i>"); break;
+ }
+
+ html.replace("{{by}}", by_actor.get_html_render(render_info));
+ html.replace("{{object}}", object->get_html_render(render_info));
+ if (published.isValid())
+ html.replace("{{published}}", render_info.locale->toString(published.toLocalTime()));
+
+ return html;
+}
diff --git a/src/activitypub/apactivity.h b/src/activitypub/apactivity.h
new file mode 100644
index 0000000..fcfb86c
--- /dev/null
+++ b/src/activitypub/apactivity.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "apactor.h"
+#include "apbase.h"
+#include "apobject.h"
+#include "../types.h"
+#include <QDateTime>
+
+enum struct APActivityType {CREATE, ANNOUNCE, UNKNOWN};
+
+struct APActivityFields {
+ QString by_actor;
+ QStringList to_actors;
+ QStringList cc_actors;
+ QString object_url; // maps to "id", represents the Activity, *not* the URL of the object it contains
+ QString published;
+ APObject* object; // will generally be a APPost or APReblog
+ StatusType visibility;
+ APActivityType type = APActivityType::UNKNOWN;
+};
+
+class APActivity : APBase {
+public:
+ APActivity();
+
+ // An Activity that can easily be constructed from an ActivityStreams JSON Activity. Make sure to pass the APObject.
+ APActivity(APActivityFields fields);
+ ~APActivity();
+
+ APObject* object = nullptr; // the object that's manipulated by this Activity, use APPost for something useful and "manually" create it
+
+ QString get_html_render(HtmlRenderDetails render_info);
+
+private:
+ APActorList to_actors;
+ APActorList cc_actors;
+ APActor by_actor; // the Actor who did this Activity, maps to "actor"
+
+ APActivityType type;
+ StatusType visibility;
+
+ //QString object_url; // activity's URL, maps to "id"
+
+ QDateTime published;
+};
diff --git a/src/activitypub/apactor.cpp b/src/activitypub/apactor.cpp
new file mode 100644
index 0000000..6731dd3
--- /dev/null
+++ b/src/activitypub/apactor.cpp
@@ -0,0 +1,35 @@
+#include "apactor.h"
+
+APActor::APActor() {}
+
+APActor::APActor(const QString url) {
+ object_url = url;
+}
+
+const QString APActor::get_url() {
+ return object_url;
+}
+
+QString APActor::get_html_render(HtmlRenderDetails render_info) {
+ // TODO: could be made nicer with profile images
+ return object_url;
+}
+
+const QString APActor::get_plain_render() {
+ return object_url;
+}
+
+QString APActorList::get_html_render(HtmlRenderDetails render_info) {
+ // have something basic for the moment
+ QString text;
+
+ for (APActor elem : *this)
+ text.append(elem.get_plain_render() + ", ");
+
+ if (text.isEmpty())
+ text = "<em>(empty)</em>";
+ else
+ text.chop(2); // remove leftover ", "
+
+ return text;
+}
diff --git a/src/activitypub/apactor.h b/src/activitypub/apactor.h
new file mode 100644
index 0000000..60ef0bf
--- /dev/null
+++ b/src/activitypub/apactor.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "apbase.h"
+#include <vector>
+
+// APActors will have an avatar, display name, username and more that will be fetched using the remote instance
+class APActor : APBase {
+public:
+ // Empty actor
+ APActor();
+ APActor(const QString url);
+ ~APActor() {};
+
+ const QString get_url();
+
+ QString get_html_render(HtmlRenderDetails render_info);
+ const QString get_plain_render();
+private:
+ QString object_url;
+};
+
+class APActorList : public std::vector<APActor>, APBase {
+public:
+ QString get_html_render(HtmlRenderDetails render_info);
+};
diff --git a/src/activitypub/apattachment.cpp b/src/activitypub/apattachment.cpp
new file mode 100644
index 0000000..0589291
--- /dev/null
+++ b/src/activitypub/apattachment.cpp
@@ -0,0 +1,45 @@
+#include "apattachment.h"
+#include <QFileInfo>
+
+APAttachment::APAttachment() {}
+
+APAttachment::APAttachment(APAttachmentFields fields) {
+ path_url = fields.path;
+ type = fields.media_type;
+ description = fields.name;
+}
+
+QString APAttachment::get_html_render(HtmlRenderDetails info) {
+ QString html(get_html_template("apattachment"));
+
+ if (not path_url.isEmpty()) {
+ html.replace("{{path}}", path_url);
+ html.replace("{{filename}}", QFileInfo(path_url).fileName()); // FIXME: this is maybe ugly?
+ }
+
+ if (type.startsWith("image/"))
+ // dynamically resize image based on the display widget size to avoid horizontal scrolling
+ html.replace("{{img-width}}", QString::number((float)info.text_zone_width - (float)info.text_zone_width*0.15)); // finally after passing this for so long i finally use it
+ else
+ html.replace("{{img-width}}", "0");
+
+ html.replace("{{alt-text}}", description);
+
+ return html;
+}
+
+QString APAttachmentList::get_html_render(HtmlRenderDetails render_info) {
+ QString html;
+
+ int i = 1;
+ for (APAttachment attachment : *this) {
+ QString item_html(get_html_template("apattachmentlist_item"));
+ item_html.replace("{{id}}", QString::number(i));
+ item_html.replace("{{attachment}}", attachment.get_html_render(render_info));
+
+ html.append(item_html);
+ ++i;
+ }
+
+ return html;
+}
diff --git a/src/activitypub/apattachment.h b/src/activitypub/apattachment.h
new file mode 100644
index 0000000..c8cdda0
--- /dev/null
+++ b/src/activitypub/apattachment.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "apbase.h"
+#include <array>
+#include <vector>
+
+struct APAttachmentFields {
+ QString path;
+ QString filename; // "nicer" filename
+ QString media_type;
+ QString name;
+};
+
+class APAttachment : APBase {
+public:
+ APAttachment();
+ APAttachment(APAttachmentFields fields);
+ ~APAttachment() {};
+ QString get_html_render(HtmlRenderDetails render_info);
+
+private:
+ QString blurhash;
+ std::array<float, 2> focal_point = {0.,0.};
+ QString type; // MIME type, maps to "media_type"
+ QString description; // alt text, maps to "name"
+ QString path_url; // attachment URL, might be file on filesystem (make sure that the attachment dir has been found and that the path is correct)
+ QString filename; // nicer descriptor of the attachment's path
+};
+
+class APAttachmentList : public std::vector<APAttachment>, APBase {
+public:
+ QString get_html_render(HtmlRenderDetails render_info);
+};
diff --git a/src/activitypub/apbase.cpp b/src/activitypub/apbase.cpp
new file mode 100644
index 0000000..52c995d
--- /dev/null
+++ b/src/activitypub/apbase.cpp
@@ -0,0 +1,21 @@
+#include "apbase.h"
+#include <QHash>
+#include <QFile>
+#include <QDebug>
+
+const QString APBase::get_html_template(const QString& template_name) {
+ static QHash<QString, QString>* templates = new QHash<QString, QString>;
+
+ if (not templates->contains(template_name)) {
+ QFile file("src/templates/"+ template_name +".html");
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ templates->insert(template_name, "<i>Error loading status_info template.</i>");
+ return templates->value(template_name);
+ }
+
+ templates->insert(template_name, file.readAll());
+ qDebug() << "Loaded HTML template:" << template_name << "Total loaded:" << templates->size(); // print size to make sure that everything is loaded by the same function
+ }
+
+ return templates->value(template_name);
+}
diff --git a/src/activitypub/apbase.h b/src/activitypub/apbase.h
new file mode 100644
index 0000000..3973886
--- /dev/null
+++ b/src/activitypub/apbase.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <QString>
+#include <QLocale>
+
+struct HtmlRenderDetails {
+ int text_zone_width;
+ QLocale* locale;
+};
+
+class APBase {
+public:
+ // maybe pass HtmlRenderDetails by reference (pointer) instead of by value? will have to be measured
+ virtual QString get_html_render(HtmlRenderDetails render_info) = 0;
+
+ // having a virtual destructor never hurts. see https://isocpp.org/wiki/faq/virtual-functions#virtual-dtors
+ // it could always be removed by deleting the line below and removing the trivial destructors that are explicitly defined from classes that inherit from APBase.
+ virtual ~APBase() {};
+protected:
+ QString object_url; // this URL represents generally the source of the object. for APObject & derived: object's URL, such as the Note/Statuses' URL for an APPost
+ static const QString get_html_template(const QString& template_name);
+};
diff --git a/src/activitypub/apobject.cpp b/src/activitypub/apobject.cpp
new file mode 100644
index 0000000..28d01b4
--- /dev/null
+++ b/src/activitypub/apobject.cpp
@@ -0,0 +1,23 @@
+#include "apobject.h"
+#include "apbase.h"
+
+APObject::APObject() {}
+
+APObject::APObject(const QString& url) {
+ load_object_url(url);
+}
+
+void APObject::load_object_url(const QString& url) {
+ object_url = url;
+ // TODO: retrieve URL to build APObject.
+ // is it possible to change an object/class from one class type to another? (yeah, i can find a way with a member pointer of type APObject* that points to the right child class and have get_html_render call its get_html_render method when it's time to render)
+ // retriveving would have to be done async and disk/memory cache will have to be checked
+}
+
+const QString APObject::get_object_url() {
+ return object_url;
+}
+
+QString APObject::get_html_render(HtmlRenderDetails render_info) {
+ return QString("<i>Note:</i> object (%1) isn't ready to be rendered as it hasn't been retrieved and built.").arg(object_url);
+}
diff --git a/src/activitypub/apobject.h b/src/activitypub/apobject.h
new file mode 100644
index 0000000..62e60c8
--- /dev/null
+++ b/src/activitypub/apobject.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "apbase.h"
+#include "apactor.h"
+#include "apattachment.h"
+#include "../types.h"
+#include <QDateTime>
+
+class APObject : protected APBase {
+public:
+ // An empty object
+ APObject();
+
+ // An object that will be fetched through the remote instance
+ APObject(const QString& url);
+ ~APObject() {};
+
+ // The object will be built from the fetched URL
+ void load_object_url(const QString& url);
+
+ const QString get_object_url();
+
+ virtual QString get_html_render(HtmlRenderDetails render_info);
+
+protected:
+ APActorList to_actors;
+ APActorList cc_actors;
+ APActor by_actor; // maps to "attributedTo"
+ StatusType visibility; // status_type
+
+ QString web_url; // URL that has something like a user accessible web user front-end
+ QString reply_to_url; // in reply to, this is not really a URL but a APPost that could be retrived. maybe have a method for creating that object?
+ QDateTime published; // publishing date
+
+ APAttachmentList attachments;
+};
diff --git a/src/activitypub/appost.cpp b/src/activitypub/appost.cpp
new file mode 100644
index 0000000..dadefe8
--- /dev/null
+++ b/src/activitypub/appost.cpp
@@ -0,0 +1,71 @@
+#include "appost.h"
+
+APPost::APPost(APObjectFields fields) {
+ for (QString actor_url : fields.to_actors)
+ to_actors.push_back(APActor(actor_url));
+ for (QString actor_url : fields.cc_actors)
+ cc_actors.push_back(APActor(actor_url));
+ by_actor = fields.by_actor;
+
+ object_url = fields.object_url;
+ web_url = fields.web_url;
+ reply_to_url = fields.reply_to_url;
+ published = QDateTime::fromString(fields.published, Qt::ISODate);
+
+ for (APAttachmentFields attachment : fields.attachments)
+ attachments.push_back(APAttachment(attachment));
+
+ languages = fields.languages;
+ content = fields.content;
+
+ if (not fields.summary.isEmpty()) {
+ is_sensitive = true;
+ summary = fields.summary;
+ }
+
+ visibility = fields.visibility;
+}
+
+QString APPost::get_html_status_languages() {
+ QString text;
+
+ for (auto lang : languages)
+ text.append(lang + ", ");
+
+ if (text.isEmpty())
+ text = "<em>(unknown)</em>";
+ else
+ text.chop(2); // remove leftover ", "
+
+ return text;
+}
+
+QString APPost::get_html_render(HtmlRenderDetails render_info) {
+ QString html(get_html_template("appost"));
+
+ if (published.isValid()) {
+ // TODO: add a UI setting for configuring the display of local time or UTC time.
+ // Using QLocale::toString() is forward compatible with Qt 6 as QDateTime::toString() will not return anymore a string in the system locale.
+ html.replace("{{published}}", render_info.locale->toString(published.toLocalTime()));
+ }
+
+ html.replace("{{url-id}}", object_url);
+
+ html.replace("{{to}}", to_actors.get_html_render(render_info));
+ html.replace("{{cc}}", cc_actors.get_html_render(render_info));
+ html.replace("{{by}}", by_actor.get_html_render(render_info));
+
+ QString summary_text = summary;
+ if (summary_text.isEmpty())
+ summary_text = "<em>(empty)</em>";
+ html.replace("{{summary}}", summary_text);
+
+ html.replace("{{attachment-div}}", attachments.get_html_render(render_info));
+
+ html.replace("{{url-status}}", web_url);
+
+ html.replace("{{content}}", content);
+ html.replace("{{lang}}", get_html_status_languages());
+
+ return html;
+}
diff --git a/src/activitypub/appost.h b/src/activitypub/appost.h
new file mode 100644
index 0000000..a8b99c5
--- /dev/null
+++ b/src/activitypub/appost.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "apobject.h"
+#include "src/activitypub/apbase.h"
+#include "src/types.h"
+#include <vector>
+
+// All these fields (including APAttachmentFields, etc. in other files) are made to map to the fields present in the ActivityStream JSON's "object" for very easy construction.
+// There are additional fields that we don't use so they are not included in the objects (including parent and APActivity).
+
+struct APObjectFields {
+ QStringList to_actors; // Start APObject
+ QStringList cc_actors;
+ QString by_actor;
+ QString object_url;
+ QString web_url;
+ QString reply_to_url;
+ QString published;
+ std::vector<APAttachmentFields> attachments; // End APObject
+ QStringList languages; // This is taken from "content_map"
+ QString content;
+ QString summary;
+ StatusType visibility; // status type discovery is better left to the archive/API parser as this can very between fedi implementations and archive formats
+};
+
+// APPost represents an ActivityPub Note Object
+class APPost : public APObject {
+public:
+ APPost();
+ // A post that will be built from strings, including attachments
+ APPost(APObjectFields fields);
+ ~APPost() {};
+
+ QString get_html_render(HtmlRenderDetails render_info);
+
+protected:
+ bool is_sensitive = false;
+ QString summary; // content warning
+ QStringList languages;
+ QString content;
+
+ QString get_html_status_languages();
+};
diff --git a/src/activitypub/apreblog.cpp b/src/activitypub/apreblog.cpp
new file mode 100644
index 0000000..a6c6c3b
--- /dev/null
+++ b/src/activitypub/apreblog.cpp
@@ -0,0 +1,13 @@
+#include "apreblog.h"
+
+APReblog::APReblog(APReblogFields fields) {
+ object.load_object_url(fields.object); // might have to be fixed
+ visibility = fields.visibility;
+}
+
+QString APReblog::get_html_render(HtmlRenderDetails info) {
+ QString text = "Reblog of this object: <a href=\"{{url}}\">{{url}}</a><hr/>";
+ text.replace("{{url}}", object.get_object_url());
+ text.append(object.get_html_render(info));
+ return text;
+}
diff --git a/src/activitypub/apreblog.h b/src/activitypub/apreblog.h
new file mode 100644
index 0000000..cc340b8
--- /dev/null
+++ b/src/activitypub/apreblog.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "apobject.h"
+#include "apobject.h"
+#include "src/types.h"
+#include <vector>
+
+struct APReblogFields {
+ QString object; // the object's URL that was reblogged
+ StatusType visibility; // you can have non-public visibility of reblogs on Mastodon
+};
+
+// APReblog represents an ActivityPub Announce activity, which is a kind of object in a sense. This class is really used just for light bookeeping.
+// The visibility is just the visibility (to & cc) of the APActivity it came from.
+class APReblog : public APObject {
+public:
+ APReblog();
+ // A post that will be built from strings
+ APReblog(APReblogFields fields);
+ ~APReblog() {};
+
+ QString get_html_render(HtmlRenderDetails render_info);
+
+protected:
+ APObject object; // the object that was reblogged (impossible to be an APReblog)
+ StatusType visibility;
+};