File indexing completed on 2024-12-29 04:50:00

0001 /*
0002     SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #include "activitypubextractor.h"
0007 
0008 #include "json/jsonldfilterengine.h"
0009 
0010 #include <KItinerary/ExtractorDocumentNode>
0011 #include <KItinerary/ExtractorResult>
0012 
0013 #include <QJsonArray>
0014 #include <QJsonObject>
0015 
0016 using namespace KItinerary;
0017 
0018 ActivityPubExtractor::ActivityPubExtractor() = default;
0019 ActivityPubExtractor::~ActivityPubExtractor() = default;
0020 
0021 QString ActivityPubExtractor::name() const
0022 {
0023     return QStringLiteral("<ActivityPub>");
0024 }
0025 
0026 static bool isActivityStreamsContext(const QJsonValue &context)
0027 {
0028   return context.isString() &&
0029          context.toString() ==
0030              QLatin1StringView("https://www.w3.org/ns/activitystreams");
0031 }
0032 
0033 bool ActivityPubExtractor::canHandle(const ExtractorDocumentNode &node) const
0034 {
0035     const auto array = node.content<QJsonArray>();
0036     for (const auto &v : array) {
0037         if (!v.isObject()) {
0038             continue;
0039         }
0040         const auto obj = v.toObject();
0041         const auto context = obj.value(QLatin1StringView("@context"));
0042         if (isActivityStreamsContext(context)) {
0043             return true;
0044         }
0045         if (context.isArray()) {
0046             const auto contexts = context.toArray();
0047             if (std::any_of(contexts.begin(), contexts.end(), isActivityStreamsContext)) {
0048                 return true;
0049             }
0050         }
0051     }
0052     return false;
0053 }
0054 
0055 static void convertPlace(QJsonObject &obj)
0056 {
0057   QJsonObject geo({
0058       {QLatin1StringView("@type"), QLatin1String("GeoCoordinates")},
0059       {QLatin1StringView("latitude"), obj.value(QLatin1String("latitude"))},
0060       {QLatin1StringView("longitude"), obj.value(QLatin1String("longitude"))},
0061   });
0062   obj.insert(QLatin1StringView("geo"), geo);
0063 }
0064 
0065 // filter functions applied to objects of the corresponding (already normalized) type
0066 // IMPORTANT: keep alphabetically sorted by type!
0067 static constexpr const JsonLdFilterEngine::TypeFilter type_filters[] = {
0068     { "Place", convertPlace },
0069 };
0070 
0071 // property mappings
0072 // IMPORTANT: keep alphabetically sorted by type!
0073 static constexpr const JsonLdFilterEngine::PropertyMapping property_mappings[] = {
0074     { "Event", "endTime", "endDate" },
0075     { "Event", "startTime", "startDate"},
0076 };
0077 
0078 // in theory we would need the whole JSON-LD schema-aware normalization here
0079 // in practice this is good enough and works
0080 // (should we ever need more, KHealthCertificate has some of that JSON-LD code)
0081 static QJsonValue convertActivityStreamObject(const QJsonValue &value)
0082 {
0083     if (!value.isObject()) {
0084         return value;
0085     }
0086 
0087     auto obj = value.toObject();
0088     for (auto it = obj.begin(); it != obj.end(); ++it) {
0089         if (it.value().isObject()) {
0090             (*it) = convertActivityStreamObject(it.value());
0091         }
0092     }
0093 
0094     if (const auto t = obj.value(QLatin1StringView("type")).toString();
0095         !t.isEmpty()) {
0096       obj.insert(QLatin1StringView("@type"), t);
0097     }
0098 
0099     JsonLdFilterEngine filterEngine;
0100     filterEngine.setTypeFilters(type_filters);
0101     filterEngine.setPropertyMappings(property_mappings);
0102     filterEngine.filterRecursive(obj);
0103 
0104     return obj;
0105 }
0106 
0107 ExtractorResult ActivityPubExtractor::extract(const ExtractorDocumentNode &node, [[maybe_unused]] const ExtractorEngine *engine) const
0108 {
0109     const auto array = node.content<QJsonArray>();
0110     QJsonArray result;
0111     std::transform(array.begin(), array.end(), std::back_inserter(result), convertActivityStreamObject);
0112     return result;
0113 }