Compare commits

...
Author SHA1 Message Date
NickVs2015 fc02c8910b feat: add WhatsApp proxy container 2026-11-05 14:08:12 +03:00
42 changed files with 1265 additions and 9 deletions

No files matched your search

+2
View File
@@ -38,6 +38,7 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/installers/mtProxyInstaller.h
${CLIENT_ROOT_DIR}/core/installers/telemtInstaller.h
${CLIENT_ROOT_DIR}/core/installers/tProxyInstaller.h
${CLIENT_ROOT_DIR}/core/installers/whatsAppProxyInstaller.h
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.h
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.h
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.h
@@ -128,6 +129,7 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/installers/mtProxyInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/telemtInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/tProxyInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/whatsAppProxyInstaller.cpp
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.cpp
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.cpp
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.cpp
+4 -1
View File
@@ -120,6 +120,9 @@ void CoreController::initModels()
m_tProxyConfigModel = new TProxyConfigModel(this);
setQmlContextProperty("TProxyConfigModel", m_tProxyConfigModel);
m_whatsAppProxyConfigModel = new WhatsAppProxyConfigModel(this);
setQmlContextProperty("WhatsAppProxyConfigModel", m_whatsAppProxyConfigModel);
m_clientManagementModel = new ClientManagementModel(this);
setQmlContextProperty("ClientManagementModel", m_clientManagementModel);
@@ -197,7 +200,7 @@ void CoreController::initControllers()
m_ikev2ConfigModel,
#endif
m_sftpConfigModel, m_socks5ConfigModel, m_mtProxyConfigModel, m_telemtConfigModel,
m_tProxyConfigModel,
m_tProxyConfigModel, m_whatsAppProxyConfigModel,
m_connectionController, this);
setQmlContextProperty("InstallController", m_installUiController);
+2
View File
@@ -81,6 +81,7 @@
#include "ui/models/services/mtProxyConfigModel.h"
#include "ui/models/services/telemtConfigModel.h"
#include "ui/models/services/tProxyConfigModel.h"
#include "ui/models/services/whatsAppProxyConfigModel.h"
#include "ui/models/ipSplitTunnelingModel.h"
#include "ui/models/newsModel.h"
@@ -246,6 +247,7 @@ private:
MtProxyConfigModel* m_mtProxyConfigModel;
TelemtConfigModel* m_telemtConfigModel;
TProxyConfigModel* m_tProxyConfigModel;
WhatsAppProxyConfigModel* m_whatsAppProxyConfigModel;
CoreSignalHandlers* m_signalHandlers;
};
@@ -25,6 +25,7 @@
#include "core/installers/mtProxyInstaller.h"
#include "core/installers/telemtInstaller.h"
#include "core/installers/tProxyInstaller.h"
#include "core/installers/whatsAppProxyInstaller.h"
#include "core/installers/torInstaller.h"
#include "core/installers/wireguardInstaller.h"
#include "core/installers/xrayInstaller.h"
@@ -145,6 +146,7 @@ namespace
return container == DockerContainer::MtProxy
|| container == DockerContainer::Telemt
|| container == DockerContainer::TProxy
|| container == DockerContainer::WhatsAppProxy
|| container == DockerContainer::Xray;
}
@@ -315,6 +317,8 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
ServerCredentials credentials = adminConfig->credentials();
SshSession sshSession;
TProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
} else if (container == DockerContainer::WhatsAppProxy) {
WhatsAppProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
}
adminConfig->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
@@ -372,6 +376,8 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
} else if (container == DockerContainer::TProxy) {
TProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
} else if (container == DockerContainer::WhatsAppProxy) {
WhatsAppProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
}
if (reinstallRequired) {
if (container != DockerContainer::Xray) {
@@ -729,6 +735,8 @@ ErrorCode InstallController::configureContainerWorker(const ServerCredentials &c
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, config);
} else if (container == DockerContainer::TProxy) {
TProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, config);
} else if (container == DockerContainer::WhatsAppProxy) {
WhatsAppProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, config);
}
return ErrorCode::NoError;
@@ -910,6 +918,14 @@ ErrorCode InstallController::isServerPortBusy(const ServerCredentials &credentia
}
}
}
if (container == DockerContainer::WhatsAppProxy) {
if (const auto *whatsAppProxyConfig = config.getWhatsAppProxyProtocolConfig()) {
const QString mediaPort = whatsAppProxyConfig->effectiveMediaPort();
if (!fixedPorts.contains(mediaPort) && mediaPort != port) {
fixedPorts.append(mediaPort);
}
}
}
QString transportProto = config.protocolConfig.transportProto();
if (transportProto.isEmpty()) {
transportProto = ProtocolUtils::transportProtoToString(ProtocolUtils::defaultTransportProto(protocol), protocol);
@@ -1085,6 +1101,14 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
}
}
if (container == DockerContainer::WhatsAppProxy) {
const auto *oldW = oldConfig.getWhatsAppProxyProtocolConfig();
const auto *newW = newConfig.getWhatsAppProxyProtocolConfig();
if (oldW && newW && !oldW->equalsDockerDeploymentSettings(*newW)) {
return true;
}
}
if (container == DockerContainer::Socks5Proxy) {
return true;
}
@@ -1122,7 +1146,7 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt
|| container == DockerContainer::TProxy) {
|| container == DockerContainer::TProxy || container == DockerContainer::WhatsAppProxy) {
QString conntrackOut;
auto cbConntrack = [&](const QString &data, libssh::Client &) {
conntrackOut += data + "\n";
@@ -1422,6 +1446,7 @@ QScopedPointer<InstallerBase> InstallController::createInstaller(DockerContainer
case DockerContainer::MtProxy: return QScopedPointer<InstallerBase>(new MtProxyInstaller(this));
case DockerContainer::Telemt: return QScopedPointer<InstallerBase>(new TelemtInstaller(this));
case DockerContainer::TProxy: return QScopedPointer<InstallerBase>(new TProxyInstaller(this));
case DockerContainer::WhatsAppProxy: return QScopedPointer<InstallerBase>(new WhatsAppProxyInstaller(this));
default: return QScopedPointer<InstallerBase>(new InstallerBase(this));
}
}
@@ -1456,6 +1481,14 @@ ErrorCode InstallController::installContainer(const ServerCredentials &credentia
return ErrorCode::InternalError;
}
}
if (container == DockerContainer::WhatsAppProxy) {
if (auto *whatsAppProxyConfig = config.getWhatsAppProxyProtocolConfig()) {
if (!m_whatsAppProxyInstallMediaPort.isEmpty()) {
whatsAppProxyConfig->mediaPort = m_whatsAppProxyInstallMediaPort;
}
}
m_whatsAppProxyInstallMediaPort.clear();
}
return setupContainer(credentials, container, config, false);
}
@@ -1465,6 +1498,11 @@ void InstallController::setTProxyInstallHints(const QString &hostname, const QSt
m_tproxyInstallEmail = email;
}
void InstallController::setWhatsAppProxyInstallHints(const QString &mediaPort)
{
m_whatsAppProxyInstallMediaPort = mediaPort;
}
bool InstallController::isUpdateDockerContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig)
{
@@ -1514,6 +1552,13 @@ bool InstallController::isUpdateDockerContainerRequired(DockerContainer containe
return true;
}
return !oldP->equalsDockerDeploymentSettings(*newP);
} else if (container == DockerContainer::WhatsAppProxy) {
const auto *oldW = oldConfig.getWhatsAppProxyProtocolConfig();
const auto *newW = newConfig.getWhatsAppProxyProtocolConfig();
if (!oldW || !newW) {
return true;
}
return !oldW->equalsDockerDeploymentSettings(*newW);
}
return true;
@@ -1945,6 +1990,11 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
TProxyInstaller::applyDockerPublishedPorts(containerInfo, *tProxyConfig);
}
}
if (container == DockerContainer::WhatsAppProxy) {
if (auto *whatsAppProxyConfig = config.getWhatsAppProxyProtocolConfig()) {
WhatsAppProxyInstaller::applyDockerPublishedPorts(containerInfo, *whatsAppProxyConfig);
}
}
ErrorCode extractError = installer->extractConfigFromContainer(container, credentials, &sshSession, config);
if (extractError != ErrorCode::NoError && extractError != ErrorCode::ServerContainerMissingError) {
@@ -1975,6 +2025,11 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
TProxyInstaller::applyDockerPublishedPorts(containerInfo, *tProxyConfig);
}
}
if (container == DockerContainer::WhatsAppProxy) {
if (auto *whatsAppProxyConfig = config.getWhatsAppProxyProtocolConfig()) {
WhatsAppProxyInstaller::applyDockerPublishedPorts(containerInfo, *whatsAppProxyConfig);
}
}
ErrorCode extractError = installer->extractConfigFromContainer(container, credentials, &sshSession, config);
if (extractError != ErrorCode::NoError && extractError != ErrorCode::ServerContainerMissingError) {
@@ -1991,7 +2046,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
ErrorCode InstallController::setDockerContainerEnabledState(const QString &serverId, DockerContainer container, bool enabled)
{
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt
&& container != DockerContainer::TProxy) {
&& container != DockerContainer::TProxy && container != DockerContainer::WhatsAppProxy) {
return ErrorCode::InternalError;
}
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
@@ -2021,6 +2076,9 @@ ErrorCode InstallController::setDockerContainerEnabledState(const QString &serve
} else if (auto *tProxyConfig = currentConfig.getTProxyProtocolConfig()) {
tProxyConfig->isEnabled = enabled;
persist = true;
} else if (auto *whatsAppProxyConfig = currentConfig.getWhatsAppProxyProtocolConfig()) {
whatsAppProxyConfig->isEnabled = enabled;
persist = true;
}
if (persist) {
adminConfig->updateContainerConfig(container, currentConfig);
@@ -57,6 +57,7 @@ public:
QString fetchDockerContainerSecret(const QString &serverId, DockerContainer container);
void setTProxyInstallHints(const QString &hostname, const QString &email);
void setWhatsAppProxyInstallHints(const QString &mediaPort);
ContainerConfig generateConfig(DockerContainer container, int port, TransportProto transportProto);
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, ContainerConfig> &installedContainers, SshSession &sshSession);
@@ -136,6 +137,7 @@ private:
bool m_cancelInstallation = false;
QString m_tproxyInstallHostname;
QString m_tproxyInstallEmail;
QString m_whatsAppProxyInstallMediaPort;
#ifndef Q_OS_IOS
QList<QSharedPointer<QProcess>> m_sftpMountProcesses;
+7
View File
@@ -125,6 +125,13 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
config.protocolConfig = tProxyConfig;
break;
}
case Proto::WhatsAppProxy: {
WhatsAppProxyProtocolConfig whatsAppProxyConfig;
whatsAppProxyConfig.port = portStr.isEmpty() ? QString(protocols::whatsAppProxy::defaultPort) : portStr;
whatsAppProxyConfig.mediaPort = QString(protocols::whatsAppProxy::defaultMediaPort);
config.protocolConfig = whatsAppProxyConfig;
break;
}
case Proto::Ikev2: {
Ikev2ProtocolConfig ikev2Config;
config.protocolConfig = ikev2Config;
@@ -0,0 +1,84 @@
#include "whatsAppProxyInstaller.h"
#include "core/models/containerConfig.h"
#include "core/utils/containerEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QRegularExpression>
using namespace amnezia;
namespace
{
constexpr QLatin1String kClientJsonPath("/data/amnezia-whatsappproxy-client.json");
constexpr QLatin1String kClientJsonUploadPath("data/amnezia-whatsappproxy-client.json");
}
void WhatsAppProxyInstaller::applyDockerPublishedPorts(const QString &dockerPsPortsLine,
WhatsAppProxyProtocolConfig &config)
{
static const QRegularExpression chatRe(QStringLiteral(":(\\d+)->443/tcp"));
static const QRegularExpression mediaRe(QStringLiteral(":(\\d+)->587/tcp"));
const QRegularExpressionMatch chatMatch = chatRe.match(dockerPsPortsLine);
const QRegularExpressionMatch mediaMatch = mediaRe.match(dockerPsPortsLine);
if (chatMatch.hasMatch()) {
config.port = chatMatch.captured(1);
}
if (mediaMatch.hasMatch()) {
config.mediaPort = mediaMatch.captured(1);
}
}
WhatsAppProxyInstaller::WhatsAppProxyInstaller(QObject *parent) : InstallerBase(parent)
{
}
ErrorCode WhatsAppProxyInstaller::extractConfigFromContainer(DockerContainer container,
const ServerCredentials &credentials,
SshSession *sshSession, ContainerConfig &config)
{
if (container != DockerContainer::WhatsAppProxy || !sshSession) {
return ErrorCode::NoError;
}
WhatsAppProxyProtocolConfig *wc = config.getWhatsAppProxyProtocolConfig();
if (!wc) {
return ErrorCode::NoError;
}
ErrorCode jsonErr = ErrorCode::NoError;
const QByteArray jsonRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kClientJsonPath), jsonErr);
if (jsonErr != ErrorCode::NoError || jsonRaw.trimmed().isEmpty()) {
return ErrorCode::NoError;
}
QJsonParseError parseError;
const QJsonDocument doc = QJsonDocument::fromJson(jsonRaw.trimmed(), &parseError);
if (parseError.error != QJsonParseError::NoError || !doc.isObject()) {
return ErrorCode::NoError;
}
const WhatsAppProxyProtocolConfig snapshot = WhatsAppProxyProtocolConfig::fromJson(doc.object());
wc->publicHost = snapshot.publicHost;
wc->isEnabled = snapshot.isEnabled;
return ErrorCode::NoError;
}
void WhatsAppProxyInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
DockerContainer container, const ContainerConfig &config)
{
const WhatsAppProxyProtocolConfig *wc = config.getWhatsAppProxyProtocolConfig();
if (!wc) {
return;
}
const QByteArray payload = QJsonDocument(wc->toJson()).toJson(QJsonDocument::Compact);
const ErrorCode err = sshSession.uploadTextFileToContainer(container, credentials, QString::fromUtf8(payload),
QString(kClientJsonUploadPath));
if (err != ErrorCode::NoError) {
qWarning() << "WhatsAppProxyInstaller::uploadClientSettingsSnapshot failed" << err;
}
}
@@ -0,0 +1,24 @@
#ifndef WHATSAPPPROXYINSTALLER_H
#define WHATSAPPPROXYINSTALLER_H
#include "installerBase.h"
#include "core/models/protocols/whatsAppProxyProtocolConfig.h"
class WhatsAppProxyInstaller : public InstallerBase
{
Q_OBJECT
public:
explicit WhatsAppProxyInstaller(QObject *parent = nullptr);
amnezia::ErrorCode extractConfigFromContainer(amnezia::DockerContainer container,
const amnezia::ServerCredentials &credentials, SshSession *sshSession,
amnezia::ContainerConfig &config) override;
static void applyDockerPublishedPorts(const QString &dockerPsPortsLine, amnezia::WhatsAppProxyProtocolConfig &config);
static void uploadClientSettingsSnapshot(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container, const amnezia::ContainerConfig &config);
};
#endif // WHATSAPPPROXYINSTALLER_H
+10
View File
@@ -143,6 +143,16 @@ const TProxyProtocolConfig* ContainerConfig::getTProxyProtocolConfig() const
return protocolConfig.as<TProxyProtocolConfig>();
}
WhatsAppProxyProtocolConfig* ContainerConfig::getWhatsAppProxyProtocolConfig()
{
return protocolConfig.as<WhatsAppProxyProtocolConfig>();
}
const WhatsAppProxyProtocolConfig* ContainerConfig::getWhatsAppProxyProtocolConfig() const
{
return protocolConfig.as<WhatsAppProxyProtocolConfig>();
}
Ikev2ProtocolConfig* ContainerConfig::getIkev2ProtocolConfig()
{
return protocolConfig.as<Ikev2ProtocolConfig>();
+3
View File
@@ -66,6 +66,9 @@ struct ContainerConfig {
TProxyProtocolConfig* getTProxyProtocolConfig();
const TProxyProtocolConfig* getTProxyProtocolConfig() const;
WhatsAppProxyProtocolConfig* getWhatsAppProxyProtocolConfig();
const WhatsAppProxyProtocolConfig* getWhatsAppProxyProtocolConfig() const;
Ikev2ProtocolConfig* getIkev2ProtocolConfig();
const Ikev2ProtocolConfig* getIkev2ProtocolConfig() const;
+9
View File
@@ -12,6 +12,7 @@
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
#include "core/models/protocols/tProxyProtocolConfig.h"
#include "core/models/protocols/whatsAppProxyProtocolConfig.h"
namespace amnezia
{
@@ -47,6 +48,8 @@ Proto ProtocolConfig::type() const
return Proto::Telemt;
} else if constexpr (std::is_same_v<T, TProxyProtocolConfig>) {
return Proto::TProxy;
} else if constexpr (std::is_same_v<T, WhatsAppProxyProtocolConfig>) {
return Proto::WhatsAppProxy;
}
return Proto::Unknown;
}, data);
@@ -80,6 +83,8 @@ QString ProtocolConfig::port() const
return arg.port.isEmpty() ? QString(protocols::telemt::defaultPort) : arg.port;
} else if constexpr (std::is_same_v<T, TProxyProtocolConfig>) {
return arg.port.isEmpty() ? QString(protocols::tProxy::defaultPort) : arg.port;
} else if constexpr (std::is_same_v<T, WhatsAppProxyProtocolConfig>) {
return arg.effectivePort();
}
return QString();
}, data);
@@ -109,6 +114,8 @@ QString ProtocolConfig::transportProto() const
return QStringLiteral("tcp");
} else if constexpr (std::is_same_v<T, TProxyProtocolConfig>) {
return QStringLiteral("tcp");
} else if constexpr (std::is_same_v<T, WhatsAppProxyProtocolConfig>) {
return QStringLiteral("tcp");
}
return QString();
}, data);
@@ -326,6 +333,8 @@ ProtocolConfig ProtocolConfig::fromJson(const QJsonObject& json, Proto type)
return ProtocolConfig{TelemtProtocolConfig::fromJson(json)};
case Proto::TProxy:
return ProtocolConfig{TProxyProtocolConfig::fromJson(json)};
case Proto::WhatsAppProxy:
return ProtocolConfig{WhatsAppProxyProtocolConfig::fromJson(json)};
default:
return ProtocolConfig{AwgProtocolConfig{}};
}
+2
View File
@@ -25,6 +25,7 @@
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
#include "core/models/protocols/tProxyProtocolConfig.h"
#include "core/models/protocols/whatsAppProxyProtocolConfig.h"
namespace amnezia
{
@@ -42,6 +43,7 @@ struct ProtocolConfig {
MtProxyProtocolConfig,
TelemtProtocolConfig,
TProxyProtocolConfig,
WhatsAppProxyProtocolConfig,
Ikev2ProtocolConfig,
TorProtocolConfig,
DnsProtocolConfig
@@ -0,0 +1,47 @@
#include "whatsAppProxyProtocolConfig.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
using namespace amnezia;
QString WhatsAppProxyProtocolConfig::effectivePort() const
{
return port.isEmpty() ? QString(protocols::whatsAppProxy::defaultPort) : port;
}
QString WhatsAppProxyProtocolConfig::effectiveMediaPort() const
{
return mediaPort.isEmpty() ? QString(protocols::whatsAppProxy::defaultMediaPort) : mediaPort;
}
QJsonObject WhatsAppProxyProtocolConfig::toJson() const
{
QJsonObject obj;
if (!port.isEmpty()) {
obj[QString(configKey::port)] = port;
}
if (!mediaPort.isEmpty()) {
obj[protocols::whatsAppProxy::mediaPortKey] = mediaPort;
}
if (!publicHost.isEmpty()) {
obj[protocols::whatsAppProxy::publicHostKey] = publicHost;
}
obj[protocols::whatsAppProxy::isEnabledKey] = isEnabled;
return obj;
}
WhatsAppProxyProtocolConfig WhatsAppProxyProtocolConfig::fromJson(const QJsonObject &json)
{
WhatsAppProxyProtocolConfig c;
c.port = json.value(QString(configKey::port)).toString();
c.mediaPort = json.value(protocols::whatsAppProxy::mediaPortKey).toString();
c.publicHost = json.value(protocols::whatsAppProxy::publicHostKey).toString();
c.isEnabled = json.value(protocols::whatsAppProxy::isEnabledKey).toBool(true);
return c;
}
bool WhatsAppProxyProtocolConfig::equalsDockerDeploymentSettings(const WhatsAppProxyProtocolConfig &other) const
{
return effectivePort() == other.effectivePort() && effectiveMediaPort() == other.effectiveMediaPort();
}
@@ -0,0 +1,27 @@
#ifndef WHATSAPPPROXYPROTOCOLCONFIG_H
#define WHATSAPPPROXYPROTOCOLCONFIG_H
#include <QJsonObject>
#include <QString>
namespace amnezia
{
struct WhatsAppProxyProtocolConfig
{
QString port;
QString mediaPort;
QString publicHost;
bool isEnabled = true;
QString effectivePort() const;
QString effectiveMediaPort() const;
QJsonObject toJson() const;
static WhatsAppProxyProtocolConfig fromJson(const QJsonObject &json);
bool equalsDockerDeploymentSettings(const WhatsAppProxyProtocolConfig &other) const;
};
} // namespace amnezia
#endif // WHATSAPPPROXYPROTOCOLCONFIG_H
+7
View File
@@ -72,6 +72,7 @@ QMap<Proto, QString> ProtocolUtils::protocolHumanNames()
{ Proto::MtProxy, QObject::tr("MTProxy (Telegram)") },
{ Proto::Telemt, QObject::tr("Telemt (Telegram)") },
{ Proto::TProxy, QObject::tr("TProxy (Telegram WEB)") },
{ Proto::WhatsAppProxy, QObject::tr("WhatsApp proxy") },
};
}
@@ -99,6 +100,7 @@ ServiceType ProtocolUtils::protocolService(Proto p)
case Proto::MtProxy: return ServiceType::Other;
case Proto::Telemt: return ServiceType::Other;
case Proto::TProxy: return ServiceType::Other;
case Proto::WhatsAppProxy: return ServiceType::Other;
default: return ServiceType::Other;
}
}
@@ -114,6 +116,7 @@ int ProtocolUtils::getPortForInstall(Proto p)
case MtProxy:
case Telemt:
case TProxy:
case WhatsAppProxy:
default:
return defaultPort(p);
}
@@ -136,6 +139,7 @@ int ProtocolUtils::defaultPort(Proto p)
case Proto::MtProxy: return QString(protocols::mtProxy::defaultPort).toInt();
case Proto::Telemt: return QString(protocols::telemt::defaultPort).toInt();
case Proto::TProxy: return QString(protocols::tProxy::defaultPort).toInt();
case Proto::WhatsAppProxy: return QString(protocols::whatsAppProxy::defaultPort).toInt();
default: return -1;
}
}
@@ -157,6 +161,7 @@ bool ProtocolUtils::defaultPortChangeable(Proto p)
case Proto::MtProxy: return true;
case Proto::Telemt: return true;
case Proto::TProxy: return true;
case Proto::WhatsAppProxy: return true;
default: return false;
}
}
@@ -180,6 +185,7 @@ TransportProto ProtocolUtils::defaultTransportProto(Proto p)
case Proto::MtProxy: return TransportProto::Tcp;
case Proto::Telemt: return TransportProto::Tcp;
case Proto::TProxy: return TransportProto::Tcp;
case Proto::WhatsAppProxy: return TransportProto::Tcp;
default: return TransportProto::Udp;
}
}
@@ -202,6 +208,7 @@ bool ProtocolUtils::defaultTransportProtoChangeable(Proto p)
case Proto::MtProxy: return false;
case Proto::Telemt: return false;
case Proto::TProxy: return false;
case Proto::WhatsAppProxy: return false;
default: return false;
}
}
+1
View File
@@ -144,6 +144,7 @@ namespace amnezia
constexpr QLatin1String mtproxy("mtproxy");
constexpr QLatin1String telemt("telemt");
constexpr QLatin1String tproxy("tproxy");
constexpr QLatin1String whatsappproxy("whatsappproxy");
constexpr QLatin1String splitTunnelSites("splitTunnelSites");
constexpr QLatin1String splitTunnelType("splitTunnelType");
@@ -413,6 +413,16 @@ namespace amnezia
constexpr int maxWorkers = 32;
}
namespace whatsAppProxy
{
constexpr char mediaPortKey[] = "whatsapp_proxy_media_port";
constexpr char publicHostKey[] = "whatsapp_proxy_public_host";
constexpr char isEnabledKey[] = "whatsapp_proxy_is_enabled";
constexpr char defaultPort[] = "443";
constexpr char defaultMediaPort[] = "587";
}
} // namespace protocols
}
+1
View File
@@ -29,6 +29,7 @@ namespace amnezia
MtProxy,
Telemt,
TProxy,
WhatsAppProxy,
};
Q_ENUM_NS(DockerContainer)
} // namespace ContainerEnumNS
@@ -80,6 +80,7 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
{ DockerContainer::MtProxy, QObject::tr("MTProxy (Telegram)") },
{ DockerContainer::Telemt, QObject::tr("Telemt (Telegram)") },
{ DockerContainer::TProxy, QObject::tr("TProxy (Telegram WEB)") },
{ DockerContainer::WhatsAppProxy, QObject::tr("WhatsApp proxy") },
};
}
@@ -120,6 +121,8 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
QObject::tr("Telegram MTProto proxy (Telemt, Rust)") },
{ DockerContainer::TProxy,
QObject::tr("Telegram WEB proxy (tproxy-server)") },
{ DockerContainer::WhatsAppProxy,
QObject::tr("WhatsApp chat and media proxy") },
};
}
@@ -203,6 +206,11 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
QObject::tr("Telegram WEB proxy. Clients connect over HTTPS to a hostname; "
"the server relays traffic to official MTProxy. Requires a domain, "
"ports 80 and 443, and a WEB-capable Telegram app.") },
{ DockerContainer::WhatsAppProxy,
QObject::tr("WhatsApp proxy based on the official Meta implementation. "
"Relays chat traffic through the chat port and media through the media port. "
"Users enter the server address and both ports in WhatsApp settings. "
"Calls are not proxied.") },
};
}
@@ -235,6 +243,7 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
case DockerContainer::MtProxy: return Proto::MtProxy;
case DockerContainer::Telemt: return Proto::Telemt;
case DockerContainer::TProxy: return Proto::TProxy;
case DockerContainer::WhatsAppProxy: return Proto::WhatsAppProxy;
default: return Proto::Unknown;
}
}
@@ -265,6 +274,7 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::MtProxy: return true;
case DockerContainer::Telemt: return true;
case DockerContainer::TProxy: return true;
case DockerContainer::WhatsAppProxy: return true;
default:
return false;
}
@@ -283,6 +293,7 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::MtProxy: return true;
case DockerContainer::Telemt: return true;
case DockerContainer::TProxy: return true;
case DockerContainer::WhatsAppProxy: return true;
default:
return false;
}
@@ -304,6 +315,7 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::MtProxy: return true;
case DockerContainer::Telemt: return true;
case DockerContainer::TProxy: return true;
case DockerContainer::WhatsAppProxy: return true;
default: return false;
}
@@ -373,6 +385,7 @@ bool ContainerUtils::isShareable(DockerContainer container)
case DockerContainer::MtProxy: return false;
case DockerContainer::Telemt: return false;
case DockerContainer::TProxy: return false;
case DockerContainer::WhatsAppProxy: return false;
default: return true;
}
}
@@ -409,6 +422,7 @@ int ContainerUtils::installPageOrder(DockerContainer container)
case DockerContainer::MtProxy:
case DockerContainer::Telemt:
case DockerContainer::TProxy:
case DockerContainer::WhatsAppProxy:
return 20;
default: return 0;
}
+1
View File
@@ -34,6 +34,7 @@ namespace amnezia
MtProxy,
Telemt,
TProxy,
WhatsAppProxy,
};
Q_ENUM_NS(Proto)
@@ -22,6 +22,7 @@
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
#include "core/models/protocols/tProxyProtocolConfig.h"
#include "core/models/protocols/whatsAppProxyProtocolConfig.h"
using namespace amnezia;
using namespace ProtocolUtils;
@@ -43,6 +44,7 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
case DockerContainer::MtProxy: return QLatin1String("mtproxy");
case DockerContainer::Telemt: return QLatin1String("telemt");
case DockerContainer::TProxy: return QLatin1String("tproxy");
case DockerContainer::WhatsAppProxy: return QLatin1String("whatsapp_proxy");
default: return QString();
}
}
@@ -428,6 +430,18 @@ amnezia::ScriptVars amnezia::genTProxyVars(const ContainerConfig &containerConfi
return vars;
}
amnezia::ScriptVars amnezia::genWhatsAppProxyVars(const ContainerConfig &containerConfig)
{
ScriptVars vars;
if (auto *whatsAppProxyConfig = containerConfig.getWhatsAppProxyProtocolConfig()) {
vars.append({ { "$WHATSAPP_PROXY_CHAT_PORT", whatsAppProxyConfig->effectivePort() } });
vars.append({ { "$WHATSAPP_PROXY_MEDIA_PORT", whatsAppProxyConfig->effectiveMediaPort() } });
}
return vars;
}
amnezia::ScriptVars amnezia::genProtocolVarsForContainer(DockerContainer container, const ContainerConfig &containerConfig)
{
ScriptVars vars;
@@ -461,6 +475,9 @@ amnezia::ScriptVars amnezia::genProtocolVarsForContainer(DockerContainer contain
case Proto::TProxy:
vars.append(genTProxyVars(containerConfig));
break;
case Proto::WhatsAppProxy:
vars.append(genWhatsAppProxyVars(containerConfig));
break;
default:
break;
}
@@ -71,6 +71,7 @@ ScriptVars genSocks5ProxyVars(const ContainerConfig &containerConfig);
ScriptVars genMtProxyVars(const ContainerConfig &containerConfig);
ScriptVars genTelemtVars(const ContainerConfig &containerConfig);
ScriptVars genTProxyVars(const ContainerConfig &containerConfig);
ScriptVars genWhatsAppProxyVars(const ContainerConfig &containerConfig);
ScriptVars genProtocolVarsForContainer(DockerContainer container, const ContainerConfig &containerConfig);
}
+2 -1
View File
@@ -104,7 +104,8 @@ ErrorCode SshSession::runContainerScript(const ServerCredentials &credentials, D
return e;
const bool useSh = container == DockerContainer::Socks5Proxy || container == DockerContainer::MtProxy
|| container == DockerContainer::Telemt || container == DockerContainer::TProxy;
|| container == DockerContainer::Telemt || container == DockerContainer::TProxy
|| container == DockerContainer::WhatsAppProxy;
QString runner = QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, useSh ? "sh" : "bash");
e = runScript(credentials, replaceVars(runner, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr);
+4
View File
@@ -37,6 +37,10 @@
<file>tproxy/Dockerfile</file>
<file>tproxy/run_container.sh</file>
<file>tproxy/start.sh</file>
<file>whatsapp_proxy/configure_container.sh</file>
<file>whatsapp_proxy/Dockerfile</file>
<file>whatsapp_proxy/run_container.sh</file>
<file>whatsapp_proxy/start.sh</file>
<file>openvpn/configure_container.sh</file>
<file>openvpn/Dockerfile</file>
<file>openvpn/run_container.sh</file>
@@ -0,0 +1,12 @@
FROM haproxy:lts-alpine
USER root
RUN apk add --no-cache openssl
RUN mkdir -p /opt/amnezia /data
RUN printf '#!/bin/sh\ntail -f /dev/null\n' > /opt/amnezia/start.sh && \
chmod a+x /opt/amnezia/start.sh
VOLUME /data
ENTRYPOINT ["/bin/sh", "/opt/amnezia/start.sh"]
CMD [""]
@@ -0,0 +1,49 @@
#!/bin/sh
mkdir -p /data
if [ ! -s /data/proxy.pem ]; then
openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
-subj "/CN=$(openssl rand -hex 8).net" \
-keyout /data/proxy.key -out /data/proxy.crt 2>/dev/null
cat /data/proxy.key /data/proxy.crt > /data/proxy.pem
rm -f /data/proxy.key /data/proxy.crt
chmod 600 /data/proxy.pem
fi
cat > /data/haproxy.cfg << EOF_CFG
global
tune.bufsize 4096
maxconn 27500
spread-checks 5
ssl-server-verify none
defaults
mode tcp
timeout client-fin 1s
timeout server-fin 1s
timeout connect 5s
timeout client 200s
timeout server 200s
default-server inter 10s fastinter 1s downinter 3s error-limit 50
frontend chat
bind ipv4@*:443 ssl crt /data/proxy.pem
default_backend wa_chat
frontend media
bind ipv4@*:587
default_backend wa_media
backend wa_chat
default-server check inter 60000 observe layer4 send-proxy
server g_whatsapp_net_5222 g.whatsapp.net:5222
backend wa_media
default-server check inter 60000 observe layer4
server whatsapp_net_443 whatsapp.net:443
EOF_CFG
echo "[*] WhatsApp proxy configuration"
echo "[*] Chat port: $WHATSAPP_PROXY_CHAT_PORT"
echo "[*] Media port: $WHATSAPP_PROXY_MEDIA_PORT"
@@ -0,0 +1,8 @@
sudo docker run -d \
--log-driver none \
--restart always \
-p $WHATSAPP_PROXY_CHAT_PORT:443/tcp \
-p $WHATSAPP_PROXY_MEDIA_PORT:587/tcp \
-v amnezia-whatsappproxy-data:/data \
--name $CONTAINER_NAME \
$CONTAINER_NAME
@@ -0,0 +1,9 @@
#!/bin/sh
if [ ! -f /data/haproxy.cfg ] || [ ! -s /data/proxy.pem ]; then
echo "ERROR: /data/haproxy.cfg not found — run configure_container first"
tail -f /dev/null
exit 1
fi
exec haproxy -W -db -f /data/haproxy.cfg
@@ -55,6 +55,7 @@ InstallUiController::InstallUiController(InstallController *installController,
MtProxyConfigModel* mtConfigModel,
TelemtConfigModel *telemtConfigModel,
TProxyConfigModel *tProxyConfigModel,
WhatsAppProxyConfigModel *whatsAppProxyConfigModel,
ConnectionController *connectionController,
QObject *parent)
: QObject(parent),
@@ -76,6 +77,7 @@ InstallUiController::InstallUiController(InstallController *installController,
m_mtProxyConfigModel(mtConfigModel),
m_telemtConfigModel(telemtConfigModel),
m_tProxyConfigModel(tProxyConfigModel),
m_whatsAppProxyConfigModel(whatsAppProxyConfigModel),
m_connectionController(connectionController)
{
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
@@ -92,6 +94,9 @@ void InstallUiController::install(DockerContainer container, int port, Transport
m_installController->setTProxyInstallHints(m_tProxyConfigModel->getHostname(),
m_tProxyConfigModel->getAcmeEmail());
}
if (container == DockerContainer::WhatsAppProxy && m_whatsAppProxyConfigModel) {
m_installController->setWhatsAppProxyInstallHints(m_whatsAppProxyConfigModel->getMediaPort());
}
const bool isNewServer = serverId.isEmpty();
@@ -269,6 +274,10 @@ bool InstallUiController::buildContainerConfigFromModel(int containerIndex, int
containerConfig.protocolConfig = m_tProxyConfigModel->getProtocolConfig();
break;
}
case Proto::WhatsAppProxy: {
containerConfig.protocolConfig = m_whatsAppProxyConfigModel->getProtocolConfig();
break;
}
#ifdef Q_OS_WINDOWS
case Proto::Ikev2: {
containerConfig.protocolConfig = m_ikev2ConfigModel->getProtocolConfig();
@@ -325,7 +334,7 @@ void InstallUiController::updateServerConfig(const QString &serverId, int contai
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
const bool asyncUpdate = container == DockerContainer::MtProxy || container == DockerContainer::Telemt
|| container == DockerContainer::TProxy
|| container == DockerContainer::TProxy || container == DockerContainer::WhatsAppProxy
|| container == DockerContainer::Xray || container == DockerContainer::SSXray;
@@ -336,7 +345,7 @@ void InstallUiController::updateServerConfig(const QString &serverId, int contai
m_serverConfigUpdateInProgress = true;
const bool emitBusy = container == DockerContainer::MtProxy || container == DockerContainer::Telemt
|| container == DockerContainer::TProxy;
|| container == DockerContainer::TProxy || container == DockerContainer::WhatsAppProxy;
if (emitBusy)
emit serverIsBusy(true);
auto *watcher = new QFutureWatcher<ErrorCode>(this);
@@ -391,7 +400,7 @@ void InstallUiController::setContainerEnabled(const QString &serverId, int conta
{
const DockerContainer container = static_cast<DockerContainer>(containerIndex);
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt
&& container != DockerContainer::TProxy) {
&& container != DockerContainer::TProxy && container != DockerContainer::WhatsAppProxy) {
return;
}
@@ -423,7 +432,7 @@ void InstallUiController::refreshContainerStatus(const QString &serverId, int co
{
const DockerContainer container = static_cast<DockerContainer>(containerIndex);
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt
&& container != DockerContainer::TProxy) {
&& container != DockerContainer::TProxy && container != DockerContainer::WhatsAppProxy) {
return;
}
@@ -759,6 +768,7 @@ void InstallUiController::updateProtocolConfigModel(const QString &serverId, int
case Proto::MtProxy: updateIfPresent(m_mtProxyConfigModel, containerConfig.getMtProxyProtocolConfig()); break;
case Proto::Telemt: updateIfPresent(m_telemtConfigModel, containerConfig.getTelemtProtocolConfig()); break;
case Proto::TProxy: updateIfPresent(m_tProxyConfigModel, containerConfig.getTProxyProtocolConfig()); break;
case Proto::WhatsAppProxy: updateIfPresent(m_whatsAppProxyConfigModel, containerConfig.getWhatsAppProxyProtocolConfig()); break;
#ifdef Q_OS_WINDOWS
case Proto::Ikev2: updateIfPresent(m_ikev2ConfigModel, containerConfig.getIkev2ProtocolConfig()); break;
#endif
@@ -32,6 +32,7 @@
#include "ui/models/services/mtProxyConfigModel.h"
#include "ui/models/services/telemtConfigModel.h"
#include "ui/models/services/tProxyConfigModel.h"
#include "ui/models/services/whatsAppProxyConfigModel.h"
class InstallUiController : public QObject
{
@@ -55,6 +56,7 @@ public:
MtProxyConfigModel* mtConfigModel,
TelemtConfigModel* telemtConfigModel,
TProxyConfigModel* tProxyConfigModel,
WhatsAppProxyConfigModel* whatsAppProxyConfigModel,
ConnectionController* connectionController,
QObject *parent = nullptr);
~InstallUiController();
@@ -163,6 +165,7 @@ private:
MtProxyConfigModel* m_mtProxyConfigModel;
TelemtConfigModel* m_telemtConfigModel;
TProxyConfigModel* m_tProxyConfigModel;
WhatsAppProxyConfigModel* m_whatsAppProxyConfigModel;
ConnectionController* m_connectionController;
ServerCredentials m_processedServerCredentials;
@@ -576,6 +576,8 @@ QStringList ServersUiController::getAllInstalledServicesName(int serverIndex) co
servicesName.append("Telemt");
} else if (container == DockerContainer::TProxy) {
servicesName.append("TProxy");
} else if (container == DockerContainer::WhatsAppProxy) {
servicesName.append("WhatsApp");
}
}
}
+2
View File
@@ -78,6 +78,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
case IsMtProxyRole: return container == DockerContainer::MtProxy;
case IsTelemtRole: return container == DockerContainer::Telemt;
case IsTProxyRole: return container == DockerContainer::TProxy;
case IsWhatsAppProxyRole: return container == DockerContainer::WhatsAppProxy;
case InstallPageOrderRole: return ContainerUtils::installPageOrder(container);
}
@@ -193,5 +194,6 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
roles[IsMtProxyRole] = "isMtProxy";
roles[IsTelemtRole] = "isTelemt";
roles[IsTProxyRole] = "isTProxy";
roles[IsWhatsAppProxyRole] = "isWhatsAppProxy";
return roles;
}
+1
View File
@@ -54,6 +54,7 @@ public:
IsMtProxyRole,
IsTelemtRole,
IsTProxyRole,
IsWhatsAppProxyRole,
};
Q_INVOKABLE void openContainerSettings(int containerIndex);
+3
View File
@@ -45,6 +45,7 @@ QHash<int, QByteArray> ProtocolsModel::roleNames() const
roles[IsMtProxyRole] = "isMtProxy";
roles[IsTelemtRole] = "isTelemt";
roles[IsTProxyRole] = "isTProxy";
roles[IsWhatsAppProxyRole] = "isWhatsAppProxy";
return roles;
}
@@ -77,6 +78,7 @@ QVariant ProtocolsModel::data(const QModelIndex &index, int role) const
case IsMtProxyRole: return proto == Proto::MtProxy;
case IsTelemtRole: return proto == Proto::Telemt;
case IsTProxyRole: return proto == Proto::TProxy;
case IsWhatsAppProxyRole: return proto == Proto::WhatsAppProxy;
case RawConfigRole:
return getRawConfig();
case IsClientProtocolExistsRole:
@@ -133,6 +135,7 @@ PageLoader::PageEnum ProtocolsModel::serverProtocolPage(Proto protocol) const
case Proto::MtProxy: return PageLoader::PageEnum::PageServiceMtProxySettings;
case Proto::Telemt: return PageLoader::PageEnum::PageServiceTelemtSettings;
case Proto::TProxy: return PageLoader::PageEnum::PageServiceTProxySettings;
case Proto::WhatsAppProxy: return PageLoader::PageEnum::PageServiceWhatsAppProxySettings;
default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
}
}
+1
View File
@@ -29,6 +29,7 @@ public:
IsMtProxyRole,
IsTelemtRole,
IsTProxyRole,
IsWhatsAppProxyRole,
};
explicit ProtocolsModel(QObject *parent = nullptr);
@@ -0,0 +1,155 @@
#include "whatsAppProxyConfigModel.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/networkUtilities.h"
#include <QHostAddress>
#include <QRegularExpression>
using namespace amnezia;
namespace
{
const QRegularExpression kHostnameRe(
QStringLiteral("^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+$"));
}
WhatsAppProxyConfigModel::WhatsAppProxyConfigModel(QObject *parent) : QObject(parent)
{
}
bool WhatsAppProxyConfigModel::isEnabled() const
{
return m_protocolConfig.isEnabled;
}
void WhatsAppProxyConfigModel::updateModel(DockerContainer container, const WhatsAppProxyProtocolConfig &protocolConfig)
{
m_container = container;
m_protocolConfig = protocolConfig;
emit changed();
}
void WhatsAppProxyConfigModel::updateModel(const QJsonObject &config)
{
m_fullConfig = config;
m_protocolConfig = WhatsAppProxyProtocolConfig::fromJson(config.value(configKey::whatsappproxy).toObject());
m_protocolConfig.port = m_protocolConfig.effectivePort();
m_protocolConfig.mediaPort = m_protocolConfig.effectiveMediaPort();
emit changed();
}
QJsonObject WhatsAppProxyConfigModel::getConfig()
{
m_fullConfig.insert(configKey::whatsappproxy, m_protocolConfig.toJson());
return m_fullConfig;
}
WhatsAppProxyProtocolConfig WhatsAppProxyConfigModel::getProtocolConfig()
{
return m_protocolConfig;
}
void WhatsAppProxyConfigModel::setPort(const QString &port)
{
const QString p = sanitizePortFieldText(port);
if (m_protocolConfig.port == p) {
return;
}
m_protocolConfig.port = p;
emit changed();
}
void WhatsAppProxyConfigModel::setMediaPort(const QString &port)
{
const QString p = sanitizePortFieldText(port);
if (m_protocolConfig.mediaPort == p) {
return;
}
m_protocolConfig.mediaPort = p;
emit changed();
}
void WhatsAppProxyConfigModel::setPublicHost(const QString &host)
{
const QString h = host.trimmed();
if (!isValidPublicHost(h) || m_protocolConfig.publicHost == h) {
return;
}
m_protocolConfig.publicHost = h;
emit changed();
}
void WhatsAppProxyConfigModel::setEnabled(bool enabled)
{
if (m_protocolConfig.isEnabled == enabled) {
return;
}
m_protocolConfig.isEnabled = enabled;
emit changed();
}
QString WhatsAppProxyConfigModel::getPort() const
{
return m_protocolConfig.effectivePort();
}
QString WhatsAppProxyConfigModel::getMediaPort() const
{
return m_protocolConfig.effectiveMediaPort();
}
QString WhatsAppProxyConfigModel::getPublicHost() const
{
return m_protocolConfig.publicHost;
}
QString WhatsAppProxyConfigModel::defaultPort() const
{
return QString(protocols::whatsAppProxy::defaultPort);
}
QString WhatsAppProxyConfigModel::defaultMediaPort() const
{
return QString(protocols::whatsAppProxy::defaultMediaPort);
}
bool WhatsAppProxyConfigModel::isValidPort(const QString &port) const
{
bool ok = false;
const int n = port.toInt(&ok);
return ok && n >= 1 && n <= 65535;
}
bool WhatsAppProxyConfigModel::isValidPublicHost(const QString &host) const
{
const QString t = host.trimmed();
if (t.isEmpty()) {
return true;
}
if (t.length() > 253) {
return false;
}
const QHostAddress address(t);
if (address.protocol() == QHostAddress::IPv4Protocol) {
return NetworkUtilities::checkIPv4Format(t);
}
if (address.protocol() == QHostAddress::IPv6Protocol) {
return !address.isNull() && !address.isLoopback();
}
return kHostnameRe.match(t).hasMatch();
}
QString WhatsAppProxyConfigModel::sanitizePortFieldText(const QString &input) const
{
QString out;
out.reserve(qMin(input.size(), 5));
for (const QChar &c : input) {
const ushort u = c.unicode();
if (u >= '0' && u <= '9' && out.size() < 5) {
out.append(c);
}
}
return out;
}
@@ -0,0 +1,55 @@
#ifndef WHATSAPPPROXYCONFIGMODEL_H
#define WHATSAPPPROXYCONFIGMODEL_H
#include "core/models/protocols/whatsAppProxyProtocolConfig.h"
#include "core/utils/containerEnum.h"
#include <QJsonObject>
#include <QObject>
class WhatsAppProxyConfigModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QString port READ getPort WRITE setPort NOTIFY changed)
Q_PROPERTY(QString mediaPort READ getMediaPort WRITE setMediaPort NOTIFY changed)
Q_PROPERTY(QString publicHost READ getPublicHost WRITE setPublicHost NOTIFY changed)
Q_PROPERTY(bool isEnabled READ isEnabled WRITE setEnabled NOTIFY changed)
public:
explicit WhatsAppProxyConfigModel(QObject *parent = nullptr);
bool isEnabled() const;
public slots:
void updateModel(amnezia::DockerContainer container, const amnezia::WhatsAppProxyProtocolConfig &protocolConfig);
void updateModel(const QJsonObject &config);
QJsonObject getConfig();
amnezia::WhatsAppProxyProtocolConfig getProtocolConfig();
Q_INVOKABLE void setPort(const QString &port);
Q_INVOKABLE void setMediaPort(const QString &port);
Q_INVOKABLE void setPublicHost(const QString &host);
Q_INVOKABLE void setEnabled(bool enabled);
Q_INVOKABLE QString getPort() const;
Q_INVOKABLE QString getMediaPort() const;
Q_INVOKABLE QString getPublicHost() const;
Q_INVOKABLE QString defaultPort() const;
Q_INVOKABLE QString defaultMediaPort() const;
Q_INVOKABLE bool isValidPort(const QString &port) const;
Q_INVOKABLE bool isValidPublicHost(const QString &host) const;
Q_INVOKABLE QString sanitizePortFieldText(const QString &input) const;
signals:
void changed();
private:
amnezia::DockerContainer m_container;
QJsonObject m_fullConfig;
amnezia::WhatsAppProxyProtocolConfig m_protocolConfig;
};
#endif // WHATSAPPPROXYCONFIGMODEL_H
@@ -76,6 +76,9 @@ ListViewType {
} else if (isTProxy) {
TProxyConfigModel.updateModel(config)
PageController.goToPage(PageEnum.PageServiceTProxySettings, false)
} else if (isWhatsAppProxy) {
WhatsAppProxyConfigModel.updateModel(config)
PageController.goToPage(PageEnum.PageServiceWhatsAppProxySettings, false)
} else {
InstallController.updateProtocols(ServersUiController.processedServerId, containerIndex)
PageController.goToPage(PageEnum.PageSettingsServerProtocol)
@@ -0,0 +1,567 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import ProtocolEnum 1.0
import Style 1.0
import MtProxyConfig 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
property int containerStatus: 1
property int statusErrorCode: 0
property bool isUpdating: false
property bool isCheckingStatus: false
property bool previousEnabled: true
property int previousContainerStatus: 1
property string savedPublicHost: ""
property string savedPort: ""
property string savedMediaPort: ""
property bool pendingUpdateAfterEnable: false
property bool remoteOperationBusy: false
readonly property bool networkBlocked: !NetworkReachabilityController.hasInternetAccess
readonly property bool pageBusy: isCheckingStatus || isUpdating || remoteOperationBusy
readonly property bool navigationBlockedWhileBusy: pageBusy
property bool pageOpenHandled: false
property bool busyIndicatorShown: false
property bool containerStatusRefreshCallPending: false
readonly property bool fieldsEditable: WhatsAppProxyConfigModel.isEnabled && containerStatus === 1 && !root.pageBusy
function effectiveHost() {
return root.savedPublicHost !== "" ? root.savedPublicHost : ServersUiController.serverHostName(ServersUiController.processedServerId)
}
function shareText() {
return qsTr("WhatsApp proxy") + "\n"
+ qsTr("Proxy address: %1").arg(root.effectiveHost()) + "\n"
+ qsTr("Chat port: %1 (TLS on)").arg(root.savedPort) + "\n"
+ qsTr("Media port: %1").arg(root.savedMediaPort) + "\n"
+ qsTr("WhatsApp: Settings > Storage and data > Proxy")
}
function saveCurrentValues() {
savedPublicHost = WhatsAppProxyConfigModel.getPublicHost()
savedPort = WhatsAppProxyConfigModel.getPort()
savedMediaPort = WhatsAppProxyConfigModel.getMediaPort()
}
function syncPageBusyIndicator() {
if (!root.pageOpenHandled) {
return
}
var wantBusy = root.pageBusy
if (wantBusy === root.busyIndicatorShown) {
return
}
root.busyIndicatorShown = wantBusy
PageController.showBusyIndicator(wantBusy)
}
onPageBusyChanged: syncPageBusyIndicator()
function requestContainerStatusRefresh() {
if (!NetworkReachabilityController.hasInternetAccess) {
isCheckingStatus = false
syncPageBusyIndicator()
return
}
isCheckingStatus = true
syncPageBusyIndicator()
InstallController.refreshContainerStatus(ServersUiController.processedServerId,
ServersUiController.processedContainerIndex)
}
function scheduleContainerStatusRefresh() {
if (containerStatusRefreshCallPending) {
return
}
containerStatusRefreshCallPending = true
Qt.callLater(function () {
containerStatusRefreshCallPending = false
root.requestContainerStatusRefresh()
})
}
function onPageShown() {
if (root.pageOpenHandled) {
return
}
root.pageOpenHandled = true
PageController.disableControls(navigationBlockedWhileBusy)
isCheckingStatus = NetworkReachabilityController.hasInternetAccess
syncPageBusyIndicator()
root.scheduleContainerStatusRefresh()
}
function scheduleUpdate() {
Qt.callLater(function () {
InstallController.updateServerConfig(ServersUiController.processedServerId,
ServersUiController.processedContainerIndex, ProtocolEnum.WhatsAppProxy, false)
})
}
Connections {
target: InstallController
function onUpdateContainerFinished(message, closePage) {
if (!root.visible) {
isUpdating = false
isCheckingStatus = false
return
}
isUpdating = false
containerStatus = 1
root.saveCurrentValues()
PageController.showNotificationMessage(message)
}
function onInstallationErrorOccurred() {
if (!root.visible) {
isUpdating = false
isCheckingStatus = false
return
}
isUpdating = false
containerStatus = previousContainerStatus
WhatsAppProxyConfigModel.setEnabled(previousEnabled)
WhatsAppProxyConfigModel.setPublicHost(savedPublicHost)
WhatsAppProxyConfigModel.setPort(savedPort)
WhatsAppProxyConfigModel.setMediaPort(savedMediaPort)
}
function onSetContainerEnabledFinished(enabled) {
if (!root.visible) {
isUpdating = false
return
}
if (enabled && root.pendingUpdateAfterEnable) {
root.pendingUpdateAfterEnable = false
isUpdating = true
root.scheduleUpdate()
return
}
isUpdating = false
containerStatus = enabled ? 1 : 2
PageController.showNotificationMessage(enabled ? qsTr("WhatsApp proxy started") : qsTr("WhatsApp proxy stopped"))
}
function onServerIsBusy(busy) {
root.remoteOperationBusy = busy
}
function onContainerStatusRefreshed(status, errorCode) {
if (!root.visible) {
isCheckingStatus = false
return
}
containerStatus = status
root.statusErrorCode = errorCode
if (status === 3 && errorCode !== 0) {
PageController.showNotificationMessage(
qsTr("Settings locked: connection timed out (error code %1). Re-open the page to retry.").arg(errorCode))
}
root.saveCurrentValues()
if (status === 1) {
WhatsAppProxyConfigModel.setEnabled(true)
} else if (status === 2) {
WhatsAppProxyConfigModel.setEnabled(false)
}
isCheckingStatus = false
syncPageBusyIndicator()
}
}
Component.onCompleted: {
root.saveCurrentValues()
Qt.callLater(root.onPageShown)
}
onNavigationBlockedWhileBusyChanged: {
if (root.visible) {
PageController.disableControls(navigationBlockedWhileBusy)
}
}
onVisibleChanged: {
if (!visible) {
root.pageOpenHandled = false
containerStatusRefreshCallPending = false
isCheckingStatus = false
busyIndicatorShown = false
statusErrorCode = 0
PageController.disableControls(false)
PageController.showBusyIndicator(false)
} else {
root.onPageShown()
}
}
Connections {
target: NetworkReachabilityController
function onHasInternetAccessChanged() {
if (!root.visible) {
return
}
if (NetworkReachabilityController.hasInternetAccess) {
root.scheduleContainerStatusRefresh()
}
}
}
Item {
id: contentLayer
anchors.fill: parent
enabled: !root.pageBusy
ColumnLayout {
id: pageHeader
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
NavigationBarType {
Layout.fillWidth: true
title: qsTr("WhatsApp proxy settings")
}
LinkButtonType {
Layout.leftMargin: 16
Layout.rightMargin: 16
size: LinkButtonType.Medium
horizontalPadding: 14
verticalPadding: 8
text: qsTr("Read more about WhatsApp proxy")
onClicked: Qt.openUrlExternally("https://github.com/WhatsApp/proxy")
}
SmallTextType {
Layout.fillWidth: true
Layout.topMargin: 12
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: root.networkBlocked
text: qsTr("No internet connection. Connect to the internet to change WhatsApp proxy settings.")
}
}
TabsType {
id: mainTabBar
anchors.top: pageHeader.bottom
anchors.topMargin: 24
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
TabButtonType {
text: qsTr("Connection")
}
TabButtonType {
text: qsTr("Settings")
}
}
StackLayout {
anchors.top: mainTabBar.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
currentIndex: mainTabBar.currentIndex
Item {
FlickableType {
id: connectionFlickable
anchors.top: parent.top
contentHeight: connectionContent.implicitHeight
ColumnLayout {
id: connectionContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
SmallTextType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Enter these values in WhatsApp: Settings > Storage and data > Proxy")
}
ListGroupType {
Layout.fillWidth: true
Layout.topMargin: 12
Layout.leftMargin: 16
Layout.rightMargin: 16
CopyableValueRow {
Layout.fillWidth: true
copiedText: qsTr("Copied")
label: qsTr("Proxy address")
value: root.effectiveHost()
}
CopyableValueRow {
Layout.fillWidth: true
copiedText: qsTr("Copied")
label: qsTr("Chat port (TLS on)")
value: root.savedPort
}
CopyableValueRow {
Layout.fillWidth: true
copiedText: qsTr("Copied")
label: qsTr("Media port")
value: root.savedMediaPort
showSeparator: false
}
}
ButtonType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Share")
onClicked: {
ExportController.generateQrFromStringRaw(root.shareText())
PageController.goToShareConnectionPage(qsTr("WhatsApp proxy"),
qsTr("WhatsApp proxy settings"), "", ".txt", "whatsapp_proxy")
}
}
CaptionTextType {
Layout.fillWidth: true
Layout.topMargin: 12
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Anyone who knows the address and ports can use the proxy. Calls are not proxied.")
}
ButtonType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 16 + PageController.safeAreaBottomMargin
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: ServersUiController.isProcessedServerHasWriteAccess()
variant: ButtonType.Destructive
text: qsTr("Delete WhatsApp proxy")
onClicked: {
var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName())
var yesButtonFunction = function () {
PageController.goToPage(PageEnum.PageDeinstalling)
InstallController.removeContainer(ServersUiController.processedServerId,
ServersUiController.processedContainerIndex)
}
Dialogs.askQuestion(headerText,
qsTr("The proxy will be stopped and all users will lose access."),
qsTr("Continue"), qsTr("Cancel"), yesButtonFunction, function () {})
}
}
}
}
}
Item {
FlickableType {
id: settingsFlickable
anchors.top: parent.top
contentHeight: settingsContent.implicitHeight
ColumnLayout {
id: settingsContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 12
SwitchRowType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Enable WhatsApp proxy")
checked: WhatsAppProxyConfigModel.isEnabled
enabled: containerStatus !== 0 && containerStatus !== 3 && !root.pageBusy && !root.networkBlocked
onToggled: function () {
if (checked !== WhatsAppProxyConfigModel.isEnabled) {
previousEnabled = WhatsAppProxyConfigModel.isEnabled
previousContainerStatus = containerStatus
WhatsAppProxyConfigModel.isEnabled = checked
isUpdating = true
if (checked) {
root.pendingUpdateAfterEnable = true
}
InstallController.setContainerEnabled(ServersUiController.processedServerId,
ServersUiController.processedContainerIndex, checked)
}
}
}
SmallTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: !root.fieldsEditable && !root.pageBusy
text: (containerStatus === 1 || containerStatus === 2)
? qsTr("Enable WhatsApp proxy to edit settings")
: (statusErrorCode !== 0
? qsTr("Settings locked: connection timed out (error code %1). Re-open the page to retry.").arg(statusErrorCode)
: qsTr("Cannot reach the server — settings are unavailable"))
}
TextFieldType {
id: publicHostTextField
Layout.topMargin: 12
enabled: root.fieldsEditable
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Public host / IP")
textField.placeholderText: ServersUiController.serverHostName(ServersUiController.processedServerId)
textField.text: WhatsAppProxyConfigModel.publicHost
textField.maximumLength: 253
textField.validator: PublicHostInputValidator {
}
textField.onEditingFinished: {
textField.text = textField.text.trim()
if (!WhatsAppProxyConfigModel.isValidPublicHost(textField.text)) {
publicHostTextField.errorText = qsTr("Enter a valid IP address or domain name")
return
}
publicHostTextField.errorText = ""
}
}
CaptionTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Leave empty to use server IP automatically")
}
TextFieldType {
id: portTextField
enabled: root.fieldsEditable
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Chat port")
textField.placeholderText: WhatsAppProxyConfigModel.defaultPort()
textField.text: WhatsAppProxyConfigModel.port
textField.maximumLength: 5
textField.inputMethodHints: Qt.ImhDigitsOnly
textField.validator: IntValidator { bottom: 1; top: 65535 }
}
TextFieldType {
id: mediaPortTextField
enabled: root.fieldsEditable
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Media port")
textField.placeholderText: WhatsAppProxyConfigModel.defaultMediaPort()
textField.text: WhatsAppProxyConfigModel.mediaPort
textField.maximumLength: 5
textField.inputMethodHints: Qt.ImhDigitsOnly
textField.validator: IntValidator { bottom: 1; top: 65535 }
}
CaptionTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Changing a port reinstalls the container. WhatsApp uses 443 and 587 by default; with other ports users enter them manually and enable TLS for the chat port.")
}
ButtonType {
Layout.fillWidth: true
Layout.topMargin: 12
Layout.bottomMargin: 16 + PageController.safeAreaBottomMargin
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Save")
enabled: root.fieldsEditable && !root.networkBlocked
onClicked: {
publicHostTextField.errorText = ""
portTextField.errorText = ""
mediaPortTextField.errorText = ""
var host = publicHostTextField.textField.text.trim()
var chatPort = WhatsAppProxyConfigModel.sanitizePortFieldText(portTextField.textField.text)
var mediaPort = WhatsAppProxyConfigModel.sanitizePortFieldText(mediaPortTextField.textField.text)
var hasError = false
if (!WhatsAppProxyConfigModel.isValidPublicHost(host)) {
publicHostTextField.errorText = qsTr("Enter a valid IP address or domain name")
hasError = true
}
if (!WhatsAppProxyConfigModel.isValidPort(chatPort)) {
portTextField.errorText = qsTr("The port must be in the range of 1 to 65535")
hasError = true
}
if (!WhatsAppProxyConfigModel.isValidPort(mediaPort)) {
mediaPortTextField.errorText = qsTr("The port must be in the range of 1 to 65535")
hasError = true
} else if (mediaPort === chatPort) {
mediaPortTextField.errorText = qsTr("The media port must differ from the chat port")
hasError = true
}
if (hasError) {
return
}
previousContainerStatus = containerStatus
WhatsAppProxyConfigModel.setPublicHost(host)
WhatsAppProxyConfigModel.setPort(chatPort)
WhatsAppProxyConfigModel.setMediaPort(mediaPort)
isUpdating = true
root.scheduleUpdate()
}
}
}
}
}
}
}
}
@@ -59,6 +59,7 @@ PageType {
required property string detailedDescription
required property int dockerContainer
required property bool isTProxy
required property bool isWhatsAppProxy
function install() {
if (isTProxy) {
@@ -96,6 +97,20 @@ PageType {
return
}
if (isWhatsAppProxy) {
mediaPort.errorText = ""
if (!mediaPort.textField.acceptableInput) {
mediaPort.errorText = qsTr("The port must be in the range of 1 to 65535")
return
}
if (mediaPort.textField.text === port.textField.text) {
mediaPort.errorText = qsTr("The media port must differ from the chat port")
return
}
WhatsAppProxyConfigModel.setPort(port.textField.text)
WhatsAppProxyConfigModel.setMediaPort(mediaPort.textField.text)
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.install(dockerContainer, port.textField.text, transportProtoSelector.currentIndex, ServersUiController.processedServerId)
}
@@ -155,11 +170,33 @@ PageType {
visible: !delegateRoot.isTProxy
headerText: qsTr("Port")
headerText: delegateRoot.isWhatsAppProxy ? qsTr("Chat port") : qsTr("Port")
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
}
TextFieldType {
id: mediaPort
Layout.fillWidth: true
visible: delegateRoot.isWhatsAppProxy
headerText: qsTr("Media port")
textField.text: WhatsAppProxyConfigModel.defaultMediaPort()
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
}
CaptionTextType {
Layout.fillWidth: true
visible: delegateRoot.isWhatsAppProxy
text: qsTr("WhatsApp uses chat port 443 and media port 587 by default. With other ports, users must enter them in WhatsApp proxy settings and enable TLS for the chat port. Calls are not proxied.")
color: AmneziaStyle.color.textAccent
}
TextFieldType {
id: tproxyHostname
+1
View File
@@ -133,6 +133,7 @@
<file>Pages2/PageServiceMtProxySettings.qml</file>
<file>Pages2/PageServiceTelemtSettings.qml</file>
<file>Pages2/PageServiceTProxySettings.qml</file>
<file>Pages2/PageServiceWhatsAppProxySettings.qml</file>
<file>Pages2/PageServiceSftpSettings.qml</file>
<file>Pages2/PageServiceSocksProxySettings.qml</file>
<file>Pages2/PageServiceTorWebsiteSettings.qml</file>
+1
View File
@@ -50,6 +50,7 @@ namespace PageLoader
PageServiceMtProxySettings,
PageServiceTelemtSettings,
PageServiceTProxySettings,
PageServiceWhatsAppProxySettings,
PageSetupWizardStart,
PageSetupWizardCredentials,