aboutsummaryrefslogtreecommitdiffstats
path: root/src/archive_parser.cpp
blob: 603ffa7065fd342477bc82d93997b2dc011c48d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#include "archive_parser.h"
#include "src/list_item.h"
#include "src/types.h"

#include <QFile>
#include <QJsonParseError>
#include <QTextDocument>

Archive::Archive(QString outbox_filename, ArchiveType archive_type) :
    outbox_filename(outbox_filename), archive_type(archive_type) {}

Archive::InitError Archive::init() {
    QFile outbox_file(outbox_filename);

    if (!outbox_file.open(QIODevice::ReadOnly | QIODevice::Text))
        return FailedOpeningFile;

    QJsonParseError json_error;
    QJsonDocument outbox_json_document = QJsonDocument::fromJson(outbox_file.readAll(), &json_error);
    outbox_file.close();

    if (json_error.error != QJsonParseError::NoError)
        return JsonParseError;
    if (outbox_json_document.isEmpty())
        return JsonEmpty;
    if (outbox_json_document.isNull())
        return JsonNull;

    if (outbox_json_document.isObject())
        outbox_json = new QJsonObject (outbox_json_document.object());
    else
        return JsonNotObject;

    // Do some more throughful checks to make sure that the JSON is actually valid.
    if (not (outbox_json->contains("@context") and outbox_json->value("@context").isString() and
        (outbox_json->value("@context").toString() == "https://www.w3.org/ns/activitystreams")))
        return JsonNotActivityStream;
    if (outbox_json->contains("orderedItems") and outbox_json->value("orderedItems").isArray()) {
        outbox_items = new QJsonArray(outbox_json->value("orderedItems").toArray()); // we'll need it during Archive's lifetime
    }

    return NoError;
}

Archive::~Archive() {
    delete outbox_json; outbox_json = nullptr;
    delete outbox_items; outbox_items = nullptr;
}

bool is_status_type_allowed(StatusType status_type, ViewStatusTypes allowed_types) {
    switch (status_type) {
        case PUBLIC: return allowed_types.includePublic;
        case UNLISTED: return allowed_types.includeUnlisted;
        case PRIVATE: return allowed_types.includePrivate;
        case DIRECT: return allowed_types.includeDirect;
        default: return true;
    }
}

StatusType get_status_type(QJsonObject obj) {
   /*
    * public:
    *  to: #Public
    * unlisted:
    *  to: /followers
    *  cc: #Public
    * followers:
    *  to: /followers
    * direct:
    *  to: the Actors mentioned
    */

    StatusType status_type = UNKNOWN;
    bool is_private_or_unlisted = false;

    QJsonArray to;

    if (obj.value("to").isArray()) {

        to = obj.value("to").toArray();

        // see https://www.w3.org/TR/activitypub/#public-addressing
        for (auto collection : to) {
            QString col = collection.toString();
            if (col == "https://www.w3.org/ns/activitystreams#Public") {
                return PUBLIC; // status' privacy can only be promoted to a higher level
            }
            else if (col.endsWith("/followers")) // at least for Mastodon…
                is_private_or_unlisted = true; // private or unlisted
        }
    }

    if (obj.value("cc").isArray()) {
        QJsonArray cc = obj.value("cc").toArray();

        for (auto collection : cc) {
            QString col = collection.toString();
            if (col == "https://www.w3.org/ns/activitystreams#Public" and is_private_or_unlisted) {
                return UNLISTED; // status' privacy can only be promoted to a higher level
            }
        }
        if (is_private_or_unlisted)
            return PRIVATE;
        else if (to.size() > 0)
            return DIRECT;
        else if (to.size() == 0 and cc.size() == 0) // sending a direct message to yourself or to another actor that doesn't exist anymore
            return DIRECT;
    }

    return status_type;
}

void Archive::update_status_list(ViewStatusTypes allowed_types, QListWidget *parent) {
    int i = 0;
    for (auto&& item : *outbox_items) {
        if (item.isObject()){
            QJsonObject obj = item.toObject();

            if (not obj.value("type").isString()) // this shouldn't happen, but you never know
                goto next_item;
            if (obj.value("type").toString() != "Create") // ignoring everything that isn't a Create activity for now
                goto next_item;

            // Determine the status' type
            StatusType status_type = get_status_type(obj);
            if (not is_status_type_allowed(status_type, allowed_types))
                goto next_item;

            if (obj.value("object").isObject()) {
                QJsonObject activity = obj.value("object").toObject();

                if (activity.value("content").isString()) {
                    // Strip HTML for display in list, according to https://stackoverflow.com/a/12157835
                    QTextDocument strip_html;
                    strip_html.setHtml(activity.value("content").toString());

                    ListItem *item = new ListItem(strip_html.toPlainText(), status_type, parent, i);
                }
            }
        }
        next_item:
        ++i;
    }
}