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 }