Compare commits

..

6 Commits

Author SHA1 Message Date
WarmUpTill
70bbc7cdac Implement proper timestamp validation for Twitch messages
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-04-11 19:02:37 +02:00
WarmUpTill
d892298995 Add missing "[adv-ss]" log tag 2025-04-11 19:02:37 +02:00
WarmUpTill
0fe31432be Add "previous scene" to the "scene has (not) changed" checks 2025-04-11 18:57:58 +02:00
WarmUpTill
aaa0113ccb Ignore Xerrors 2025-04-11 18:57:25 +02:00
WarmUpTill
b908954b46 CI: Add cmake setup step to Linux build
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-04-02 13:48:23 +02:00
WarmUpTill
b0eede8a85 Add "disable" effect to macro conditions using "ignore" logic selection 2025-04-02 13:48:23 +02:00
15 changed files with 156 additions and 62 deletions

View File

@@ -210,6 +210,11 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-ccache-x86_64- ${{ runner.os }}-ccache-x86_64-
- name: Set up CMake 🏗️
uses: jwlawson/actions-setup-cmake@v1.13
with:
cmake-version: '3.24.x'
- name: Set up Homebrew 🍺 - name: Set up Homebrew 🍺
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@master

3
.gitmodules vendored
View File

@@ -28,3 +28,6 @@
[submodule "deps/libusb"] [submodule "deps/libusb"]
path = deps/libusb path = deps/libusb
url = https://github.com/libusb/libusb.git url = https://github.com/libusb/libusb.git
[submodule "deps/date"]
path = deps/date
url = https://github.com/HowardHinnant/date.git

1
deps/date vendored Submodule

Submodule deps/date added at 5bdb7e6f31

View File

@@ -550,6 +550,11 @@ static void initProc2()
#endif #endif
} }
int ignoreXerror(Display *d, XErrorEvent *e)
{
return 0;
}
void PlatformInit() void PlatformInit()
{ {
auto display = disp(); auto display = disp();
@@ -560,6 +565,7 @@ void PlatformInit()
initXss(); initXss();
initProcps(); initProcps();
initProc2(); initProc2();
XSetErrorHandler(ignoreXerror);
} }
static void cleanupHelper(QLibrary *lib) static void cleanupHelper(QLibrary *lib)
@@ -576,6 +582,7 @@ void PlatformCleanup()
cleanupHelper(libprocps); cleanupHelper(libprocps);
cleanupHelper(libproc2); cleanupHelper(libproc2);
cleanupDisplay(); cleanupDisplay();
XSetErrorHandler(NULL);
} }
} // namespace advss } // namespace advss

View File

@@ -7,8 +7,6 @@
#include "section.hpp" #include "section.hpp"
#include "switch-button.hpp" #include "switch-button.hpp"
#include <QGraphicsOpacityEffect>
namespace advss { namespace advss {
static inline void populateActionSelection(QComboBox *list) static inline void populateActionSelection(QComboBox *list)
@@ -119,17 +117,6 @@ void MacroActionEdit::SetEntryData(std::shared_ptr<MacroAction> *data)
_entryData = data; _entryData = data;
} }
void MacroActionEdit::SetDisableEffect(bool value)
{
if (value) {
auto effect = new QGraphicsOpacityEffect(this);
effect->setOpacity(0.5);
_section->setGraphicsEffect(effect);
} else {
_section->setGraphicsEffect(nullptr);
}
}
void MacroActionEdit::ActionEnableChanged(bool value) void MacroActionEdit::ActionEnableChanged(bool value)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -147,13 +134,9 @@ void MacroActionEdit::UpdateActionState()
return; return;
} }
SetEnableAppearance((*_entryData)->Enabled()); const bool enabled = (*_entryData)->Enabled();
} SetEnableAppearance(enabled);
_enable->setChecked(enabled);
void MacroActionEdit::SetEnableAppearance(bool value)
{
_enable->setChecked(value);
SetDisableEffect(!value);
} }
std::shared_ptr<MacroSegment> MacroActionEdit::Data() const std::shared_ptr<MacroSegment> MacroActionEdit::Data() const

View File

@@ -27,8 +27,6 @@ private slots:
private: private:
std::shared_ptr<MacroSegment> Data() const; std::shared_ptr<MacroSegment> Data() const;
void SetDisableEffect(bool);
void SetEnableAppearance(bool);
FilterComboBox *_actionSelection; FilterComboBox *_actionSelection;
SwitchButton *_enable; SwitchButton *_enable;

View File

@@ -11,15 +11,12 @@ bool MacroAction::Save(obs_data_t *obj) const
{ {
MacroSegment::Save(obj); MacroSegment::Save(obj);
obs_data_set_string(obj, "id", GetId().c_str()); obs_data_set_string(obj, "id", GetId().c_str());
obs_data_set_bool(obj, "enabled", _enabled);
return true; return true;
} }
bool MacroAction::Load(obs_data_t *obj) bool MacroAction::Load(obs_data_t *obj)
{ {
MacroSegment::Load(obj); MacroSegment::Load(obj);
obs_data_set_default_bool(obj, "enabled", true);
_enabled = obs_data_get_bool(obj, "enabled");
return true; return true;
} }
@@ -28,16 +25,6 @@ void MacroAction::LogAction() const
ablog(LOG_INFO, "performed action %s", GetId().c_str()); ablog(LOG_INFO, "performed action %s", GetId().c_str());
} }
void MacroAction::SetEnabled(bool value)
{
_enabled = value;
}
bool MacroAction::Enabled() const
{
return _enabled;
}
void MacroAction::ResolveVariablesToFixedValues() {} void MacroAction::ResolveVariablesToFixedValues() {}
std::string_view MacroAction::GetDefaultID() std::string_view MacroAction::GetDefaultID()

View File

@@ -19,13 +19,9 @@ public:
// Used to resolve variables before actions are added to action queues // Used to resolve variables before actions are added to action queues
virtual void ResolveVariablesToFixedValues(); virtual void ResolveVariablesToFixedValues();
void SetEnabled(bool);
bool Enabled() const;
static std::string_view GetDefaultID(); static std::string_view GetDefaultID();
private: private:
bool _enabled = true;
}; };
class EXPORT MacroRefAction : virtual public MacroAction { class EXPORT MacroRefAction : virtual public MacroAction {

View File

@@ -152,6 +152,8 @@ void MacroConditionEdit::LogicSelectionChanged(int idx)
const auto logic = static_cast<Logic::Type>( const auto logic = static_cast<Logic::Type>(
_logicSelection->itemData(idx).toInt()); _logicSelection->itemData(idx).toInt());
(*_entryData)->SetLogicType(logic); (*_entryData)->SetLogicType(logic);
SetEnableAppearance(logic != Logic::Type::NONE);
} }
bool MacroConditionEdit::IsRootNode() bool MacroConditionEdit::IsRootNode()
@@ -164,6 +166,7 @@ void MacroConditionEdit::SetLogicSelection()
const auto logic = (*_entryData)->GetLogicType(); const auto logic = (*_entryData)->GetLogicType();
_logicSelection->setCurrentIndex( _logicSelection->setCurrentIndex(
_logicSelection->findData(static_cast<int>(logic))); _logicSelection->findData(static_cast<int>(logic)));
SetEnableAppearance(logic != Logic::Type::NONE);
} }
void MacroConditionEdit::SetRootNode(bool root) void MacroConditionEdit::SetRootNode(bool root)

View File

@@ -6,6 +6,7 @@
#include <QApplication> #include <QApplication>
#include <QEvent> #include <QEvent>
#include <QGraphicsOpacityEffect>
#include <QLabel> #include <QLabel>
#include <QMouseEvent> #include <QMouseEvent>
#include <QScrollBar> #include <QScrollBar>
@@ -24,24 +25,24 @@ bool MacroSegment::Save(obs_data_t *obj) const
obs_data_set_bool(data, "collapsed", _collapsed); obs_data_set_bool(data, "collapsed", _collapsed);
obs_data_set_bool(data, "useCustomLabel", _useCustomLabel); obs_data_set_bool(data, "useCustomLabel", _useCustomLabel);
obs_data_set_string(data, "customLabel", _customLabel.c_str()); obs_data_set_string(data, "customLabel", _customLabel.c_str());
obs_data_set_bool(data, "enabled", _enabled);
obs_data_set_int(data, "version", 1);
obs_data_set_obj(obj, "segmentSettings", data); obs_data_set_obj(obj, "segmentSettings", data);
return true; return true;
} }
bool MacroSegment::Load(obs_data_t *obj) bool MacroSegment::Load(obs_data_t *obj)
{ {
OBSDataAutoRelease data = obs_data_get_obj(obj, "segmentSettings");
_collapsed = obs_data_get_bool(data, "collapsed");
_useCustomLabel = obs_data_get_bool(data, "useCustomLabel");
_customLabel = obs_data_get_string(data, "customLabel");
obs_data_set_default_bool(data, "enabled", true);
_enabled = obs_data_get_bool(data, "enabled");
// TODO: remove this fallback at some point // TODO: remove this fallback at some point
if (obs_data_has_user_value(obj, "segmentSettings")) { if (!obs_data_has_user_value(data, "version")) {
OBSDataAutoRelease data = _enabled = obs_data_get_bool(obj, "enabled");
obs_data_get_obj(obj, "segmentSettings");
_collapsed = obs_data_get_bool(data, "collapsed");
_useCustomLabel = obs_data_get_bool(data, "useCustomLabel");
_customLabel = obs_data_get_string(data, "customLabel");
} else {
_collapsed = obs_data_get_bool(obj, "collapsed");
_useCustomLabel = false;
_customLabel = obs_module_text(
"AdvSceneSwitcher.macroTab.segment.defaultCustomLabel");
} }
ClearAvailableTempvars(); ClearAvailableTempvars();
@@ -73,6 +74,16 @@ bool MacroSegment::GetHighlightAndReset()
return false; return false;
} }
void MacroSegment::SetEnabled(bool value)
{
_enabled = value;
}
bool MacroSegment::Enabled() const
{
return _enabled;
}
std::string MacroSegment::GetVariableValue() const std::string MacroSegment::GetVariableValue() const
{ {
if (_supportsVariableValue) { if (_supportsVariableValue) {
@@ -320,6 +331,22 @@ void MacroSegmentEdit::Collapsed(bool collapsed)
} }
} }
void MacroSegmentEdit::SetDisableEffect(bool value)
{
if (value) {
auto effect = new QGraphicsOpacityEffect(this);
effect->setOpacity(0.5);
_section->setGraphicsEffect(effect);
} else {
_section->setGraphicsEffect(nullptr);
}
}
void MacroSegmentEdit::SetEnableAppearance(bool value)
{
SetDisableEffect(!value);
}
void MacroSegmentEdit::SetFocusPolicyOfWidgets() void MacroSegmentEdit::SetFocusPolicyOfWidgets()
{ {
QList<QWidget *> widgets = this->findChildren<QWidget *>(); QList<QWidget *> widgets = this->findChildren<QWidget *>();

View File

@@ -39,6 +39,8 @@ public:
virtual std::string GetId() const = 0; virtual std::string GetId() const = 0;
void EnableHighlight(); void EnableHighlight();
bool GetHighlightAndReset(); bool GetHighlightAndReset();
void SetEnabled(bool);
bool Enabled() const;
virtual std::string GetVariableValue() const; virtual std::string GetVariableValue() const;
protected: protected:
@@ -75,6 +77,7 @@ private:
// UI helper // UI helper
bool _highlight = false; bool _highlight = false;
bool _collapsed = false; bool _collapsed = false;
bool _enabled = true;
// Custom header labels // Custom header labels
bool _useCustomLabel = false; bool _useCustomLabel = false;
@@ -118,6 +121,9 @@ signals:
void SceneGroupRenamed(const QString &oldName, const QString newName); void SceneGroupRenamed(const QString &oldName, const QString newName);
protected: protected:
void SetDisableEffect(bool);
void SetEnableAppearance(bool);
bool eventFilter(QObject *obj, QEvent *ev) override; bool eventFilter(QObject *obj, QEvent *ev) override;
Section *_section; Section *_section;

View File

@@ -94,11 +94,15 @@ bool MacroConditionScene::CheckCondition()
SetVariableValue(GetWeakSourceName(GetCurrentScene())); SetVariableValue(GetWeakSourceName(GetCurrentScene()));
SetTempVarValue("current", SetTempVarValue("current",
GetWeakSourceName(GetCurrentScene())); GetWeakSourceName(GetCurrentScene()));
SetTempVarValue("previous",
GetWeakSourceName(GetPreviousScene()));
return sceneChanged; return sceneChanged;
case Type::NOT_CHANGED: case Type::NOT_CHANGED:
SetVariableValue(GetWeakSourceName(GetCurrentScene())); SetVariableValue(GetWeakSourceName(GetCurrentScene()));
SetTempVarValue("current", SetTempVarValue("current",
GetWeakSourceName(GetCurrentScene())); GetWeakSourceName(GetCurrentScene()));
SetTempVarValue("previous",
GetWeakSourceName(GetPreviousScene()));
return !sceneChanged; return !sceneChanged;
case Type::CURRENT_PATTERN: { case Type::CURRENT_PATTERN: {
auto scene = getCurrentSceneHelper(_useTransitionTargetScene); auto scene = getCurrentSceneHelper(_useTransitionTargetScene);
@@ -212,8 +216,6 @@ void MacroConditionScene::SetupTempVars()
MacroCondition::SetupTempVars(); MacroCondition::SetupTempVars();
switch (_type) { switch (_type) {
case Type::CURRENT: case Type::CURRENT:
case Type::CHANGED:
case Type::NOT_CHANGED:
case Type::CURRENT_PATTERN: case Type::CURRENT_PATTERN:
AddTempvar("current", AddTempvar("current",
obs_module_text( obs_module_text(
@@ -231,6 +233,15 @@ void MacroConditionScene::SetupTempVars()
obs_module_text( obs_module_text(
"AdvSceneSwitcher.tempVar.scene.preview")); "AdvSceneSwitcher.tempVar.scene.preview"));
break; break;
case Type::CHANGED:
case Type::NOT_CHANGED:
AddTempvar("current",
obs_module_text(
"AdvSceneSwitcher.tempVar.scene.current"));
AddTempvar("previous",
obs_module_text(
"AdvSceneSwitcher.tempVar.scene.previous"));
break;
default: default:
break; break;
} }

View File

@@ -30,6 +30,31 @@ if(NOT ZLIB_FOUND)
return() return()
endif() endif()
set(DATE_LIB_DIR "${ADVSS_SOURCE_DIR}/deps/date")
if(EXISTS "${DATE_LIB_DIR}/CMakeLists.txt")
set(BUILD_TZ_LIB ON)
if(OS_WINDOWS)
if(CURL_FOUND AND TARGET CURL::libcurl)
get_target_property(CURL_INCLUDE_DIR CURL::libcurl
INTERFACE_INCLUDE_DIRECTORIES)
add_subdirectory("${DATE_LIB_DIR}" "${DATE_LIB_DIR}/build"
EXCLUDE_FROM_ALL)
target_include_directories(date-tz PRIVATE "${CURL_INCLUDE_DIR}")
set(VERIFY_TWITCH_TIMESTAMPS ON)
else()
message(WARNING "CURL not found - not verifying Twitch timestamps")
endif()
else()
add_subdirectory("${DATE_LIB_DIR}" "${DATE_LIB_DIR}/build" EXCLUDE_FROM_ALL)
target_compile_options(date-tz PUBLIC -Wno-error=conversion
-Wno-error=shadow)
set(VERIFY_TWITCH_TIMESTAMPS ON)
endif()
else()
message(WARNING "date lib not found in \"${DATE_LIB_DIR}\"!\n"
"Twitch timestamps will not be checked!")
endif()
# --- End of section --- # --- End of section ---
add_library(${PROJECT_NAME} MODULE) add_library(${PROJECT_NAME} MODULE)
@@ -76,6 +101,14 @@ set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
target_include_directories(${PROJECT_NAME} PRIVATE "${CPP_HTTPLIB_DIR}/" target_include_directories(${PROJECT_NAME} PRIVATE "${CPP_HTTPLIB_DIR}/"
"${OPENSSL_INCLUDE_DIR}") "${OPENSSL_INCLUDE_DIR}")
target_link_libraries(${PROJECT_NAME} PRIVATE ${OPENSSL_LIBRARIES} ZLIB::ZLIB) target_link_libraries(${PROJECT_NAME} PRIVATE ${OPENSSL_LIBRARIES} ZLIB::ZLIB)
if(DEFINED VERIFY_TWITCH_TIMESTAMPS)
target_compile_definitions(${PROJECT_NAME} PRIVATE VERIFY_TIMESTAMPS=1)
target_link_libraries(${PROJECT_NAME} PRIVATE date::date-tz)
if(OS_WINDOWS)
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl)
endif()
endif()
install_advss_plugin(${PROJECT_NAME}) install_advss_plugin(${PROJECT_NAME})
if(OS_WINDOWS) if(OS_WINDOWS)
# Couldn't really find a better way to install runtime dependencies for # Couldn't really find a better way to install runtime dependencies for

View File

@@ -4,6 +4,10 @@
#include <log-helper.hpp> #include <log-helper.hpp>
#ifdef VERIFY_TIMESTAMPS
#include "date/tz.h"
#endif
namespace advss { namespace advss {
using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_1;
@@ -237,15 +241,43 @@ void EventSub::OnOpen(connection_hdl)
static bool isValidTimestamp(const std::string &timestamp) static bool isValidTimestamp(const std::string &timestamp)
{ {
std::tm tm = {}; #ifdef VERIFY_TIMESTAMPS
std::istringstream ss(timestamp); // Example input: 2023-07-19T14:56:51.634234626Z
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S.%fZ"); try {
auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm)); // Discard the nanosecond part
tp += std::chrono::hours(1); // UTC static constexpr size_t dotPos = 19;
std::chrono::system_clock::time_point currentTime = std::string trimmed = timestamp.substr(0, dotPos);
std::chrono::system_clock::now(); auto tzStart = timestamp.find_first_of("Z+-", dotPos);
auto diff = currentTime - tp; trimmed = timestamp.substr(0, dotPos);
return diff <= std::chrono::minutes(10); if (tzStart != std::string::npos) {
trimmed += timestamp.substr(tzStart);
}
std::istringstream in(trimmed);
date::sys_time<std::chrono::seconds> parsedTime;
in >> date::parse("%FT%TZ", parsedTime);
if (in.fail()) {
blog(LOG_WARNING, "failed to parse timestamp %s",
timestamp.c_str());
return false;
}
auto now = date::zoned_time{date::current_zone(),
std::chrono::system_clock::now()}
.get_sys_time();
auto duration = now - parsedTime;
// Clocks might be off by a bit, so allow negative values also
return duration <= std::chrono::minutes(10) &&
duration >= std::chrono::minutes(-1);
} catch (const std::exception &e) {
blog(LOG_WARNING, "%s: %s", __func__, e.what());
return false;
}
#else
// Just assume timestamps are always valid
return true;
#endif
} }
bool EventSub::IsValidMessageID(const std::string &id) bool EventSub::IsValidMessageID(const std::string &id)
@@ -288,7 +320,8 @@ void EventSub::OnMessage(connection_hdl, EventSubWSClient::message_ptr message)
obs_data_get_string(metadata, "message_timestamp"); obs_data_get_string(metadata, "message_timestamp");
if (!isValidTimestamp(timestamp)) { if (!isValidTimestamp(timestamp)) {
blog(LOG_WARNING, blog(LOG_WARNING,
"Discarding Twitch EventSub with invalid timestamp"); "Discarding Twitch EventSub with invalid timestamp %s",
timestamp.c_str());
return; return;
} }
std::string id = obs_data_get_string(metadata, "message_id"); std::string id = obs_data_get_string(metadata, "message_id");

View File

@@ -1,6 +1,7 @@
#include "points-reward-selection.hpp" #include "points-reward-selection.hpp"
#include "twitch-helpers.hpp" #include "twitch-helpers.hpp"
#include <log-helper.hpp>
#include <obs-module-helper.hpp> #include <obs-module-helper.hpp>
#include <ui-helpers.hpp> #include <ui-helpers.hpp>