]> git.sur5r.net Git - minitube/blob - lib/js/js.cpp
New upstream version 3.9.1
[minitube] / lib / js / js.cpp
1 #include "js.h"
2
3 #include "cachedhttp.h"
4
5 namespace {
6 Http &cachedHttp() {
7     static Http *h = [] {
8         CachedHttp *cachedHttp = new CachedHttp(Http::instance(), "js");
9         cachedHttp->setMaxSeconds(3600);
10         // Avoid expiring the cached js
11         cachedHttp->setMaxSize(0);
12
13         cachedHttp->getValidators().insert("application/javascript", [](const auto &reply) -> bool {
14             return !reply.body().isEmpty();
15         });
16
17         return cachedHttp;
18     }();
19     return *h;
20 }
21 } // namespace
22
23 JS &JS::instance() {
24     static thread_local JS i;
25     return i;
26 }
27
28 JS::JS(QObject *parent) : QObject(parent), engine(nullptr) {}
29
30 void JS::initialize(const QUrl &url) {
31     this->url = url;
32     initialize();
33 }
34
35 bool JS::checkError(const QJSValue &value) {
36     if (value.isError()) {
37         qWarning() << "Error" << value.toString();
38         qDebug() << value.property("stack").toString().splitRef('\n');
39         return true;
40     }
41     return false;
42 }
43
44 bool JS::isInitialized() {
45     if (ready) return true;
46     initialize();
47     return false;
48 }
49
50 JSResult &JS::callFunction(JSResult *result, const QString &name, const QJSValueList &args) {
51     if (!isInitialized()) {
52         qDebug() << "Not initialized";
53         QTimer::singleShot(1000, this,
54                            [this, result, name, args] { callFunction(result, name, args); });
55         return *result;
56     }
57
58     resetNAM();
59
60     auto function = engine->evaluate(name);
61     if (!function.isCallable()) {
62         qWarning() << function.toString() << " is not callable";
63         QTimer::singleShot(0, result, [result, function] { result->setError(function); });
64         return *result;
65     }
66
67     auto args2 = args;
68     args2.prepend(engine->newQObject(result));
69     qDebug() << "Calling" << function.toString();
70     auto v = function.call(args2);
71     if (checkError(v)) QTimer::singleShot(0, result, [result, v] { result->setError(v); });
72
73     return *result;
74 }
75
76 void JS::resetNAM() {
77     class MyCookieJar : public QNetworkCookieJar {
78         bool insertCookie(const QNetworkCookie &cookie) {
79             if (cookie.name().contains("CONSENT")) {
80                 qDebug() << "Fixing CONSENT cookie" << cookie;
81                 auto cookie2 = cookie;
82                 cookie2.setValue(cookie.value().replace("PENDING", "YES"));
83                 return QNetworkCookieJar::insertCookie(cookie2);
84             }
85             return QNetworkCookieJar::insertCookie(cookie);
86         }
87     };
88
89     auto nam = getEngine().networkAccessManager();
90     nam->clearAccessCache();
91     nam->setCookieJar(new MyCookieJar());
92 }
93
94 void JS::initialize() {
95     if (url.isEmpty()) {
96         qDebug() << "No js url set";
97         return;
98     }
99
100     if (initializing) return;
101     initializing = true;
102     qDebug() << "Initializing";
103
104     if (engine) engine->deleteLater();
105     engine = new QQmlEngine(this);
106     engine->setNetworkAccessManagerFactory(&namFactory);
107     engine->globalObject().setProperty("global", engine->globalObject());
108
109     QJSValue timer = engine->newQObject(new JSTimer(engine));
110     engine->globalObject().setProperty("setTimeoutQt", timer.property("setTimeout"));
111     QJSValue setTimeoutWrapperFunction =
112             engine->evaluate("function setTimeout(cb, delay) {"
113                              "const args = Array.prototype.slice.call(arguments, 2);"
114                              "return setTimeoutQt(cb, delay, args);"
115                              "}");
116     checkError(setTimeoutWrapperFunction);
117     engine->globalObject().setProperty("clearTimeout", timer.property("clearTimeout"));
118
119     connect(cachedHttp().get(url), &HttpReply::finished, this, [this](auto &reply) {
120         if (!reply.isSuccessful()) {
121             emit initFailed("Cannot load JS");
122             qDebug() << "Cannot load JS";
123             initializing = false;
124             return;
125         }
126         auto value = engine->evaluate(reply.body());
127         if (!checkError(value)) {
128             qDebug() << "Initialized";
129             ready = true;
130             emit initialized();
131         }
132         initializing = false;
133     });
134 }