mirror of
https://github.com/Qv2ray/Qv2ray.git
synced 2025-05-20 02:40:20 +08:00
[add] Added vmess:// shares and vmess:// QRCode import, support multiple lines vmess:// supported importing image
Former-commit-id: 4e72ecfe02
This commit is contained in:
parent
d999231321
commit
a18de6cfe1
@ -1 +1 @@
|
||||
485
|
||||
501
|
||||
|
@ -1,6 +1,7 @@
|
||||
#ifndef QV2RAYBASE_H
|
||||
#define QV2RAYBASE_H
|
||||
#include <QtCore>
|
||||
#include <QMap>
|
||||
#include "QvTinyLog.hpp"
|
||||
#include "QvCoreConfigObjects.hpp"
|
||||
#include "QvNetSpeedPlugin.hpp"
|
||||
|
@ -4,6 +4,7 @@ namespace Qv2ray
|
||||
{
|
||||
namespace ConfigOperations
|
||||
{
|
||||
|
||||
QMap<QString, QJsonObject> GetConnections(list<string> connectionNames)
|
||||
{
|
||||
QMap<QString, QJsonObject> list;
|
||||
@ -16,7 +17,6 @@ namespace Qv2ray
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
int StartPreparation(QJsonObject fullConfig)
|
||||
{
|
||||
// Writes the final configuration to the disk.
|
||||
@ -24,7 +24,6 @@ namespace Qv2ray
|
||||
StringToFile(&json, new QFile(QV2RAY_GENERATED_FILE_PATH));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FindIndexByTag(QJsonArray list, QString *tag)
|
||||
{
|
||||
for (int i = 0; i < list.count(); i++) {
|
||||
|
@ -34,6 +34,7 @@ namespace Qv2ray
|
||||
// VMess URI Protocol
|
||||
QJsonObject ConvertConfigFromVMessString(QString vmess, QString *alias, QString *errMessage);
|
||||
QJsonObject ConvertConfigFromFile(QString sourceFilePath, bool overrideInbounds);
|
||||
QString ConvertConfigToVMessString(const StreamSettingsObject &transfer, const VMessServerObject &serverConfig, const QString &alias);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -6,6 +6,45 @@ namespace Qv2ray
|
||||
{
|
||||
namespace Conversion
|
||||
{
|
||||
// From https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
|
||||
QString ConvertConfigToVMessString(const StreamSettingsObject &transfer, const VMessServerObject &serverConfig, const QString &alias)
|
||||
{
|
||||
QJsonObject vmessUriRoot;
|
||||
// Constant
|
||||
vmessUriRoot["v"] = 2;
|
||||
vmessUriRoot["ps"] = alias;
|
||||
vmessUriRoot["add"] = QSTRING(serverConfig.address);
|
||||
vmessUriRoot["port"] = serverConfig.port;
|
||||
vmessUriRoot["id"] = QSTRING(serverConfig.users.front().id);
|
||||
vmessUriRoot["aid"] = serverConfig.users.front().alterId;
|
||||
vmessUriRoot["net"] = QSTRING(transfer.network);
|
||||
vmessUriRoot["tls"] = QSTRING(transfer.security);
|
||||
|
||||
if (transfer.network == "tcp") {
|
||||
vmessUriRoot["type"] = QSTRING(transfer.tcpSettings.header.type);
|
||||
} else if (transfer.network == "kcp") {
|
||||
vmessUriRoot["type"] = QSTRING(transfer.kcpSettings.header.type);
|
||||
} else if (transfer.network == "quic") {
|
||||
vmessUriRoot["type"] = QSTRING(transfer.quicSettings.header.type);
|
||||
vmessUriRoot["host"] = QSTRING(transfer.quicSettings.security);
|
||||
vmessUriRoot["path"] = QSTRING(transfer.quicSettings.key);
|
||||
} else if (transfer.network == "ws") {
|
||||
auto x = QMap<string, string>(transfer.wsSettings.headers);
|
||||
auto host = x.contains("host");
|
||||
auto CapHost = x.contains("Host");
|
||||
auto realHost = host ? x["host"] : (CapHost ? x["Host"] : "");
|
||||
//
|
||||
vmessUriRoot["host"] = QSTRING(realHost);
|
||||
vmessUriRoot["path"] = QSTRING(transfer.wsSettings.path);
|
||||
} else if (transfer.network == "h2" || transfer.network == "http") {
|
||||
vmessUriRoot["host"] = Stringify(transfer.httpSettings.host, ",");
|
||||
vmessUriRoot["path"] = QSTRING(transfer.httpSettings.path);
|
||||
}
|
||||
|
||||
//
|
||||
auto vmessPart = Base64Encode(JsonToString(vmessUriRoot, QJsonDocument::JsonFormat::Compact));
|
||||
return "vmess://" + vmessPart;
|
||||
}
|
||||
QString DecodeSubscriptionString(QByteArray arr)
|
||||
{
|
||||
// Some subscription providers may use plain vmess:// saperated by lines
|
||||
@ -150,7 +189,7 @@ namespace Qv2ray
|
||||
|
||||
if (net == "tcp") {
|
||||
streaming.tcpSettings.header.type = type;
|
||||
} else if (net == "http") {
|
||||
} else if (net == "http" || net == "h2") {
|
||||
// Fill hosts for HTTP
|
||||
for (auto _host : QString::fromStdString(host).split(',')) {
|
||||
streaming.httpSettings.host.push_back(_host.toStdString());
|
||||
|
@ -104,18 +104,18 @@ namespace Qv2ray
|
||||
return JsonFromString(json);
|
||||
}
|
||||
|
||||
QString JsonToString(QJsonObject json)
|
||||
QString JsonToString(QJsonObject json, QJsonDocument::JsonFormat format)
|
||||
{
|
||||
QJsonDocument doc;
|
||||
doc.setObject(json);
|
||||
return doc.toJson();
|
||||
return doc.toJson(format);
|
||||
}
|
||||
|
||||
QString JsonToString(QJsonArray array)
|
||||
QString JsonToString(QJsonArray array, QJsonDocument::JsonFormat format)
|
||||
{
|
||||
QJsonDocument doc;
|
||||
doc.setArray(array);
|
||||
return doc.toJson();
|
||||
return doc.toJson(format);
|
||||
}
|
||||
|
||||
QString VerifyJsonString(const QString *source)
|
||||
|
@ -37,8 +37,8 @@ namespace Qv2ray
|
||||
bool StringToFile(const QString *text, QFile *target);
|
||||
//
|
||||
QJsonObject JsonFromString(QString string);
|
||||
QString JsonToString(QJsonObject json);
|
||||
QString JsonToString(QJsonArray array);
|
||||
QString JsonToString(QJsonObject json, QJsonDocument::JsonFormat format = QJsonDocument::JsonFormat::Indented);
|
||||
QString JsonToString(QJsonArray array, QJsonDocument::JsonFormat format = QJsonDocument::JsonFormat::Indented);
|
||||
//
|
||||
QString VerifyJsonString(const QString *source);
|
||||
//
|
||||
|
@ -18,7 +18,7 @@ ConfigExporter::ConfigExporter(const QString &data, QWidget *parent): ConfigExpo
|
||||
{
|
||||
QZXingEncoderConfig conf;
|
||||
conf.border = true;
|
||||
conf.errorCorrectionLevel = QZXing::EncodeErrorCorrectionLevel_Q;
|
||||
conf.imageSize = QSize(400, 400);
|
||||
auto img = qzxing.encodeData(data, conf);
|
||||
image = img.copy();
|
||||
message = data;
|
||||
@ -40,6 +40,10 @@ void ConfigExporter::changeEvent(QEvent *e)
|
||||
retranslateUi(this);
|
||||
break;
|
||||
|
||||
case QEvent::Resize:
|
||||
imageLabel->setPixmap(QPixmap::fromImage(image));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -16,24 +16,21 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="10,0,3,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="imageLabel">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Box</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Image</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../../resources.qrc">:/icons/qv2ray.ico</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::NoTextInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@ -83,6 +80,8 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<resources>
|
||||
<include location="../../resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "QvCoreConfigOperations.hpp"
|
||||
|
||||
#include "w_OutboundEditor.hpp"
|
||||
#include "w_JsonEditor.hpp"
|
||||
#include "w_ImportConfig.hpp"
|
||||
|
||||
|
||||
@ -19,7 +20,7 @@ ImportConfigWindow::ImportConfigWindow(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setupUi(this);
|
||||
nameTxt->setText(QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-MM-ss") + "_.imported");
|
||||
nameTxt->setText(QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm-ss") + "_imported");
|
||||
}
|
||||
|
||||
void ImportConfigWindow::on_importSourceCombo_currentIndexChanged(int index)
|
||||
@ -35,28 +36,29 @@ void ImportConfigWindow::on_selectFileBtn_clicked()
|
||||
|
||||
void ImportConfigWindow::on_qrFromScreenBtn_clicked()
|
||||
{
|
||||
LOG(MODULE_UI, "We currently only support the primary screen.")
|
||||
QThread::msleep(static_cast<ulong>(doubleSpinBox->value() * 1000));
|
||||
QScreen *screen = qApp->primaryScreen();
|
||||
bool hasVmessDetected = false;
|
||||
|
||||
if (const QWindow *window = windowHandle()) {
|
||||
screen = window->screen();
|
||||
for (auto screen : qApp->screens()) {
|
||||
if (!screen) {
|
||||
LOG(MODULE_UI, "Cannot even find a screen. RARE")
|
||||
QvMessageBox(this, tr("Screenshot failed"), tr("Cannot find a valid screen, it's rare."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto pix = screen->grabWindow(0);
|
||||
auto str = QZXing().decodeImage(pix.toImage());
|
||||
|
||||
if (str.isEmpty()) {
|
||||
continue;
|
||||
} else {
|
||||
vmessConnectionStringTxt->appendPlainText(str.trimmed() + NEWLINE);
|
||||
hasVmessDetected = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!screen) {
|
||||
LOG(MODULE_UI, "Cannot even find a screen.")
|
||||
QvMessageBox(this, tr("Screenshot failed"), tr("Cannot find a valid screen, it's rare."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto pix = screen->grabWindow(0);
|
||||
auto str = QZXing().decodeImage(pix.toImage());
|
||||
|
||||
if (str.isEmpty()) {
|
||||
QvMessageBox(this, tr("QRCode scanning failed"), tr("Cannot find any QRCode from the primary screen."));
|
||||
return;
|
||||
} else {
|
||||
vmessConnectionStringTxt->appendPlainText(str.trimmed() + NEWLINE);
|
||||
if (!hasVmessDetected) {
|
||||
QvMessageBox(this, tr("QRCode scanning failed"), tr("Cannot find any QRCode from any screens."));
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,6 +156,20 @@ void ImportConfigWindow::on_selectImageBtn_clicked()
|
||||
{
|
||||
QString dir = QFileDialog::getOpenFileName(this, tr("Select an image to import"));
|
||||
imageFileEdit->setText(dir);
|
||||
//
|
||||
QFile file(dir);
|
||||
file.open(QFile::OpenModeFlag::ReadOnly);
|
||||
auto buf = file.readAll();
|
||||
file.close();
|
||||
//
|
||||
auto str = QZXing().decodeImage(QImage::fromData(buf));
|
||||
|
||||
if (str.isEmpty()) {
|
||||
QvMessageBox(this, tr("QRCode scanning failed"), tr("Cannot find any QRCode from the image."));
|
||||
return;
|
||||
} else {
|
||||
vmessConnectionStringTxt->appendPlainText(str.trimmed() + NEWLINE);
|
||||
}
|
||||
}
|
||||
|
||||
void ImportConfigWindow::on_errorsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
|
||||
@ -180,3 +196,42 @@ void ImportConfigWindow::on_errorsList_currentItemChanged(QListWidgetItem *curre
|
||||
c.setPosition(endPos, QTextCursor::KeepAnchor);
|
||||
vmessConnectionStringTxt->setTextCursor(c);
|
||||
}
|
||||
|
||||
void ImportConfigWindow::on_editFileBtn_clicked()
|
||||
{
|
||||
QFile file(fileLineTxt->text());
|
||||
|
||||
if (!file.exists()) {
|
||||
QvMessageBox(this, tr("Edit file as JSON"), tr("Provided file not found: ") + fileLineTxt->text());
|
||||
return;
|
||||
}
|
||||
|
||||
auto jsonString = StringFromFile(&file);
|
||||
auto jsonCheckingError = VerifyJsonString(&jsonString);
|
||||
|
||||
if (!jsonCheckingError.isEmpty()) {
|
||||
LOG(MODULE_FILE, "Currupted JSON file detected")
|
||||
|
||||
if (QvMessageBoxAsk(this, tr("Edit file as JSON"), tr("The file you selected has json syntax error. Continue editing may make you lose data. Would you like to continue?") +
|
||||
NEWLINE + jsonCheckingError) != QMessageBox::Yes) {
|
||||
return;
|
||||
} else {
|
||||
LOG(MODULE_FILE, "Continue editing curruped json file, data loss is expected.")
|
||||
}
|
||||
}
|
||||
|
||||
auto json = JsonFromString(jsonString);
|
||||
auto editor = new JsonEditor(json, this);
|
||||
json = editor->OpenEditor();
|
||||
|
||||
if (editor->result() == QDialog::Accepted) {
|
||||
auto str = JsonToString(json);
|
||||
bool result = StringToFile(&str, &file);
|
||||
|
||||
if (!result) {
|
||||
QvMessageBox(this, tr("Edit file as JSON"), tr("Failed to save file, please check if you have the required permissions"));
|
||||
}
|
||||
} else {
|
||||
LOG(MODULE_FILE, "Canceled saving a file.")
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ class ImportConfigWindow : public QDialog, private Ui::ImportConfigWindow
|
||||
void on_selectImageBtn_clicked();
|
||||
void on_errorsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
|
||||
|
||||
void on_editFileBtn_clicked();
|
||||
|
||||
private:
|
||||
QMap<QString, QString> vmessErrors;
|
||||
};
|
||||
|
@ -81,7 +81,7 @@
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="filePage">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
@ -132,7 +132,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="pushButton_3">
|
||||
<widget class="QPushButton" name="editFileBtn">
|
||||
<property name="text">
|
||||
<string>Open in JSON Editor</string>
|
||||
</property>
|
||||
@ -188,29 +188,6 @@
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Delay in secs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="doubleSpinBox">
|
||||
<property name="maximum">
|
||||
<double>5.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.250000000000000</double>
|
||||
</property>
|
||||
<property name="stepType">
|
||||
<enum>QAbstractSpinBox::AdaptiveDecimalStepType</enum>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="qrFromScreenBtn">
|
||||
<property name="sizePolicy">
|
||||
@ -230,6 +207,36 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>After</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="doubleSpinBox">
|
||||
<property name="maximum">
|
||||
<double>5.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.500000000000000</double>
|
||||
</property>
|
||||
<property name="stepType">
|
||||
<enum>QAbstractSpinBox::AdaptiveDecimalStepType</enum>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>second(s).</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
|
@ -400,7 +400,8 @@ void MainWindow::ShowAndSetConnection(QString guiConnectionName, bool SetConnect
|
||||
if (guiConnectionName.isEmpty()) return;
|
||||
|
||||
// --------- BRGIN Show Connection
|
||||
auto outBoundRoot = (connections[guiConnectionName])["outbounds"].toArray().first().toObject();
|
||||
auto root = connections[guiConnectionName];
|
||||
auto outBoundRoot = root["outbounds"].toArray().first().toObject();
|
||||
//
|
||||
auto outboundType = outBoundRoot["protocol"].toString();
|
||||
_OutBoundTypeLabel->setText(outboundType);
|
||||
@ -425,7 +426,7 @@ void MainWindow::ShowAndSetConnection(QString guiConnectionName, bool SetConnect
|
||||
_portLabel->setText(QSTRING(to_string(Server.port)));
|
||||
}
|
||||
|
||||
routeCountLabel->setText(QString::number((connections[guiConnectionName])["routing"].toArray().count()));
|
||||
routeCountLabel->setText(QString::number(root["routing"].toObject()["rules"].toArray().count()));
|
||||
|
||||
// --------- END Show Connection
|
||||
//
|
||||
@ -683,8 +684,25 @@ void MainWindow::on_pingTestBtn_clicked()
|
||||
void MainWindow::on_shareBtn_clicked()
|
||||
{
|
||||
// Share QR
|
||||
ConfigExporter v("FUTURE VMESS:// GOES HERE!", this);
|
||||
v.OpenExport();
|
||||
if (connectionListWidget->currentRow() < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto alias = connectionListWidget->currentItem()->text();
|
||||
auto root = connections[alias];
|
||||
auto outBoundRoot = root["outbounds"].toArray().first().toObject();
|
||||
//
|
||||
auto outboundType = outBoundRoot["protocol"].toString();
|
||||
|
||||
if (outboundType == "vmess") {
|
||||
auto vmessServer = StructFromJsonString<VMessServerObject>(JsonToString(outBoundRoot["settings"].toObject()["vnext"].toArray().first().toObject()));
|
||||
auto transport = StructFromJsonString<StreamSettingsObject>(JsonToString(outBoundRoot["streamSettings"].toObject()));
|
||||
auto vmess = ConvertConfigToVMessString(transport, vmessServer, alias);
|
||||
ConfigExporter v(vmess, this);
|
||||
v.OpenExport();
|
||||
} else {
|
||||
QvMessageBox(this, tr("Share Connection"), tr("There're no support of sharing configs other than vmess"));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_action_RCM_ShareQR_triggered()
|
||||
|
@ -45,7 +45,7 @@
|
||||
<enum>QTabWidget::Rounded</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
|
Loading…
Reference in New Issue
Block a user