aboutsummaryrefslogtreecommitdiffstats
path: root/src/activitypub
diff options
context:
space:
mode:
authorConfuSomu2023-07-05 15:14:46 +0200
committerConfuSomu2023-07-05 15:14:46 +0200
commite515ca109faa897e84be8a97a1240c59df612dd4 (patch)
tree2a5e0593bb4387449a9a60fb2aefcc74779b5c67 /src/activitypub
parent0dfab2456b2dcd33f1879ac7d5606dd65cdb8e40 (diff)
downloadActorViewer-e515ca109faa897e84be8a97a1240c59df612dd4.tar
ActorViewer-e515ca109faa897e84be8a97a1240c59df612dd4.tar.gz
ActorViewer-e515ca109faa897e84be8a97a1240c59df612dd4.zip
Implement a small library of ActivityPub objects
These objects allow, and will allow us, to move HTML rendering out of the archive parser and into separate classes. Each of the derived classes specialise HTML rendering for their specific requirements. Furthermore, these ActivityPub objects will be able to be expanded upon and have support to be written to disk, in a database, for instance. Separating ActivityPub object retrieving from rendering allows us to implement other retrieving sources and methods, such as downloading posts from a configured remote instance.
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;
+};