diff options
Diffstat (limited to 'src/activitypub')
-rw-r--r-- | src/activitypub/apactivity.cpp | 46 | ||||
-rw-r--r-- | src/activitypub/apactivity.h | 45 | ||||
-rw-r--r-- | src/activitypub/apactor.cpp | 35 | ||||
-rw-r--r-- | src/activitypub/apactor.h | 25 | ||||
-rw-r--r-- | src/activitypub/apattachment.cpp | 45 | ||||
-rw-r--r-- | src/activitypub/apattachment.h | 33 | ||||
-rw-r--r-- | src/activitypub/apbase.cpp | 21 | ||||
-rw-r--r-- | src/activitypub/apbase.h | 22 | ||||
-rw-r--r-- | src/activitypub/apobject.cpp | 23 | ||||
-rw-r--r-- | src/activitypub/apobject.h | 36 | ||||
-rw-r--r-- | src/activitypub/appost.cpp | 71 | ||||
-rw-r--r-- | src/activitypub/appost.h | 43 | ||||
-rw-r--r-- | src/activitypub/apreblog.cpp | 13 | ||||
-rw-r--r-- | src/activitypub/apreblog.h | 27 |
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; +}; |