mirror of
https://github.com/MatsuriDayo/nekoray.git
synced 2025-12-17 20:44:38 +03:00
feat: hook.js
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -22,7 +22,7 @@ tags
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.directory
|
.directory
|
||||||
*.debug
|
*.debug
|
||||||
Makefile*
|
/Makefile*
|
||||||
*.prl
|
*.prl
|
||||||
*.app
|
*.app
|
||||||
moc_*.cpp
|
moc_*.cpp
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +1,6 @@
|
|||||||
[submodule "3rdparty/QHotkey"]
|
[submodule "3rdparty/QHotkey"]
|
||||||
path = 3rdparty/QHotkey
|
path = 3rdparty/QHotkey
|
||||||
url = https://github.com/Skycoder42/QHotkey.git
|
url = https://github.com/Skycoder42/QHotkey.git
|
||||||
|
[submodule "3rdparty/qjs"]
|
||||||
|
path = 3rdparty/qjs
|
||||||
|
url = https://github.com/MatsuriDayo/qjs
|
||||||
|
|||||||
1
3rdparty/qjs
vendored
Submodule
1
3rdparty/qjs
vendored
Submodule
Submodule 3rdparty/qjs added at 578534fb41
@@ -69,6 +69,15 @@ if (NKR_NO_EXTERNAL)
|
|||||||
set(NKR_NO_YAML 1)
|
set(NKR_NO_YAML 1)
|
||||||
set(NKR_NO_ZXING 1)
|
set(NKR_NO_ZXING 1)
|
||||||
set(NKR_NO_QHOTKEY 1)
|
set(NKR_NO_QHOTKEY 1)
|
||||||
|
set(NKR_NO_QUICKJS 1)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# quickjs (static submodule)
|
||||||
|
if (NKR_NO_QUICKJS)
|
||||||
|
nkr_add_compile_definitions(NKR_NO_QUICKJS)
|
||||||
|
else ()
|
||||||
|
add_subdirectory(3rdparty/qjs)
|
||||||
|
list(APPEND NKR_EXTERNAL_TARGETS quickjs)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# grpc
|
# grpc
|
||||||
@@ -124,6 +133,7 @@ set(PROJECT_SOURCES
|
|||||||
main/main.cpp
|
main/main.cpp
|
||||||
main/NekoRay.cpp
|
main/NekoRay.cpp
|
||||||
main/NekoRay_Utils.cpp
|
main/NekoRay_Utils.cpp
|
||||||
|
main/QJS.cpp
|
||||||
|
|
||||||
3rdparty/base64.cpp
|
3rdparty/base64.cpp
|
||||||
3rdparty/qrcodegen.cpp
|
3rdparty/qrcodegen.cpp
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "db/Database.hpp"
|
#include "db/Database.hpp"
|
||||||
#include "fmt/includes.h"
|
#include "fmt/includes.h"
|
||||||
#include "fmt/Preset.hpp"
|
#include "fmt/Preset.hpp"
|
||||||
|
#include "main/QJS.hpp"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
@@ -12,10 +13,26 @@ namespace NekoRay {
|
|||||||
// Common
|
// Common
|
||||||
|
|
||||||
QSharedPointer<BuildConfigResult> BuildConfig(const QSharedPointer<ProxyEntity> &ent, bool forTest, bool forExport) {
|
QSharedPointer<BuildConfigResult> BuildConfig(const QSharedPointer<ProxyEntity> &ent, bool forTest, bool forExport) {
|
||||||
|
QSharedPointer<BuildConfigResult> result;
|
||||||
if (IS_NEKO_BOX) {
|
if (IS_NEKO_BOX) {
|
||||||
return BuildConfigSingBox(ent, forTest, forExport);
|
result = BuildConfigSingBox(ent, forTest, forExport);
|
||||||
|
} else {
|
||||||
|
result = BuildConfigV2Ray(ent, forTest, forExport);
|
||||||
}
|
}
|
||||||
return BuildConfigV2Ray(ent, forTest, forExport);
|
// hook.js
|
||||||
|
if (result->error.isEmpty()) {
|
||||||
|
auto source = qjs::ReadHookJS();
|
||||||
|
if (!source.isEmpty()) {
|
||||||
|
qjs::QJS js(source);
|
||||||
|
auto js_result = js.EvalFunction("hook.hook_core_config", QJsonObject2QString(result->coreConfig, true));
|
||||||
|
auto js_result_json = QString2QJsonObject(js_result);
|
||||||
|
if (!js_result_json.isEmpty() && result->coreConfig != js_result_json) {
|
||||||
|
MW_show_log("hook.js modified your " + software_core_name + " json config.");
|
||||||
|
result->coreConfig = js_result_json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString BuildChain(int chainId, const QSharedPointer<BuildConfigStatus> &status) {
|
QString BuildChain(int chainId, const QSharedPointer<BuildConfigStatus> &status) {
|
||||||
@@ -878,6 +895,16 @@ namespace NekoRay {
|
|||||||
.replace("%TUN_NAME%", tun_name)
|
.replace("%TUN_NAME%", tun_name)
|
||||||
.replace("%STRICT_ROUTE%", dataStore->vpn_strict_route ? "true" : "false")
|
.replace("%STRICT_ROUTE%", dataStore->vpn_strict_route ? "true" : "false")
|
||||||
.replace("%PORT%", Int2String(dataStore->inbound_socks_port));
|
.replace("%PORT%", Int2String(dataStore->inbound_socks_port));
|
||||||
|
// hook.js
|
||||||
|
auto source = qjs::ReadHookJS();
|
||||||
|
if (!source.isEmpty()) {
|
||||||
|
qjs::QJS js(source);
|
||||||
|
auto js_result = js.EvalFunction("hook.hook_vpn_config", config);
|
||||||
|
if (config != js_result) {
|
||||||
|
MW_show_log("hook.js modified your VPN config.");
|
||||||
|
config = js_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
// write config
|
// write config
|
||||||
QFile file;
|
QFile file;
|
||||||
file.setFileName(QFileInfo(configFn).fileName());
|
file.setFileName(QFileInfo(configFn).fileName());
|
||||||
@@ -897,6 +924,16 @@ namespace NekoRay {
|
|||||||
.replace("$PROTECT_LISTEN_PATH", protectPath)
|
.replace("$PROTECT_LISTEN_PATH", protectPath)
|
||||||
.replace("$CONFIG_PATH", configPath)
|
.replace("$CONFIG_PATH", configPath)
|
||||||
.replace("$TABLE_FWMARK", "514");
|
.replace("$TABLE_FWMARK", "514");
|
||||||
|
// hook.js
|
||||||
|
auto source = qjs::ReadHookJS();
|
||||||
|
if (!source.isEmpty()) {
|
||||||
|
qjs::QJS js(source);
|
||||||
|
auto js_result = js.EvalFunction("hook.hook_vpn_script", script);
|
||||||
|
if (script != js_result) {
|
||||||
|
MW_show_log("hook.js modified your VPN script.");
|
||||||
|
script = js_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
// write script
|
// write script
|
||||||
QFile file2;
|
QFile file2;
|
||||||
file2.setFileName(QFileInfo(scriptFn).fileName());
|
file2.setFileName(QFileInfo(scriptFn).fileName());
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ namespace NekoRay {
|
|||||||
_add(new configItem("sp_format", &system_proxy_format, itemType::string));
|
_add(new configItem("sp_format", &system_proxy_format, itemType::string));
|
||||||
_add(new configItem("sub_clear", &sub_clear, itemType::boolean));
|
_add(new configItem("sub_clear", &sub_clear, itemType::boolean));
|
||||||
_add(new configItem("sub_insecure", &sub_insecure, itemType::boolean));
|
_add(new configItem("sub_insecure", &sub_insecure, itemType::boolean));
|
||||||
|
_add(new configItem("enable_js_hook", &enable_js_hook, itemType::boolean));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataStore::UpdateStartedId(int id) {
|
void DataStore::UpdateStartedId(int id) {
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ namespace NekoRay {
|
|||||||
// Security
|
// Security
|
||||||
bool insecure_hint = true;
|
bool insecure_hint = true;
|
||||||
bool skip_cert = false;
|
bool skip_cert = false;
|
||||||
|
bool enable_js_hook = false;
|
||||||
|
|
||||||
// Remember
|
// Remember
|
||||||
int remember_spmode = NekoRay::SystemProxyMode::DISABLE;
|
int remember_spmode = NekoRay::SystemProxyMode::DISABLE;
|
||||||
|
|||||||
143
main/QJS.cpp
Normal file
143
main/QJS.cpp
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
#include "QJS.hpp"
|
||||||
|
|
||||||
|
#include "3rdparty/qjs/nekoray_qjs.h"
|
||||||
|
#include "main/NekoRay.hpp"
|
||||||
|
|
||||||
|
namespace NekoRay::qjs {
|
||||||
|
#ifndef NKR_NO_QUICKJS
|
||||||
|
namespace exception {
|
||||||
|
static void js_dump_obj(JSContext *ctx, QString &out, JSValueConst val) {
|
||||||
|
const char *str;
|
||||||
|
|
||||||
|
str = JS_ToCString(ctx, val);
|
||||||
|
if (str) {
|
||||||
|
out.append(str);
|
||||||
|
out.append('\n');
|
||||||
|
JS_FreeCString(ctx, str);
|
||||||
|
} else {
|
||||||
|
out += "[exception]\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_std_dump_error1(JSContext *ctx, QString &out, JSValueConst exception_val) {
|
||||||
|
JSValue val;
|
||||||
|
auto is_error = JS_IsError(ctx, exception_val);
|
||||||
|
js_dump_obj(ctx, out, exception_val);
|
||||||
|
if (is_error) {
|
||||||
|
val = JS_GetPropertyStr(ctx, exception_val, "stack");
|
||||||
|
if (!JS_IsUndefined(val)) {
|
||||||
|
js_dump_obj(ctx, out, val);
|
||||||
|
}
|
||||||
|
JS_FreeValue(ctx, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString js_std_dump_error(JSContext *ctx) {
|
||||||
|
QString result;
|
||||||
|
JSValue exception_val;
|
||||||
|
|
||||||
|
exception_val = JS_GetException(ctx);
|
||||||
|
js_std_dump_error1(ctx, result, exception_val);
|
||||||
|
JS_FreeValue(ctx, exception_val);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace exception
|
||||||
|
|
||||||
|
JSValue func_log(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
|
QString qString;
|
||||||
|
|
||||||
|
int i;
|
||||||
|
const char *str;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
for (i = 0; i < argc; i++) {
|
||||||
|
if (i != 0) qString.append(' ');
|
||||||
|
str = JS_ToCStringLen(ctx, &len, argv[i]);
|
||||||
|
if (!str)
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
qString.append(str);
|
||||||
|
JS_FreeCString(ctx, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
MW_show_log(qString);
|
||||||
|
qDebug() << "func_log:" << qString;
|
||||||
|
|
||||||
|
return JS_UNDEFINED;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define NEKO_CTX ((nekoray_qjs_context *) this->neko_ctx)
|
||||||
|
|
||||||
|
QJS::QJS() {
|
||||||
|
#ifndef NKR_NO_QUICKJS
|
||||||
|
MW_show_log("loading quickjs......");
|
||||||
|
//
|
||||||
|
this->neko_ctx = malloc(sizeof(nekoray_qjs_context));
|
||||||
|
nekoray_qjs_new_arg arg;
|
||||||
|
arg.neko_ctx = NEKO_CTX;
|
||||||
|
arg.func_log = func_log;
|
||||||
|
nekoray_qjs_new(arg);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QJS::QJS(const QByteArray &jsSource) : QJS() {
|
||||||
|
this->Eval(jsSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJS::~QJS() {
|
||||||
|
#ifndef NKR_NO_QUICKJS
|
||||||
|
nekoray_qjs_free(NEKO_CTX);
|
||||||
|
free(this->neko_ctx);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QJS::Eval(const QByteArray &jsSource) const {
|
||||||
|
#ifndef NKR_NO_QUICKJS
|
||||||
|
auto result = nekoray_qjs_eval(NEKO_CTX, jsSource.data(), jsSource.length());
|
||||||
|
if (JS_IsException(result)) {
|
||||||
|
MW_show_log(exception::js_std_dump_error(NEKO_CTX->ctx));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto cString = JS_ToCString(NEKO_CTX->ctx, result);
|
||||||
|
QString qString(cString);
|
||||||
|
JS_FreeCString(NEKO_CTX->ctx, cString);
|
||||||
|
JS_FreeValue(NEKO_CTX->ctx, result);
|
||||||
|
return qString;
|
||||||
|
#else
|
||||||
|
return {};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QJS::Eval(const QString &jsSource) const {
|
||||||
|
return this->Eval(jsSource.toUtf8());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QJS::EvalFile(const QString &jsPath) const {
|
||||||
|
return this->Eval(ReadFile(jsPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QJS::EvalFunction(const QString &funcName, const QString &arg) const {
|
||||||
|
#ifndef NKR_NO_QUICKJS
|
||||||
|
auto ba1 = arg.toUtf8();
|
||||||
|
JSValue globalObj = JS_GetGlobalObject(NEKO_CTX->ctx);
|
||||||
|
JSValue tempObj = JS_NewStringLen(NEKO_CTX->ctx, ba1.data(), ba1.length());
|
||||||
|
JS_SetPropertyStr(NEKO_CTX->ctx, globalObj, "tempObj", tempObj);
|
||||||
|
auto result = this->Eval(QString("%1(tempObj)").arg(funcName));
|
||||||
|
JS_DeleteProperty(NEKO_CTX->ctx, globalObj, JS_NewAtom(NEKO_CTX->ctx, "tempObj"), 1); // Free tempObj
|
||||||
|
JS_FreeValue(NEKO_CTX->ctx, globalObj);
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
|
return {};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray ReadHookJS() {
|
||||||
|
#ifndef NKR_NO_QUICKJS
|
||||||
|
if (NekoRay::dataStore->enable_js_hook) {
|
||||||
|
return ReadFile(QString("./hook.%1.js").arg(software_name.toLower()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} // namespace NekoRay::qjs
|
||||||
23
main/QJS.hpp
Normal file
23
main/QJS.hpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class QByteArray;
|
||||||
|
class QString;
|
||||||
|
|
||||||
|
namespace NekoRay::qjs {
|
||||||
|
class QJS {
|
||||||
|
public:
|
||||||
|
QJS();
|
||||||
|
explicit QJS(const QByteArray &jsSource);
|
||||||
|
~QJS();
|
||||||
|
|
||||||
|
QString Eval(const QByteArray &jsSource) const;
|
||||||
|
QString Eval(const QString &jsSource) const;
|
||||||
|
QString EvalFile(const QString &jsPath) const;
|
||||||
|
QString EvalFunction(const QString &funcName, const QString &arg) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void *neko_ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
QByteArray ReadHookJS();
|
||||||
|
} // namespace NekoRay::qjs
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "db/ProfileFilter.hpp"
|
#include "db/ProfileFilter.hpp"
|
||||||
#include "fmt/includes.h"
|
#include "fmt/includes.h"
|
||||||
#include "fmt/Preset.hpp"
|
#include "fmt/Preset.hpp"
|
||||||
|
#include "main/QJS.hpp"
|
||||||
|
|
||||||
#include "GroupUpdater.hpp"
|
#include "GroupUpdater.hpp"
|
||||||
|
|
||||||
@@ -394,6 +395,17 @@ namespace NekoRay::sub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hook.js
|
||||||
|
auto source = qjs::ReadHookJS();
|
||||||
|
if (!source.isEmpty()) {
|
||||||
|
qjs::QJS js(source);
|
||||||
|
auto js_result = js.EvalFunction("hook.hook_import", content);
|
||||||
|
if (content != js_result) {
|
||||||
|
MW_show_log("hook.js modified your import content.");
|
||||||
|
content = js_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 解析并添加 profile
|
// 解析并添加 profile
|
||||||
rawUpdater->update(content);
|
rawUpdater->update(content);
|
||||||
|
|
||||||
|
|||||||
@@ -203,6 +203,10 @@
|
|||||||
<source>Ignore TLS errors when updating subscription</source>
|
<source>Ignore TLS errors when updating subscription</source>
|
||||||
<translation>更新订阅时忽略 TLS 错误</translation>
|
<translation>更新订阅时忽略 TLS 错误</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Enable hook.js</source>
|
||||||
|
<translation>启用 hook.js 功能</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>DialogEditGroup</name>
|
<name>DialogEditGroup</name>
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent)
|
|||||||
|
|
||||||
D_LOAD_BOOL(insecure_hint)
|
D_LOAD_BOOL(insecure_hint)
|
||||||
D_LOAD_BOOL(skip_cert)
|
D_LOAD_BOOL(skip_cert)
|
||||||
|
D_LOAD_BOOL(enable_js_hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
DialogBasicSettings::~DialogBasicSettings() {
|
DialogBasicSettings::~DialogBasicSettings() {
|
||||||
@@ -278,6 +279,7 @@ void DialogBasicSettings::accept() {
|
|||||||
|
|
||||||
D_SAVE_BOOL(insecure_hint)
|
D_SAVE_BOOL(insecure_hint)
|
||||||
D_SAVE_BOOL(skip_cert)
|
D_SAVE_BOOL(skip_cert)
|
||||||
|
D_SAVE_BOOL(enable_js_hook)
|
||||||
|
|
||||||
MW_dialog_message(Dialog_DialogBasicSettings, "UpdateDataStore");
|
MW_dialog_message(Dialog_DialogBasicSettings, "UpdateDataStore");
|
||||||
QDialog::accept();
|
QDialog::accept();
|
||||||
|
|||||||
@@ -578,6 +578,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="enable_js_hook">
|
||||||
|
<property name="text">
|
||||||
|
<string>Enable hook.js</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
|
|||||||
Reference in New Issue
Block a user