add: support Plugin Serialization

This commit is contained in:
Qv2ray-dev 2020-04-10 12:44:34 +08:00
parent 3020df5726
commit 059bad8d4b
7 changed files with 135 additions and 56 deletions

View File

@ -216,6 +216,74 @@ namespace Qv2ray::components::plugins
} }
} }
const QList<QvPluginEditor *> QvPluginHost::GetOutboundEditorWidgets() const
{
QList<QvPluginEditor *> data;
for (const auto &plugin : plugins)
{
if (!plugin.isLoaded)
continue;
auto editor = plugin.pluginInterface->GetEditorWidget(UI_TYPE::UI_TYPE_OUTBOUND_EDITOR);
if (editor)
{
data.append(editor.release());
}
}
return data;
}
const QMultiHash<QString, QPair<QString, QJsonObject>> QvPluginHost::TryDeserializeShareLink(const QString &sharelink, //
QString *prefix, //
QString *errMessage, //
QString *newGroupName, //
bool *status) const
{
Q_UNUSED(newGroupName)
QMultiHash<QString, QPair<QString, QJsonObject>> data;
*status = true;
for (const auto &plugin : plugins)
{
if (plugin.isLoaded && plugin.metadata.SpecialPluginType.contains(SPECIAL_TYPE_SERIALIZOR))
{
auto serializer = plugin.pluginInterface->GetSerializer();
bool thisPluginCanHandle = false;
for (const auto &prefix : serializer->ShareLinkPrefixes())
{
thisPluginCanHandle = thisPluginCanHandle || sharelink.startsWith(prefix);
}
if (thisPluginCanHandle)
{
auto [protocol, outboundSettings] = serializer->DeserializeOutbound(sharelink, prefix, errMessage);
*status = *status && errMessage->isEmpty();
data.insert(*prefix, { protocol, outboundSettings });
}
}
}
return data;
}
const QString QvPluginHost::TrySerializeShareLink(const QString &protocol, //
const QJsonObject &outboundSettings, //
const QString &alias, //
const QString &groupName, //
bool *status) const
{
*status = false;
for (const auto &plugin : plugins)
{
if (plugin.isLoaded && plugin.metadata.SpecialPluginType.contains(SPECIAL_TYPE_SERIALIZOR))
{
auto serializer = plugin.pluginInterface->GetSerializer();
if (serializer->OutboundProtocols().contains(protocol))
{
auto link = serializer->SerializeOutbound(protocol, alias, groupName, outboundSettings);
*status = true;
return link;
}
}
}
return "";
}
const QString GetPluginTypeString(const SPECIAL_TYPE_FLAGS &types) const QString GetPluginTypeString(const SPECIAL_TYPE_FLAGS &types)
{ {
QStringList typesList; QStringList typesList;

View File

@ -46,10 +46,6 @@ namespace Qv2ray::components::plugins
{ {
return plugins.value(internalName).pluginInterface->GetSettingsWidget(); return plugins.value(internalName).pluginInterface->GetSettingsWidget();
} }
inline std::unique_ptr<QvPluginEditor> GetPluginEditorWidget(const QString &internalName, UI_TYPE type) const
{
return plugins.value(internalName).pluginInterface->GetEditorWidget(type);
}
const inline QJsonObject GetPluginSettings(const QString &internalName) const const inline QJsonObject GetPluginSettings(const QString &internalName) const
{ {
return plugins.value(internalName).pluginInterface->GetSettngs(); return plugins.value(internalName).pluginInterface->GetSettngs();
@ -62,23 +58,19 @@ namespace Qv2ray::components::plugins
{ {
return plugins.value(internalName).metadata; return plugins.value(internalName).metadata;
} }
const QList<QPair<QvPluginOutboundObject, QString>> GetOutboundEditorWidgets() const //
{ const QMultiHash<QString, QPair<QString, QJsonObject>> TryDeserializeShareLink(const QString &sharelink, //
QList<QPair<QvPluginOutboundObject, QString>> data; QString *prefix, //
for (const auto &plugin : plugins) QString *errMessage, //
{ QString *newGroupName, //
if (!plugin.isLoaded) bool *status) const;
continue; //
if (auto editor = plugin.pluginInterface->GetEditorWidget(UI_TYPE::UI_TYPE_OUTBOUND_EDITOR); editor) const QString TrySerializeShareLink(const QString &protocol, //
{ const QJsonObject &outboundSettings, //
for (const auto &cap : editor->OutboundCapabilities()) const QString &alias, //
{ const QString &groupName, //
data.append({ cap, plugin.metadata.InternalName }); bool *status) const;
} const QList<QvPluginEditor *> GetOutboundEditorWidgets() const;
}
}
return data;
}
// //
void Send_ConnectionStatsEvent(const QvConnectionStatsEventObject &object); void Send_ConnectionStatsEvent(const QvConnectionStatsEventObject &object);
void Send_ConnectivityEvent(const QvConnectivityEventObject &object); void Send_ConnectivityEvent(const QvConnectivityEventObject &object);

@ -1 +1 @@
Subproject commit 7df6621bcdfcddadd94bdfd253306e1680035941 Subproject commit 71dab9339510d7e766bff00c89159f0407cff910

View File

@ -2,6 +2,7 @@
#include "Generation.hpp" #include "Generation.hpp"
#include "common/QvHelpers.hpp" #include "common/QvHelpers.hpp"
#include "components/plugins/QvPluginHost.hpp"
#include "core/CoreUtils.hpp" #include "core/CoreUtils.hpp"
#include "core/handler/ConfigHandler.hpp" #include "core/handler/ConfigHandler.hpp"
@ -11,29 +12,45 @@ namespace Qv2ray::core::connection
{ {
QMultiHash<QString, CONFIGROOT> ConvertConfigFromString(const QString &link, QString *prefix, QString *errMessage, QString *newGroupName) QMultiHash<QString, CONFIGROOT> ConvertConfigFromString(const QString &link, QString *prefix, QString *errMessage, QString *newGroupName)
{ {
QMultiHash<QString, CONFIGROOT> config; QMultiHash<QString, CONFIGROOT> connectionConf;
if (link.startsWith("vmess://")) if (link.startsWith("vmess://"))
{ {
auto conf = ConvertConfigFromVMessString(link, prefix, errMessage); auto conf = ConvertConfigFromVMessString(link, prefix, errMessage);
config.insert(*prefix, conf); connectionConf.insert(*prefix, conf);
} }
else if (link.startsWith("ss://")) else if (link.startsWith("ss://"))
{ {
auto conf = ConvertConfigFromSSString(link, prefix, errMessage); auto conf = ConvertConfigFromSSString(link, prefix, errMessage);
config.insert(*prefix, conf); connectionConf.insert(*prefix, conf);
} }
else if (link.startsWith("ssd://")) else if (link.startsWith("ssd://"))
{ {
QStringList errMessageList; QStringList errMessageList;
config = ConvertConfigFromSSDString(link, newGroupName, &errMessageList); connectionConf = ConvertConfigFromSSDString(link, newGroupName, &errMessageList);
*errMessage = errMessageList.join(NEWLINE); *errMessage = errMessageList.join(NEWLINE);
} }
else else
{ {
*errMessage = QObject::tr("Unsupported share link format."); bool ok = false;
auto configs = PluginHost->TryDeserializeShareLink(link, prefix, errMessage, newGroupName, &ok);
for (const auto &key : configs.keys())
{
auto vals = configs.values(key);
for (const auto &val : vals)
{
CONFIGROOT root;
auto outbound = GenerateOutboundEntry(val.first, OUTBOUNDSETTING(val.second), {});
root.insert("outbounds", QJsonArray{ outbound });
connectionConf.insert(key, root);
}
}
if (!ok)
{
*errMessage = QObject::tr("Unsupported share link format.");
}
} }
return config; return connectionConf;
} }
const QString ConvertConfigToString(const ConnectionId &id, bool isSip002) const QString ConvertConfigToString(const ConnectionId &id, bool isSip002)
@ -45,51 +62,51 @@ namespace Qv2ray::core::connection
return QV2RAY_SERIALIZATION_COMPLEX_CONFIG_PLACEHOLDER; return QV2RAY_SERIALIZATION_COMPLEX_CONFIG_PLACEHOLDER;
} }
auto server = ConnectionManager->GetConnectionRoot(id); auto server = ConnectionManager->GetConnectionRoot(id);
return ConvertConfigToString(alias, server, isSip002); return ConvertConfigToString(alias, GetDisplayName(GetConnectionGroupId(id)), server, isSip002);
} }
const QString ConvertConfigToString(const QString &alias, const CONFIGROOT &server, bool isSip002) const QString ConvertConfigToString(const QString &alias, const QString &groupName, const CONFIGROOT &server, bool isSip002)
{ {
OUTBOUND outbound = OUTBOUND(server["outbounds"].toArray().first().toObject()); const auto outbound = OUTBOUND(server["outbounds"].toArray().first().toObject());
auto type = outbound["protocol"].toString(); const auto type = outbound["protocol"].toString();
const auto &settings = outbound["settings"].toObject();
QString sharelink = ""; QString sharelink = "";
if (type == "vmess") if (type == "vmess")
{ {
auto vmessServer = auto vmessServer = StructFromJsonString<VMessServerObject>(JsonToString(settings["vnext"].toArray().first().toObject()));
StructFromJsonString<VMessServerObject>(JsonToString(outbound["settings"].toObject()["vnext"].toArray().first().toObject()));
auto transport = StructFromJsonString<StreamSettingsObject>(JsonToString(outbound["streamSettings"].toObject())); auto transport = StructFromJsonString<StreamSettingsObject>(JsonToString(outbound["streamSettings"].toObject()));
sharelink = vmess::ConvertConfigToVMessString(transport, vmessServer, alias); sharelink = vmess::ConvertConfigToVMessString(transport, vmessServer, alias);
} }
else if (type == "shadowsocks") else if (type == "shadowsocks")
{ {
auto ssServer = StructFromJsonString<ShadowSocksServerObject>( auto ssServer = StructFromJsonString<ShadowSocksServerObject>(JsonToString(settings["servers"].toArray().first().toObject()));
JsonToString(outbound["settings"].toObject()["servers"].toArray().first().toObject()));
sharelink = ss::ConvertConfigToSSString(ssServer, alias, isSip002); sharelink = ss::ConvertConfigToSSString(ssServer, alias, isSip002);
} }
else else
{ {
if (!type.isEmpty()) if (type.isEmpty())
{ {
// DEBUG(MODULE_CONNECTION, "WARNING: Unsupported outbound type: " + type) DEBUG(MODULE_CONNECTION, "WARNING: Empty outbound type.")
} }
else else
{ {
DEBUG(MODULE_CONNECTION, "WARNING: Empty outbound type.") bool ok = false;
sharelink = PluginHost->TrySerializeShareLink(type, settings, alias, groupName, &ok);
Q_UNUSED(ok)
} }
} }
return sharelink; return sharelink;
} }
QString DecodeSubscriptionString(QByteArray arr) QString DecodeSubscriptionString(const QByteArray &arr)
{ {
// String may start with: vmess:// and ss:// // String may start with: vmess:// and ss://
// We only process vmess:// here // We only process vmess:// here
// Some subscription providers may use plain vmess:// saperated by // Some subscription providers may use plain vmess:// saperated by
// lines But others may use base64 of above. // lines But others may use base64 of above.
auto result = QString::fromUtf8(arr).trimmed(); auto result = QString::fromUtf8(arr).trimmed();
return result.startsWith("vmess://") ? result : Base64Decode(result); return result.contains("://") ? result : Base64Decode(result);
} }
} // namespace Serialization } // namespace Serialization
} // namespace Qv2ray::core::connection } // namespace Qv2ray::core::connection

View File

@ -15,11 +15,11 @@ namespace Qv2ray::core::connection
inline auto QV2RAY_SSD_DEFAULT_NAME_PATTERN = QObject::tr("%1 - %2 (rate %3)"); inline auto QV2RAY_SSD_DEFAULT_NAME_PATTERN = QObject::tr("%1 - %2 (rate %3)");
// //
// General // General
QString DecodeSubscriptionString(QByteArray arr); QString DecodeSubscriptionString(const QByteArray &arr);
QMultiHash<QString, CONFIGROOT> ConvertConfigFromString(const QString &link, QString *aliasPrefix, QString *errMessage, QMultiHash<QString, CONFIGROOT> ConvertConfigFromString(const QString &link, QString *aliasPrefix, QString *errMessage,
QString *newGroupName = nullptr); QString *newGroupName = nullptr);
const QString ConvertConfigToString(const ConnectionId &id, bool isSip002 = false); const QString ConvertConfigToString(const ConnectionId &id, bool isSip002 = false);
const QString ConvertConfigToString(const QString &alias, const CONFIGROOT &server, bool isSip002); const QString ConvertConfigToString(const QString &alias, const QString &groupName, const CONFIGROOT &server, bool isSip002);
// //
// VMess URI Protocol // VMess URI Protocol
namespace vmess namespace vmess

View File

@ -24,10 +24,12 @@ OutboundEditor::OutboundEditor(QWidget *parent) : QDialog(parent), tag(OUTBOUND_
auto pluginEditorWidgetsInfo = PluginHost->GetOutboundEditorWidgets(); auto pluginEditorWidgetsInfo = PluginHost->GetOutboundEditorWidgets();
for (const auto &plugin : pluginEditorWidgetsInfo) for (const auto &plugin : pluginEditorWidgetsInfo)
{ {
outBoundTypeCombo->addItem(plugin.first.displayName, plugin.first.protocol); for (const auto &_d : plugin->OutboundCapabilities())
auto widget = PluginHost->GetPluginEditorWidget(plugin.second, UI_TYPE::UI_TYPE_OUTBOUND_EDITOR).release(); {
auto index = outboundTypeStackView->addWidget(widget); outBoundTypeCombo->addItem(_d.displayName, _d.protocol);
pluginWidgets.insert(index, { plugin.first, plugin.second, widget }); auto index = outboundTypeStackView->addWidget(plugin);
pluginWidgets.insert(index, { _d, plugin });
}
} }
// //
outboundType = "vmess"; outboundType = "vmess";
@ -117,10 +119,10 @@ OUTBOUND OutboundEditor::GenerateConnectionJson()
bool processed = false; bool processed = false;
for (const auto &plugin : pluginWidgets) for (const auto &plugin : pluginWidgets)
{ {
if (get<0>(plugin).protocol == outboundType) if (plugin.first.protocol == outboundType)
{ {
get<2>(plugin)->SetHostInfo(address, port); plugin.second->SetHostInfo(address, port);
settings = OUTBOUNDSETTING(get<2>(plugin)->GetContent()); settings = OUTBOUNDSETTING(plugin.second->GetContent());
processed = true; processed = true;
break; break;
} }
@ -193,11 +195,11 @@ void OutboundEditor::ReloadGUI()
for (const auto &index : pluginWidgets.keys()) for (const auto &index : pluginWidgets.keys())
{ {
const auto &plugin = pluginWidgets.value(index); const auto &plugin = pluginWidgets.value(index);
if (get<0>(plugin).protocol == outboundType) if (plugin.first.protocol == outboundType)
{ {
get<2>(plugin)->SetContent(settings); plugin.second->SetContent(settings);
outBoundTypeCombo->setCurrentIndex(index); outBoundTypeCombo->setCurrentIndex(index);
auto [_address, _port] = get<2>(plugin)->GetHostInfo(); auto [_address, _port] = plugin.second->GetHostInfo();
address = _address; address = _address;
port = _port; port = _port;
processed = true; processed = true;
@ -284,7 +286,7 @@ void OutboundEditor::on_outBoundTypeCombo_currentIndexChanged(int index)
} }
else else
{ {
outboundType = get<0>(pluginWidgets.value(index)).protocol; outboundType = pluginWidgets.value(index).first.protocol;
} }
} }

View File

@ -64,5 +64,5 @@ class OutboundEditor
// //
StreamSettingsWidget *streamSettingsWidget; StreamSettingsWidget *streamSettingsWidget;
// //
QMap<int, std::tuple<QvPluginOutboundObject, QString, QvPluginEditor *>> pluginWidgets; QMap<int, QPair<QvPluginOutboundObject, QvPluginEditor *>> pluginWidgets;
}; };