aqi: Add Qt6 support
This commit is contained in:
parent
2575f43518
commit
3257413b74
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2020, nymea GmbH
|
* Copyright 2013 - 2025, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -54,8 +54,9 @@ QUuid AirQualityIndex::searchByName(const QString &name)
|
|||||||
{
|
{
|
||||||
if (m_apiKey.isEmpty()) {
|
if (m_apiKey.isEmpty()) {
|
||||||
qCWarning(dcAirQualityIndex()) << "API key is not set, not sending request";
|
qCWarning(dcAirQualityIndex()) << "API key is not set, not sending request";
|
||||||
return "";
|
return QUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
QUuid requestId = QUuid::createUuid();;
|
QUuid requestId = QUuid::createUuid();;
|
||||||
QUrl url;
|
QUrl url;
|
||||||
url.setUrl(m_baseUrl);
|
url.setUrl(m_baseUrl);
|
||||||
@ -75,13 +76,14 @@ QUuid AirQualityIndex::searchByName(const QString &name)
|
|||||||
|
|
||||||
// Check HTTP status code
|
// Check HTTP status code
|
||||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||||
if (status == 400) {
|
if (status == 400)
|
||||||
qCWarning(dcAirQualityIndex()) << "Request error due to exceeded request quota";
|
qCWarning(dcAirQualityIndex()) << "Request error due to exceeded request quota";
|
||||||
}
|
|
||||||
requestExecuted(requestId, false);
|
emit requestExecuted(requestId, false);
|
||||||
qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString();
|
qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray rawData = reply->readAll();
|
QByteArray rawData = reply->readAll();
|
||||||
qCDebug(dcAirQualityIndex()) << "Search response" << rawData;
|
qCDebug(dcAirQualityIndex()) << "Search response" << rawData;
|
||||||
|
|
||||||
@ -95,7 +97,7 @@ QUuid AirQualityIndex::searchByName(const QString &name)
|
|||||||
|
|
||||||
QList<Station> stations;
|
QList<Station> stations;
|
||||||
QVariantList stationList = doc.toVariant().toMap().value("data").toList();
|
QVariantList stationList = doc.toVariant().toMap().value("data").toList();
|
||||||
foreach (QVariant stationVariant, stationList) {
|
foreach (const QVariant &stationVariant, stationList) {
|
||||||
Station station;
|
Station station;
|
||||||
station.aqi = stationVariant.toMap().value("aqi").toInt();
|
station.aqi = stationVariant.toMap().value("aqi").toInt();
|
||||||
station.idx = stationVariant.toMap().value("idx").toInt();
|
station.idx = stationVariant.toMap().value("idx").toInt();
|
||||||
@ -107,10 +109,11 @@ QUuid AirQualityIndex::searchByName(const QString &name)
|
|||||||
station.location.longitude = stationVariant.toMap().value("city").toMap().value("geo").toList().last().toDouble();
|
station.location.longitude = stationVariant.toMap().value("city").toMap().value("geo").toList().last().toDouble();
|
||||||
stations.append(station);
|
stations.append(station);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stations.isEmpty())
|
if (!stations.isEmpty())
|
||||||
emit stationsReceived(requestId, stations);
|
emit stationsReceived(requestId, stations);
|
||||||
|
|
||||||
requestExecuted(requestId, true);
|
emit requestExecuted(requestId, true);
|
||||||
});
|
});
|
||||||
return requestId;
|
return requestId;
|
||||||
}
|
}
|
||||||
@ -119,18 +122,23 @@ QUuid AirQualityIndex::getDataByIp()
|
|||||||
{
|
{
|
||||||
if (m_apiKey.isEmpty()) {
|
if (m_apiKey.isEmpty()) {
|
||||||
qCWarning(dcAirQualityIndex()) << "API key is not set, not sending request";
|
qCWarning(dcAirQualityIndex()) << "API key is not set, not sending request";
|
||||||
return "";
|
return QUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
QUuid requestId = QUuid::createUuid();
|
QUuid requestId = QUuid::createUuid();
|
||||||
|
|
||||||
QUrl url;
|
QUrl url;
|
||||||
url.setUrl(m_baseUrl);
|
url.setUrl(m_baseUrl);
|
||||||
url.setPath("/feed/here/");
|
url.setPath("/feed/here/");
|
||||||
|
|
||||||
QUrlQuery query;
|
QUrlQuery query;
|
||||||
query.addQueryItem("token", m_apiKey);
|
query.addQueryItem("token", m_apiKey);
|
||||||
url.setQuery(query);
|
url.setQuery(query);
|
||||||
|
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setUrl(url);
|
request.setUrl(url);
|
||||||
request.setRawHeader("User-Agent", "nymea");
|
request.setRawHeader("User-Agent", "nymea");
|
||||||
|
|
||||||
qCDebug(dcAirQualityIndex()) << "Get data by IP request" << url.toString();
|
qCDebug(dcAirQualityIndex()) << "Get data by IP request" << url.toString();
|
||||||
QNetworkReply *reply = m_networkAccessManager->get(request);
|
QNetworkReply *reply = m_networkAccessManager->get(request);
|
||||||
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
|
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
|
||||||
@ -139,17 +147,20 @@ QUuid AirQualityIndex::getDataByIp()
|
|||||||
|
|
||||||
// Check HTTP status code
|
// Check HTTP status code
|
||||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||||
if (status == 400) {
|
if (status == 400)
|
||||||
qCWarning(dcAirQualityIndex()) << "Request error due to exceeded request quota";
|
qCWarning(dcAirQualityIndex()) << "Request error due to exceeded request quota";
|
||||||
}
|
|
||||||
requestExecuted(requestId, false);
|
emit requestExecuted(requestId, false);
|
||||||
qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString();
|
qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parseData(requestId, reply->readAll()))
|
if (!parseData(requestId, reply->readAll()))
|
||||||
requestExecuted(requestId, false);
|
emit requestExecuted(requestId, false);
|
||||||
requestExecuted(requestId, true);
|
|
||||||
|
emit requestExecuted(requestId, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
return requestId;
|
return requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,19 +168,23 @@ QUuid AirQualityIndex::getDataByGeolocation(double lat, double lng)
|
|||||||
{
|
{
|
||||||
if (m_apiKey.isEmpty()) {
|
if (m_apiKey.isEmpty()) {
|
||||||
qCWarning(dcAirQualityIndex()) << "API key is not set, not sending request";
|
qCWarning(dcAirQualityIndex()) << "API key is not set, not sending request";
|
||||||
return "";
|
return QUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
QUuid requestId = QUuid::createUuid();
|
QUuid requestId = QUuid::createUuid();
|
||||||
|
|
||||||
QUrl url;
|
QUrl url;
|
||||||
url.setUrl(m_baseUrl);
|
url.setUrl(m_baseUrl);
|
||||||
url.setPath(QString("/feed/geo:%1;%2/").arg(lat).arg(lng));
|
url.setPath(QString("/feed/geo:%1;%2/").arg(lat).arg(lng));
|
||||||
|
|
||||||
QUrlQuery query;
|
QUrlQuery query;
|
||||||
query.addQueryItem("token", m_apiKey);
|
query.addQueryItem("token", m_apiKey);
|
||||||
url.setQuery(query);
|
url.setQuery(query);
|
||||||
|
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setUrl(url);
|
request.setUrl(url);
|
||||||
request.setRawHeader("User-Agent", "nymea");
|
request.setRawHeader("User-Agent", "nymea");
|
||||||
|
|
||||||
qCDebug(dcAirQualityIndex()) << "Get data by geo location request" << url.toString();
|
qCDebug(dcAirQualityIndex()) << "Get data by geo location request" << url.toString();
|
||||||
QNetworkReply *reply = m_networkAccessManager->get(request);
|
QNetworkReply *reply = m_networkAccessManager->get(request);
|
||||||
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
|
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
|
||||||
@ -178,21 +193,22 @@ QUuid AirQualityIndex::getDataByGeolocation(double lat, double lng)
|
|||||||
|
|
||||||
// Check HTTP status code
|
// Check HTTP status code
|
||||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||||
if (status == 400) {
|
if (status == 400)
|
||||||
qCWarning(dcAirQualityIndex()) << "Request error due to exceeded request quota";
|
qCWarning(dcAirQualityIndex()) << "Request error due to exceeded request quota";
|
||||||
}
|
|
||||||
requestExecuted(requestId, false);
|
emit requestExecuted(requestId, false);
|
||||||
qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString();
|
qCWarning(dcAirQualityIndex()) << "Request error:" << status << reply->errorString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!parseData(requestId, reply->readAll()))
|
if (!parseData(requestId, reply->readAll()))
|
||||||
requestExecuted(requestId, false);
|
emit requestExecuted(requestId, false);
|
||||||
requestExecuted(requestId, true);
|
|
||||||
|
emit requestExecuted(requestId, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
return requestId;
|
return requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool AirQualityIndex::parseData(QUuid requestId, const QByteArray &data)
|
bool AirQualityIndex::parseData(QUuid requestId, const QByteArray &data)
|
||||||
{
|
{
|
||||||
qCDebug(dcAirQualityIndex()) << "Parsing data" << data;
|
qCDebug(dcAirQualityIndex()) << "Parsing data" << data;
|
||||||
@ -241,6 +257,7 @@ bool AirQualityIndex::parseData(QUuid requestId, const QByteArray &data)
|
|||||||
aqiData.co = iaqi["co"].toMap().value("v").toDouble();
|
aqiData.co = iaqi["co"].toMap().value("v").toDouble();
|
||||||
aqiData.temperature = iaqi["t"].toMap().value("v").toDouble();
|
aqiData.temperature = iaqi["t"].toMap().value("v").toDouble();
|
||||||
aqiData.windSpeed = iaqi["w"].toMap().value("v").toDouble();
|
aqiData.windSpeed = iaqi["w"].toMap().value("v").toDouble();
|
||||||
|
|
||||||
emit dataReceived(requestId, aqiData);
|
emit dataReceived(requestId, aqiData);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2020, nymea GmbH
|
* Copyright 2013 - 2025, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -31,12 +31,13 @@
|
|||||||
#ifndef AIRQUALITYINDEX_H
|
#ifndef AIRQUALITYINDEX_H
|
||||||
#define AIRQUALITYINDEX_H
|
#define AIRQUALITYINDEX_H
|
||||||
|
|
||||||
#include "network/networkaccessmanager.h"
|
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
#include <QTime>
|
#include <QTime>
|
||||||
|
|
||||||
|
#include <network/networkaccessmanager.h>
|
||||||
|
|
||||||
class AirQualityIndex : public QObject
|
class AirQualityIndex : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -70,6 +71,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
explicit AirQualityIndex(NetworkAccessManager *networkAccessManager, const QString &apiKey, QObject *parent = nullptr);
|
explicit AirQualityIndex(NetworkAccessManager *networkAccessManager, const QString &apiKey, QObject *parent = nullptr);
|
||||||
|
|
||||||
void setApiKey(const QString &apiKey);
|
void setApiKey(const QString &apiKey);
|
||||||
QUuid searchByName(const QString &name);
|
QUuid searchByName(const QString &name);
|
||||||
QUuid getDataByIp();
|
QUuid getDataByIp();
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
include(../plugins.pri)
|
include(../plugins.pri)
|
||||||
|
|
||||||
QT+= network
|
QT *= network
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
airqualityindex.cpp \
|
airqualityindex.cpp \
|
||||||
integrationpluginaqi.cpp \
|
integrationpluginaqi.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
airqualityindex.h \
|
airqualityindex.h \
|
||||||
integrationpluginaqi.h \
|
integrationpluginaqi.h
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2020, nymea GmbH
|
* Copyright 2013 - 2025, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -30,7 +30,8 @@
|
|||||||
|
|
||||||
#include "integrationpluginaqi.h"
|
#include "integrationpluginaqi.h"
|
||||||
#include "plugininfo.h"
|
#include "plugininfo.h"
|
||||||
#include "nymeasettings.h"
|
|
||||||
|
#include <nymeasettings.h>
|
||||||
|
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
|
|
||||||
@ -122,7 +123,8 @@ void IntegrationPluginAqi::discoverThings(ThingDiscoveryInfo *info)
|
|||||||
if(!createAqiConnection()) {
|
if(!createAqiConnection()) {
|
||||||
return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("API key is not available."));
|
return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("API key is not available."));
|
||||||
}
|
}
|
||||||
connect(info, &ThingDiscoveryInfo::aborted, [this] {
|
|
||||||
|
connect(info, &ThingDiscoveryInfo::aborted, this, [this] {
|
||||||
if (myThings().filterByThingClassId(airQualityIndexThingClassId).isEmpty()) {
|
if (myThings().filterByThingClassId(airQualityIndexThingClassId).isEmpty()) {
|
||||||
m_aqiConnection->deleteLater();
|
m_aqiConnection->deleteLater();
|
||||||
m_aqiConnection = nullptr;
|
m_aqiConnection = nullptr;
|
||||||
@ -133,7 +135,9 @@ void IntegrationPluginAqi::discoverThings(ThingDiscoveryInfo *info)
|
|||||||
}
|
}
|
||||||
QUuid requestId = m_aqiConnection->getDataByIp();
|
QUuid requestId = m_aqiConnection->getDataByIp();
|
||||||
m_asyncDiscovery.insert(requestId, info);
|
m_asyncDiscovery.insert(requestId, info);
|
||||||
connect(info, &ThingDiscoveryInfo::aborted, [=] {m_asyncDiscovery.remove(requestId);});
|
connect(info, &ThingDiscoveryInfo::aborted, this, [this, requestId] {
|
||||||
|
m_asyncDiscovery.remove(requestId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntegrationPluginAqi::setupThing(ThingSetupInfo *info)
|
void IntegrationPluginAqi::setupThing(ThingSetupInfo *info)
|
||||||
@ -148,7 +152,7 @@ void IntegrationPluginAqi::setupThing(ThingSetupInfo *info)
|
|||||||
QUuid requestId = m_aqiConnection->getDataByGeolocation(latitude, longitude);
|
QUuid requestId = m_aqiConnection->getDataByGeolocation(latitude, longitude);
|
||||||
m_asyncSetups.insert(requestId, info);
|
m_asyncSetups.insert(requestId, info);
|
||||||
|
|
||||||
connect(info, &ThingSetupInfo::aborted, [requestId, this] {
|
connect(info, &ThingSetupInfo::aborted, this, [requestId, this] {
|
||||||
m_asyncSetups.remove(requestId);
|
m_asyncSetups.remove(requestId);
|
||||||
if (myThings().filterByThingClassId(airQualityIndexThingClassId).isEmpty()) {
|
if (myThings().filterByThingClassId(airQualityIndexThingClassId).isEmpty()) {
|
||||||
m_aqiConnection->deleteLater();
|
m_aqiConnection->deleteLater();
|
||||||
@ -219,6 +223,7 @@ double IntegrationPluginAqi::convertFromAQI(int aqi, const QList<QPair<int, doub
|
|||||||
il = map.at(index - 1).first;
|
il = map.at(index - 1).first;
|
||||||
vl = map.at(index - 1).second;
|
vl = map.at(index - 1).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
ih = map.at(index).first;
|
ih = map.at(index).first;
|
||||||
vh = map.at(index).second;
|
vh = map.at(index).second;
|
||||||
double value = (aqi - il) * (vh - vl) / (ih - il) + vl;
|
double value = (aqi - il) * (vh - vl) / (ih - il) + vl;
|
||||||
@ -253,7 +258,6 @@ void IntegrationPluginAqi::onAirQualityDataReceived(QUuid requestId, AirQualityI
|
|||||||
if (!thing)
|
if (!thing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
thing->setStateValue(airQualityIndexConnectedStateTypeId, true);
|
thing->setStateValue(airQualityIndexConnectedStateTypeId, true);
|
||||||
thing->setStateValue(airQualityIndexHumidityStateTypeId, data.humidity);
|
thing->setStateValue(airQualityIndexHumidityStateTypeId, data.humidity);
|
||||||
thing->setStateValue(airQualityIndexTemperatureStateTypeId, data.temperature);
|
thing->setStateValue(airQualityIndexTemperatureStateTypeId, data.temperature);
|
||||||
@ -284,7 +288,6 @@ void IntegrationPluginAqi::onAirQualityStationsReceived(QUuid requestId, QList<A
|
|||||||
info->finish(Thing::ThingErrorNoError);
|
info->finish(Thing::ThingErrorNoError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (m_asyncRequests.contains(requestId)) {
|
if (m_asyncRequests.contains(requestId)) {
|
||||||
Thing * thing = myThings().findById(m_asyncRequests.value(requestId));
|
Thing * thing = myThings().findById(m_asyncRequests.value(requestId));
|
||||||
if (!thing) {
|
if (!thing) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2020, nymea GmbH
|
* Copyright 2013 - 2025, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -31,11 +31,11 @@
|
|||||||
#ifndef INTEGRATIONPLUGINAQI_H
|
#ifndef INTEGRATIONPLUGINAQI_H
|
||||||
#define INTEGRATIONPLUGINAQI_H
|
#define INTEGRATIONPLUGINAQI_H
|
||||||
|
|
||||||
#include "plugintimer.h"
|
#include <plugintimer.h>
|
||||||
#include "integrations/integrationplugin.h"
|
#include <integrations/integrationplugin.h>
|
||||||
#include "network/networkaccessmanager.h"
|
#include <network/networkaccessmanager.h>
|
||||||
#include "airqualityindex.h"
|
|
||||||
|
|
||||||
|
#include "airqualityindex.h"
|
||||||
#include "extern-plugininfo.h"
|
#include "extern-plugininfo.h"
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
@ -64,6 +64,7 @@ private:
|
|||||||
QHash<QUuid, ThingDiscoveryInfo *> m_asyncDiscovery;
|
QHash<QUuid, ThingDiscoveryInfo *> m_asyncDiscovery;
|
||||||
QHash<QUuid, ThingSetupInfo *> m_asyncSetups;
|
QHash<QUuid, ThingSetupInfo *> m_asyncSetups;
|
||||||
QHash<QUuid, ThingId> m_asyncRequests;
|
QHash<QUuid, ThingId> m_asyncRequests;
|
||||||
|
|
||||||
QString getApiKey();
|
QString getApiKey();
|
||||||
bool createAqiConnection();
|
bool createAqiConnection();
|
||||||
|
|
||||||
@ -74,6 +75,7 @@ private slots:
|
|||||||
void onRequestExecuted(QUuid requestId, bool success);
|
void onRequestExecuted(QUuid requestId, bool success);
|
||||||
void onAirQualityDataReceived(QUuid requestId, AirQualityIndex::AirQualityData data);
|
void onAirQualityDataReceived(QUuid requestId, AirQualityIndex::AirQualityData data);
|
||||||
void onAirQualityStationsReceived(QUuid requestId, QList<AirQualityIndex::Station> stations);
|
void onAirQualityStationsReceived(QUuid requestId, QList<AirQualityIndex::Station> stations);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // INTEGRATIONPLUGINAQI_H
|
#endif // INTEGRATIONPLUGINAQI_H
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user