]> git.sur5r.net Git - minitube/blob - src/yt/invidious/invidious.cpp
New upstream version 3.8
[minitube] / src / yt / invidious / invidious.cpp
1 #include "invidious.h"
2
3 #include "cachedhttp.h"
4 #include "http.h"
5 #include "httputils.h"
6 #include "jsfunctions.h"
7 #include "throttledhttp.h"
8
9 #ifdef APP_EXTRA
10 #include "extra.h"
11 #endif
12
13 namespace {
14 QStringList fallbackServers{"https://invidious.snopyta.org"};
15 QStringList preferredServers;
16
17 void shuffle(QStringList &sl) {
18     std::shuffle(sl.begin(), sl.end(), *QRandomGenerator::global());
19 }
20
21 } // namespace
22
23 Invidious &Invidious::instance() {
24     static Invidious i;
25     return i;
26 }
27
28 Http &Invidious::http() {
29     static Http *h = [] {
30         Http *http = new Http;
31         http->addRequestHeader("User-Agent", HttpUtils::stealthUserAgent());
32         http->setMaxRetries(0);
33         return http;
34     }();
35     return *h;
36 }
37
38 Http &Invidious::cachedHttp() {
39     static Http *h = [] {
40         ThrottledHttp *throttledHttp = new ThrottledHttp(http());
41         throttledHttp->setMilliseconds(500);
42
43         CachedHttp *cachedHttp = new CachedHttp(*throttledHttp, "iv");
44         cachedHttp->setMaxSeconds(3600);
45         cachedHttp->setIgnoreHostname(true);
46
47         cachedHttp->getValidators().insert("application/json", [](const auto &reply) -> bool {
48             const auto body = reply.body();
49             if (body.isEmpty() || body == "[]" || body == "{}") return false;
50             return true;
51         });
52
53         return cachedHttp;
54     }();
55     return *h;
56 }
57
58 Invidious::Invidious(QObject *parent) : QObject(parent) {
59
60 }
61
62 void Invidious::initServers() {
63     if (!servers.isEmpty()) shuffleServers();
64
65     QString instanceApi = JsFunctions::instance()->string("ivInstances()");
66     if (instanceApi.isEmpty()) {
67         qDebug() << "No instances API url";
68         return;
69     }
70
71     if (initializing) {
72         qDebug() << "Already initializing";
73         return;
74     }
75     initializing = true;
76     servers.clear();
77
78     preferredServers = JsFunctions::instance()->stringArray("ivPreferred()");
79 #ifdef APP_EXTRA
80     preferredServers << Extra::extraFunctions()->stringArray("ivPreferred()");
81 #endif
82     shuffle(preferredServers);
83     servers << preferredServers;
84
85     auto newFallbackServers = JsFunctions::instance()->stringArray("ivFallback()");
86     if (!newFallbackServers.isEmpty()) {
87         fallbackServers = newFallbackServers;
88         shuffle(fallbackServers);
89     }
90
91     auto reply = cachedHttp().get(instanceApi);
92     connect(reply, &HttpReply::finished, this, [this](auto &reply) {
93         if (reply.isSuccessful()) {
94             QSettings settings;
95             QStringList keywords = settings.value("recentKeywords").toStringList();
96             QString testKeyword = keywords.isEmpty() ? "test" : keywords.first();
97
98             QJsonDocument doc = QJsonDocument::fromJson(reply.body());
99             for (const auto &v : doc.array()) {
100                 auto serverArray = v.toArray();
101                 QString host = serverArray.first().toString();
102                 QJsonObject serverObj = serverArray.at(1).toObject();
103                 if (serverObj["type"] == "https") {
104                     QString url = "https://" + host;
105                     QUrl testUrl(url + "/api/v1/search?q=" + testKeyword);
106                     auto reply = http().get(testUrl);
107                     connect(reply, &HttpReply::finished, this, [this, url](auto &reply) {
108                         if (reply.isSuccessful()) {
109                             QJsonDocument doc = QJsonDocument::fromJson(reply.body());
110                             if (doc.array().isEmpty()) {
111                                 qDebug() << "Empty results" << url;
112                             } else {
113                                 qDebug() << "Adding" << url;
114                                 servers << url;
115                                 initializing = false;
116                             }
117                         }
118                     });
119                 }
120             }
121         }
122     });
123 }
124
125 void Invidious::shuffleServers() {
126     shuffle(servers);
127 }
128
129 QString Invidious::baseUrl() {
130     QString host;
131     if (servers.isEmpty()) {
132         if (preferredServers.isEmpty()) {
133             if (!fallbackServers.isEmpty()) host = fallbackServers.first();
134         } else
135             host = preferredServers.first();
136     } else
137         host = servers.first();
138
139     QString url = host + QLatin1String("/api/v1/");
140     return url;
141 }
142
143 QUrl Invidious::method(const QString &name) {
144     QString base = baseUrl();
145     if (base.isEmpty()) return QUrl();
146     QUrl url(baseUrl() + name);
147     return url;
148 }