[add] Added vmess:// shares and vmess:// QRCode import, support multiple lines vmess:// supported importing image

Former-commit-id: 4e72ecfe02
This commit is contained in:
Leroy.H.Y 2019-11-11 23:28:45 +08:00
parent d999231321
commit a18de6cfe1
14 changed files with 199 additions and 74 deletions

View File

@ -1 +1 @@
485
501

View File

@ -1,6 +1,7 @@
#ifndef QV2RAYBASE_H
#define QV2RAYBASE_H
#include <QtCore>
#include <QMap>
#include "QvTinyLog.hpp"
#include "QvCoreConfigObjects.hpp"
#include "QvNetSpeedPlugin.hpp"

View File

@ -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++) {

View File

@ -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);
}
//

View File

@ -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());

View File

@ -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)

View File

@ -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);
//

View File

@ -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;
}

View File

@ -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>

View File

@ -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.")
}
}

View 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;
};

View File

@ -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">

View File

@ -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()

View File

@ -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">