#include "macro-action-http.hpp" #include "layout-helpers.hpp" #include #undef DELETE namespace advss { const std::string MacroActionHttp::id = "http_v2"; bool MacroActionHttp::_registered = MacroActionFactory::Register( MacroActionHttp::id, {MacroActionHttp::Create, MacroActionHttpEdit::Create, "AdvSceneSwitcher.action.http"}); static httplib::Headers getHeaders(const StringList &strings) { httplib::Headers headers; for (int i = 0; i < strings.size(); i = i + 2) { const auto pair = i + 1 >= strings.size() ? std::make_pair(std::string(strings.at(i)), "") : std::make_pair( std::string(strings.at(i)), std::string(strings.at(i + 1))); headers.emplace(pair); } return headers; } static httplib::Params getParams(const StringList &strings) { httplib::Params params; for (int i = 0; i < strings.size(); i = i + 2) { const auto pair = i + 1 >= strings.size() ? std::make_pair(std::string(strings.at(i)), "") : std::make_pair( std::string(strings.at(i)), std::string(strings.at(i + 1))); params.emplace(pair); } return params; } static void setTimeout(httplib::Client &client, const Duration &timeout) { const time_t seconds = timeout.Seconds(); const time_t usecs = timeout.Milliseconds() - (seconds * 1000); client.set_read_timeout(seconds, usecs); client.set_write_timeout(seconds, usecs); } void MacroActionHttp::SetupTempVars() { MacroAction::SetupTempVars(); AddTempvar("status", obs_module_text("AdvSceneSwitcher.tempVar.http.status")); AddTempvar("body", obs_module_text("AdvSceneSwitcher.tempVar.http.body")); AddTempvar("error", obs_module_text("AdvSceneSwitcher.tempVar.http.error"), obs_module_text( "AdvSceneSwitcher.tempVar.http.error.description")); } struct URLInfo { std::string host; std::string path; }; static URLInfo getURLInfo(const std::string &input) { if (input.empty()) { return {}; } QString urlInput = QString::fromStdString(input); if (!urlInput.contains("://")) { urlInput = "http://" + urlInput; } const QUrl url(urlInput); auto host = url.scheme().toStdString() + "://" + url.host().toStdString(); int port = url.port(); if (port != -1) { host += ":" + std::to_string(port); } auto path = url.path().toStdString(); if (path.empty()) { path = "/"; } return {host, path}; } bool MacroActionHttp::PerformAction() { const auto [host, path] = getURLInfo(_url); httplib::Client cli(host); setTimeout(cli, _timeout); const auto params = _setParams ? getParams(_params) : httplib::Params(); const auto headers = _setHeaders ? getHeaders(_headers) : httplib::Headers(); httplib::Result response; switch (_method) { case MacroActionHttp::Method::GET: response = cli.Get(path, params, headers); break; case MacroActionHttp::Method::POST: { const auto pathWithParam = httplib::append_query_params(path, params); response = cli.Post(pathWithParam, headers, _body, _contentType); break; } case MacroActionHttp::Method::PUT: { const auto pathWithParam = httplib::append_query_params(path, params); response = cli.Put(pathWithParam, headers, _body, _contentType); break; } case MacroActionHttp::Method::PATCH: { const auto pathWithParam = httplib::append_query_params(path, params); response = cli.Patch(pathWithParam, headers, _body, _contentType); break; } case MacroActionHttp::Method::DELETE: { const auto pathWithParam = httplib::append_query_params(path, params); response = cli.Delete(pathWithParam, headers, _body, _contentType); break; } default: break; } if (VerboseLoggingEnabled() && !response) { blog(LOG_INFO, "HTTP action error: %s", httplib::to_string(response.error()).c_str()); } SetTempVarValue("status", response ? std::to_string(response->status) : ""); SetTempVarValue("body", response ? response->body : ""); SetTempVarValue("error", response ? "" : httplib::to_string(response.error())); return true; } static constexpr std::string_view methodToString(MacroActionHttp::Method method) { switch (method) { case MacroActionHttp::Method::GET: return "GET"; case MacroActionHttp::Method::POST: return "POST"; case MacroActionHttp::Method::PUT: return "PUT"; case MacroActionHttp::Method::PATCH: return "PATCH"; case MacroActionHttp::Method::DELETE: return "DELETE"; default: break; } return "unknown"; } static std::string stringListToString(const StringList &list) { if (list.empty()) { return "[]"; } std::string result = "["; for (const auto &string : list) { result += std::string(string) + ", "; } result.pop_back(); result.pop_back(); return result + "]"; } void MacroActionHttp::LogAction() const { ablog(LOG_INFO, "sent HTTP request (%s) " "to URL \"%s\" " "with content type \"%s\" " "with body \"%s\" " "with headers \"%s\" " "with parameters \"%s\" " "with timeout \"%s\"", methodToString(_method).data(), _url.c_str(), _contentType.c_str(), _body.c_str(), _setHeaders ? stringListToString(_headers).c_str() : "-", _setParams ? stringListToString(_params).c_str() : "-", _timeout.ToString().c_str()); } bool MacroActionHttp::Save(obs_data_t *obj) const { MacroAction::Save(obj); _url.Save(obj, "url"); _contentType.Save(obj, "contentType"); _body.Save(obj, "body"); obs_data_set_bool(obj, "setHeaders", _setHeaders); _headers.Save(obj, "headers", "header"); obs_data_set_bool(obj, "setParams", _setParams); _params.Save(obj, "params", "param"); obs_data_set_int(obj, "method", static_cast(_method)); _timeout.Save(obj); return true; } bool MacroActionHttp::Load(obs_data_t *obj) { MacroAction::Load(obj); _url.Load(obj, "url"); if (obs_data_has_user_value(obj, "path")) { std::string url = _url.UnresolvedValue(); url += obs_data_get_string(obj, "path"); _url = url; } _contentType.Load(obj, "contentType"); _body.Load(obj, "body"); _setHeaders = obs_data_get_bool(obj, "setHeaders"); _headers.Load(obj, "headers", "header"); _setParams = obs_data_get_bool(obj, "setParams"); _params.Load(obj, "params", "param"); _method = static_cast(obs_data_get_int(obj, "method")); _timeout.Load(obj); return true; } std::string MacroActionHttp::GetShortDesc() const { return _url.UnresolvedValue(); } std::shared_ptr MacroActionHttp::Create(Macro *m) { return std::make_shared(m); } std::shared_ptr MacroActionHttp::Copy() const { return std::make_shared(*this); } void MacroActionHttp::ResolveVariablesToFixedValues() { _url.ResolveVariables(); _contentType.ResolveVariables(); _body.ResolveVariables(); _headers.ResolveVariables(); _params.ResolveVariables(); _timeout.ResolveVariables(); } static inline void populateMethodSelection(QComboBox *list) { const static std::map methods = { {MacroActionHttp::Method::GET, "AdvSceneSwitcher.action.http.type.get"}, {MacroActionHttp::Method::POST, "AdvSceneSwitcher.action.http.type.post"}, {MacroActionHttp::Method::PUT, "AdvSceneSwitcher.action.http.type.put"}, {MacroActionHttp::Method::PATCH, "AdvSceneSwitcher.action.http.type.patch"}, {MacroActionHttp::Method::DELETE, "AdvSceneSwitcher.action.http.type.delete"}, }; for (const auto &[value, name] : methods) { list->addItem(obs_module_text(name.c_str()), static_cast(value)); } } MacroActionHttpEdit::MacroActionHttpEdit( QWidget *parent, std::shared_ptr entryData) : QWidget(parent), _url(new VariableLineEdit(this)), _contentType(new VariableLineEdit(this)), _contentTypeLayout(new QHBoxLayout()), _methods(new QComboBox()), _body(new VariableTextEdit(this)), _bodyLayout(new QVBoxLayout()), _setHeaders(new QCheckBox( obs_module_text("AdvSceneSwitcher.action.http.setHeaders"))), _headerList(new KeyValueListEdit( this, obs_module_text("AdvSceneSwitcher.action.http.headers"), obs_module_text("AdvSceneSwitcher.action.http.addHeader.name"), obs_module_text("AdvSceneSwitcher.action.http.headers"), obs_module_text( "AdvSceneSwitcher.action.http.addHeader.value"))), _headerListLayout(new QVBoxLayout()), _setParams(new QCheckBox( obs_module_text("AdvSceneSwitcher.action.http.setParams"))), _paramList(new KeyValueListEdit( this, obs_module_text("AdvSceneSwitcher.action.http.params"), obs_module_text("AdvSceneSwitcher.action.http.addParam.name"), obs_module_text("AdvSceneSwitcher.action.http.params"), obs_module_text( "AdvSceneSwitcher.action.http.addParam.value"))), _paramListLayout(new QVBoxLayout()), _timeout(new DurationSelection(this, false)) { populateMethodSelection(_methods); SetWidgetSignalConnections(); SetWidgetLayout(); _entryData = entryData; UpdateEntryData(); _loading = false; } void MacroActionHttpEdit::UpdateEntryData() { if (!_entryData) { return; } _url->setText(_entryData->_url); _contentType->setText(_entryData->_contentType); _body->setPlainText(_entryData->_body); _setHeaders->setChecked(_entryData->_setHeaders); _headerList->SetStringList(_entryData->_headers); _setParams->setChecked(_entryData->_setParams); _paramList->SetStringList(_entryData->_params); _methods->setCurrentIndex( _methods->findData(static_cast(_entryData->_method))); _timeout->SetDuration(_entryData->_timeout); SetWidgetVisibility(); } void MacroActionHttpEdit::URLChanged() { GUARD_LOADING_AND_LOCK(); _entryData->_url = _url->text().toStdString(); emit(HeaderInfoChanged(_url->text())); } void MacroActionHttpEdit::ContentTypeChanged() { GUARD_LOADING_AND_LOCK(); _entryData->_contentType = _contentType->text().toStdString(); } void MacroActionHttpEdit::BodyChanged() { GUARD_LOADING_AND_LOCK(); _entryData->_body = _body->toPlainText().toUtf8().constData(); adjustSize(); updateGeometry(); } void MacroActionHttpEdit::MethodChanged(int idx) { GUARD_LOADING_AND_LOCK(); _entryData->_method = static_cast( _methods->itemData(idx).toInt()); SetWidgetVisibility(); } void MacroActionHttpEdit::TimeoutChanged(const Duration &dur) { GUARD_LOADING_AND_LOCK(); _entryData->_timeout = dur; } void MacroActionHttpEdit::SetHeadersChanged(int value) { GUARD_LOADING_AND_LOCK(); _entryData->_setHeaders = value; SetWidgetVisibility(); } void MacroActionHttpEdit::HeadersChanged(const StringList &headers) { GUARD_LOADING_AND_LOCK(); _entryData->_headers = headers; adjustSize(); updateGeometry(); } void MacroActionHttpEdit::SetParamsChanged(int value) { GUARD_LOADING_AND_LOCK(); _entryData->_setParams = value; SetWidgetVisibility(); } void MacroActionHttpEdit::ParamsChanged(const StringList ¶ms) { GUARD_LOADING_AND_LOCK(); _entryData->_params = params; adjustSize(); updateGeometry(); } void MacroActionHttpEdit::SetWidgetSignalConnections() { QWidget::connect(_url, SIGNAL(editingFinished()), this, SLOT(URLChanged())); QWidget::connect(_contentType, SIGNAL(editingFinished()), this, SLOT(ContentTypeChanged())); QWidget::connect(_body, SIGNAL(textChanged()), this, SLOT(BodyChanged())); QWidget::connect(_methods, SIGNAL(currentIndexChanged(int)), this, SLOT(MethodChanged(int))); QWidget::connect(_setHeaders, SIGNAL(stateChanged(int)), this, SLOT(SetHeadersChanged(int))); QWidget::connect(_headerList, SIGNAL(StringListChanged(const StringList &)), this, SLOT(HeadersChanged(const StringList &))); QWidget::connect(_setParams, SIGNAL(stateChanged(int)), this, SLOT(SetParamsChanged(int))); QWidget::connect(_paramList, SIGNAL(StringListChanged(const StringList &)), this, SLOT(ParamsChanged(const StringList &))); QWidget::connect(_timeout, SIGNAL(DurationChanged(const Duration &)), this, SLOT(TimeoutChanged(const Duration &))); } void MacroActionHttpEdit::SetWidgetLayout() { const std::unordered_map widgets = { {"{{url}}", _url}, {"{{contentType}}", _contentType}, {"{{method}}", _methods}, {"{{body}}", _body}, {"{{timeout}}", _timeout}, }; auto actionLayout = new QHBoxLayout; PlaceWidgets( obs_module_text("AdvSceneSwitcher.action.http.layout.method"), actionLayout, widgets, false); PlaceWidgets(obs_module_text( "AdvSceneSwitcher.action.http.layout.contentType"), _contentTypeLayout, widgets); _bodyLayout->addWidget(new QLabel( obs_module_text("AdvSceneSwitcher.action.http.body"))); _bodyLayout->addWidget(_body); auto timeoutLayout = new QHBoxLayout; PlaceWidgets( obs_module_text("AdvSceneSwitcher.action.http.layout.timeout"), timeoutLayout, widgets); _headerListLayout->addWidget(new QLabel( obs_module_text("AdvSceneSwitcher.action.http.headers"))); _headerListLayout->addWidget(_headerList); _paramListLayout->addWidget(new QLabel( obs_module_text("AdvSceneSwitcher.action.http.params"))); _paramListLayout->addWidget(_paramList); auto layout = new QVBoxLayout; layout->addLayout(actionLayout); layout->addWidget(_setHeaders); layout->addLayout(_headerListLayout); layout->addWidget(_setParams); layout->addLayout(_paramListLayout); layout->addLayout(_contentTypeLayout); layout->addLayout(_bodyLayout); layout->addLayout(timeoutLayout); setLayout(layout); } void MacroActionHttpEdit::SetWidgetVisibility() { SetLayoutVisible(_headerListLayout, _entryData->_setHeaders); SetLayoutVisible(_paramListLayout, _entryData->_setParams); SetLayoutVisible(_contentTypeLayout, _entryData->_method != MacroActionHttp::Method::GET); SetLayoutVisible(_bodyLayout, _entryData->_method != MacroActionHttp::Method::GET); adjustSize(); updateGeometry(); } } // namespace advss