aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/mastodon_instance.cpp
blob: 17d23cad1f2933e7214556f38fbb1084442701bb (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
#include "mastodon_instance.h"
#include "src/activitypub/appost.h"
#include "src/activitypub/apquestion.h"
#include "src/activitypub/fields.h"
#include "src/settings_interface.h"
#include "src/types.h"

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QUrl>
#include <QDebug>

APPostPtr post_from_json(const QJsonObject &status);

MastodonInstance::MastodonInstance() : instance(SettingsInterface::quick_read_setting<QString>("net/instance/address").toStdString(), SettingsInterface::quick_read_setting<QString>("net/instance/token").toStdString()), connection(instance) {

}

MastodonInstance::~MastodonInstance() {
    if (obtain_token)
        delete obtain_token;
}

QString MastodonInstance::oauth2_step1() {
    obtain_token = new mastodonpp::Instance::ObtainToken{instance};
    auto answer{obtain_token->step_1("ActorViewer", "read", "https://twilightsparkle.space")};
    if (answer)
        return answer.body.c_str();
    else
        return "";
}

Instance::OAuth2Step2 MastodonInstance::oauth2_step2(const QString &auth_code) {
    if (!obtain_token) return {false};
    auto answer{obtain_token->step_2(auth_code.toStdString())};
    QString token;

    if (answer) {
        token = answer.body.c_str();
    }

    delete obtain_token; obtain_token = nullptr;
    return {(bool)answer, token};
}

APPostPtr MastodonInstance::get_post_from_url(const QString &url) {
    auto answer{connection.get(mastodonpp::API::v2::search, {
        {"q", QUrl::toPercentEncoding(url).toStdString()},
        {"type", "statuses"},
        {"limit", "1"},
        {"resolve", "true"}
    })};

    if (answer) {
        QJsonDocument doc = QJsonDocument::fromJson(answer.body.c_str());
        QJsonObject obj = doc.object();
        qDebug() << doc;
        if (obj.contains("statuses")) {
            QJsonObject status = obj["statuses"].toArray().first().toObject();
            if (status.isEmpty()) return std::make_shared<APPost>(APObjectFields {.content="invalid"}); // Invalid post
            return post_from_json(status);
        }
    }

    return std::make_shared<APPost>(APObjectFields {.content="connection error"});
}

// Create a filled APPost object from a Status entity (https://docs.joinmastodon.org/entities/Status/)
// We have to return a pointer as else I can't get derived classes of APPost to render properly: the additional methods are ignored.
// Furthermore, using pointers removes unecessary copying, even if that can be reduced by return value optimization.
APPostPtr post_from_json(const QJsonObject &status) {
    APObjectFields fields;
    bool is_question = status.contains("poll");

    fields.by_actor = status["account"]["url"].toString();
    fields.reply_to_url = status["in_reply_to_id"].toString(); // id ≠ url

    fields.languages.append(status["language"].toString());
    fields.published = status["created_at"].toString();

    fields.object_url = status["uri"].toString();
    fields.web_url = status["url"].toString();

    if (status["sensitive"].toBool(true))
        fields.summary = status["spoiler_text"].toString();
    if (status.contains("content"))
        fields.content = status["content"].toString();

    if (status.contains("visibility")) {
        QString visibility = status["visibility"].toString();
        if (visibility == "public") fields.visibility = StatusType::PUBLIC;
        else if (visibility == "unlisted") fields.visibility = StatusType::UNLISTED;
        else if (visibility == "private") fields.visibility = StatusType::PRIVATE;
        else if (visibility == "direct") fields.visibility = StatusType::DIRECT;
        else fields.visibility = StatusType::UNKNOWN;
    }

    if (QJsonArray medias = status["media_attachments"].toArray(); not medias.isEmpty()) {
        for (QJsonValueRef media : medias) {
            if (not media.isObject()) continue;
            QJsonObject attrib = media.toObject();
            fields.attachments.push_back({
                // FIXME: we use the preview to lower bandwidth but it would be nicer to have APAttachment fields for differencing between qualities.
                .path = attrib["preview_url"].toString(),
                // FIXME: we don't have any better attribute to work with
                .filename = attrib["id"].toString(),
                .media_type = attrib["type"].toString(),
                .name = attrib["description"].toString()
                // TODO: we don't use the blurhash yet…
            });
        }
    }

    if (is_question) {
        QJsonObject poll = status["poll"].toObject();

        fields.question.end_time = poll["expires_at"].toString();
        fields.question.closed_time = poll["expires_at"].toString();
        fields.question.total_votes = poll["votes_count"].toInt();

        for (QJsonValueRef option : poll["options"].toArray()) {
            QJsonObject element = option.toObject();
            fields.question.poll_options.push_back({element["title"].toString(), element["votes_count"].toInt()});
        }


        // TODO: have an boolean attribute for defining if the poll is multiple choice or not, int for the voters_count and bool/int for `voted` and `own_votes`
    }

    if (is_question)
        return std::make_shared<APQuestion>(fields);
    else
        return std::make_shared<APPost>(fields);
}