mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-09-06 16:46:11 -05:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20488afdd1 | ||
|
|
d9d387ad47 | ||
|
|
0736d673e1 | ||
|
|
34151e4bc6 | ||
|
|
1346c19bec | ||
|
|
be6bc48231 | ||
|
|
79a8ad57af | ||
|
|
daeb9275a3 | ||
|
|
73b542a4db | ||
|
|
e3471066e9 | ||
|
|
ec41c06b4d | ||
|
|
201e45d058 | ||
|
|
b17aa30432 |
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@@ -2,7 +2,7 @@
|
|||||||
# https://github.com/obsproject/.github/tree/master/.github/ISSUE_TEMPLATE
|
# https://github.com/obsproject/.github/tree/master/.github/ISSUE_TEMPLATE
|
||||||
|
|
||||||
name: Feature request
|
name: Feature request
|
||||||
description: Suggest an idea for this projectReport a bug or crash
|
description: Suggest an idea for this project
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
id: md_welcome
|
id: md_welcome
|
||||||
|
|||||||
@@ -103,8 +103,6 @@ target_sources(
|
|||||||
lib/macro/macro-action-macro.hpp
|
lib/macro/macro-action-macro.hpp
|
||||||
lib/macro/macro-action-queue.cpp
|
lib/macro/macro-action-queue.cpp
|
||||||
lib/macro/macro-action-queue.hpp
|
lib/macro/macro-action-queue.hpp
|
||||||
lib/macro/macro-action-script.cpp
|
|
||||||
lib/macro/macro-action-script.hpp
|
|
||||||
lib/macro/macro-action-variable.cpp
|
lib/macro/macro-action-variable.cpp
|
||||||
lib/macro/macro-action-variable.hpp
|
lib/macro/macro-action-variable.hpp
|
||||||
lib/macro/macro-action.cpp
|
lib/macro/macro-action.cpp
|
||||||
@@ -117,8 +115,6 @@ target_sources(
|
|||||||
lib/macro/macro-condition-macro.hpp
|
lib/macro/macro-condition-macro.hpp
|
||||||
lib/macro/macro-condition-queue.cpp
|
lib/macro/macro-condition-queue.cpp
|
||||||
lib/macro/macro-condition-queue.hpp
|
lib/macro/macro-condition-queue.hpp
|
||||||
lib/macro/macro-condition-script.cpp
|
|
||||||
lib/macro/macro-condition-script.hpp
|
|
||||||
lib/macro/macro-condition-tempvar.cpp
|
lib/macro/macro-condition-tempvar.cpp
|
||||||
lib/macro/macro-condition-tempvar.hpp
|
lib/macro/macro-condition-tempvar.hpp
|
||||||
lib/macro/macro-condition-variable.cpp
|
lib/macro/macro-condition-variable.cpp
|
||||||
@@ -139,14 +135,10 @@ target_sources(
|
|||||||
lib/macro/macro-ref.hpp
|
lib/macro/macro-ref.hpp
|
||||||
lib/macro/macro-run-button.cpp
|
lib/macro/macro-run-button.cpp
|
||||||
lib/macro/macro-run-button.hpp
|
lib/macro/macro-run-button.hpp
|
||||||
lib/macro/macro-script-handler.cpp
|
|
||||||
lib/macro/macro-script-handler.hpp
|
|
||||||
lib/macro/macro-segment-copy-paste.cpp
|
lib/macro/macro-segment-copy-paste.cpp
|
||||||
lib/macro/macro-segment-copy-paste.hpp
|
lib/macro/macro-segment-copy-paste.hpp
|
||||||
lib/macro/macro-segment-list.cpp
|
lib/macro/macro-segment-list.cpp
|
||||||
lib/macro/macro-segment-list.hpp
|
lib/macro/macro-segment-list.hpp
|
||||||
lib/macro/macro-segment-script.cpp
|
|
||||||
lib/macro/macro-segment-script.hpp
|
|
||||||
lib/macro/macro-segment-selection.cpp
|
lib/macro/macro-segment-selection.cpp
|
||||||
lib/macro/macro-segment-selection.hpp
|
lib/macro/macro-segment-selection.hpp
|
||||||
lib/macro/macro-segment.cpp
|
lib/macro/macro-segment.cpp
|
||||||
@@ -223,9 +215,6 @@ target_sources(
|
|||||||
lib/utils/plugin-state-helpers.hpp
|
lib/utils/plugin-state-helpers.hpp
|
||||||
lib/utils/priority-helper.cpp
|
lib/utils/priority-helper.cpp
|
||||||
lib/utils/priority-helper.hpp
|
lib/utils/priority-helper.hpp
|
||||||
lib/utils/properties-view.cpp
|
|
||||||
lib/utils/properties-view.hpp
|
|
||||||
lib/utils/properties-view.moc.hpp
|
|
||||||
lib/utils/regex-config.cpp
|
lib/utils/regex-config.cpp
|
||||||
lib/utils/regex-config.hpp
|
lib/utils/regex-config.hpp
|
||||||
lib/utils/resizing-text-edit.cpp
|
lib/utils/resizing-text-edit.cpp
|
||||||
|
|||||||
@@ -730,6 +730,8 @@ AdvSceneSwitcher.condition.twitch.type.polling.channel.live="Stream is currently
|
|||||||
AdvSceneSwitcher.condition.twitch.type.polling.channel.title="Current title matches"
|
AdvSceneSwitcher.condition.twitch.type.polling.channel.title="Current title matches"
|
||||||
AdvSceneSwitcher.condition.twitch.type.polling.channel.category="Current category is"
|
AdvSceneSwitcher.condition.twitch.type.polling.channel.category="Current category is"
|
||||||
AdvSceneSwitcher.condition.twitch.type.chat.message="Chat message received"
|
AdvSceneSwitcher.condition.twitch.type.chat.message="Chat message received"
|
||||||
|
AdvSceneSwitcher.condition.twitch.type.chat.messageRemoved="Single chat message removed"
|
||||||
|
AdvSceneSwitcher.condition.twitch.type.chat.cleared="All messages of user or channel cleared"
|
||||||
AdvSceneSwitcher.condition.twitch.type.chat.properties="Chat message properties:"
|
AdvSceneSwitcher.condition.twitch.type.chat.properties="Chat message properties:"
|
||||||
AdvSceneSwitcher.condition.twitch.type.chat.properties.select="Select chat message property:"
|
AdvSceneSwitcher.condition.twitch.type.chat.properties.select="Select chat message property:"
|
||||||
AdvSceneSwitcher.condition.twitch.type.chat.properties.firstMessage="Is first message"
|
AdvSceneSwitcher.condition.twitch.type.chat.properties.firstMessage="Is first message"
|
||||||
@@ -801,6 +803,7 @@ AdvSceneSwitcher.condition.screenshot.entry="A screenshot was taken"
|
|||||||
AdvSceneSwitcher.condition.mqtt="MQTT"
|
AdvSceneSwitcher.condition.mqtt="MQTT"
|
||||||
AdvSceneSwitcher.condition.mqtt.layout.match="Message was received from{{connection}} which matches{{regex}}:"
|
AdvSceneSwitcher.condition.mqtt.layout.match="Message was received from{{connection}} which matches{{regex}}:"
|
||||||
AdvSceneSwitcher.condition.mqtt.layout.listen="Set message selection to incoming message:{{listenButton}}"
|
AdvSceneSwitcher.condition.mqtt.layout.listen="Set message selection to incoming message:{{listenButton}}"
|
||||||
|
AdvSceneSwitcher.condition.script="Script"
|
||||||
|
|
||||||
# Macro Actions
|
# Macro Actions
|
||||||
AdvSceneSwitcher.action.unknown="Unknown action"
|
AdvSceneSwitcher.action.unknown="Unknown action"
|
||||||
@@ -1094,7 +1097,7 @@ AdvSceneSwitcher.action.http.type.post="POST"
|
|||||||
AdvSceneSwitcher.action.http.type.put="PUT"
|
AdvSceneSwitcher.action.http.type.put="PUT"
|
||||||
AdvSceneSwitcher.action.http.type.patch="PATCH"
|
AdvSceneSwitcher.action.http.type.patch="PATCH"
|
||||||
AdvSceneSwitcher.action.http.type.delete="DELETE"
|
AdvSceneSwitcher.action.http.type.delete="DELETE"
|
||||||
AdvSceneSwitcher.action.http.layout.method="Send{{method}}to URL{{url}}at path{{path}}"
|
AdvSceneSwitcher.action.http.layout.method="Send{{method}}to URL{{url}}"
|
||||||
AdvSceneSwitcher.action.http.layout.contentType="Content type:{{contentType}}"
|
AdvSceneSwitcher.action.http.layout.contentType="Content type:{{contentType}}"
|
||||||
AdvSceneSwitcher.action.http.layout.timeout="Timeout:{{timeout}}seconds"
|
AdvSceneSwitcher.action.http.layout.timeout="Timeout:{{timeout}}seconds"
|
||||||
AdvSceneSwitcher.action.http.entry.line1="Send{{method}}to{{url}}"
|
AdvSceneSwitcher.action.http.entry.line1="Send{{method}}to{{url}}"
|
||||||
@@ -1250,6 +1253,7 @@ AdvSceneSwitcher.action.obsSetting.action.setOutputCanvasX="Set output resolutio
|
|||||||
AdvSceneSwitcher.action.obsSetting.action.setOutputCanvasY="Set output resolution Y value"
|
AdvSceneSwitcher.action.obsSetting.action.setOutputCanvasY="Set output resolution Y value"
|
||||||
AdvSceneSwitcher.action.obsSetting.getCurrentValue="Get current value"
|
AdvSceneSwitcher.action.obsSetting.getCurrentValue="Get current value"
|
||||||
AdvSceneSwitcher.action.obsSettings.layout="{{actions}}{{fpsType}}{{fpsIntValue}}{{fpsStringValue}}{{canvasSizeValue}}{{getCurrentValue}}"
|
AdvSceneSwitcher.action.obsSettings.layout="{{actions}}{{fpsType}}{{fpsIntValue}}{{fpsStringValue}}{{canvasSizeValue}}{{getCurrentValue}}"
|
||||||
|
AdvSceneSwitcher.action.script="Script"
|
||||||
|
|
||||||
# Hotkey
|
# Hotkey
|
||||||
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
|
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
|
||||||
@@ -1514,6 +1518,17 @@ AdvSceneSwitcher.twitch.selection.points.reward.tooltip.noPermission="Can't sele
|
|||||||
AdvSceneSwitcher.twitch.selection.points.reward.tooltip.noChannel="Can't select points reward without entering a channel first!"
|
AdvSceneSwitcher.twitch.selection.points.reward.tooltip.noChannel="Can't select points reward without entering a channel first!"
|
||||||
AdvSceneSwitcher.twitch.selection.points.reward.tooltip.error="Can't select points reward because Twitch responded with an error! Check OBS logs for more details."
|
AdvSceneSwitcher.twitch.selection.points.reward.tooltip.error="Can't select points reward because Twitch responded with an error! Check OBS logs for more details."
|
||||||
|
|
||||||
|
AdvSceneSwitcher.script.type.inline="Inline"
|
||||||
|
AdvSceneSwitcher.script.type.file="File"
|
||||||
|
AdvSceneSwitcher.script.type.layout="Script type:{{scriptType}}"
|
||||||
|
AdvSceneSwitcher.script.language.python="Python"
|
||||||
|
AdvSceneSwitcher.script.language.lua="LUA"
|
||||||
|
AdvSceneSwitcher.script.language.select="--select language--"
|
||||||
|
AdvSceneSwitcher.script.language.layout="Script language:{{language}}"
|
||||||
|
AdvSceneSwitcher.script.file.open="Open"
|
||||||
|
AdvSceneSwitcher.script.file.open.failed="Could not open script file!"
|
||||||
|
AdvSceneSwitcher.script.file.layout="Script file:{{path}}{{open}}"
|
||||||
|
|
||||||
AdvSceneSwitcher.tempVar.select="--select value--"
|
AdvSceneSwitcher.tempVar.select="--select value--"
|
||||||
AdvSceneSwitcher.tempVar.selectionInfo.lastValues="Last values:"
|
AdvSceneSwitcher.tempVar.selectionInfo.lastValues="Last values:"
|
||||||
|
|
||||||
@@ -1907,6 +1922,24 @@ AdvSceneSwitcher.tempVar.twitch.is_turbo.chatReceive.description="'true' if the
|
|||||||
AdvSceneSwitcher.tempVar.twitch.is_vip.chatReceive="Is chatter a VIP"
|
AdvSceneSwitcher.tempVar.twitch.is_vip.chatReceive="Is chatter a VIP"
|
||||||
AdvSceneSwitcher.tempVar.twitch.is_vip.chatReceive.description="'true' if the chatter is a VIP in the channel, 'false' otherwise."
|
AdvSceneSwitcher.tempVar.twitch.is_vip.chatReceive.description="'true' if the chatter is a VIP in the channel, 'false' otherwise."
|
||||||
|
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.login.chatRemove="User login"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.login.chatRemove.description="The name of the user who sent the message."
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.message.chatRemove="Chat message"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.message_id.chatRemove.description="The chat message that was removed."
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.message_id.chatRemove="Chat message ID"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.message_id.chatRemove.description="The ID of the message in UUID format."
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.timestamp.chatRemove="Chat message timestamp"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.timestamp.chatRemove.description="The UNIX timestamp."
|
||||||
|
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.login.chatClear="User login"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.login.chatClear.description="The login name of the user whose messages were removed from the chat room because they were banned or put in a timeout."
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.user_id.chatClear="User ID"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.user_id.chatClear.description="Optional.\nThe User ID of the user that was banned or put in a timeout.\nEmpty in case the complete chat was cleared."
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.ban_duration.chatClear="Ban duration"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.ban_duration.chatClear.description="Set to zero if the complete chat was cleared or a user was banned indefinitely.\nIs set to non-zero value, if a user was put in a timeout.\nWill contain the duration of the timeout in seconds."
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.timestamp.chatClear="Timestamp"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.timestamp.chatClear.description="The UNIX timestamp."
|
||||||
|
|
||||||
AdvSceneSwitcher.tempVar.twitch.user_login.chatJoin="User login"
|
AdvSceneSwitcher.tempVar.twitch.user_login.chatJoin="User login"
|
||||||
AdvSceneSwitcher.tempVar.twitch.user_login.chatJoin.description="The user login of the person who joined the chat room."
|
AdvSceneSwitcher.tempVar.twitch.user_login.chatJoin.description="The user login of the person who joined the chat room."
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace advss {
|
|||||||
|
|
||||||
class MacroActionEdit;
|
class MacroActionEdit;
|
||||||
class MacroConditionEdit;
|
class MacroConditionEdit;
|
||||||
|
class MacroSegment;
|
||||||
class Duration;
|
class Duration;
|
||||||
class SequenceWidget;
|
class SequenceWidget;
|
||||||
struct SceneGroup;
|
struct SceneGroup;
|
||||||
@@ -192,7 +193,7 @@ signals:
|
|||||||
void MacroRemoved(const QString &name);
|
void MacroRemoved(const QString &name);
|
||||||
void MacroRenamed(const QString &oldName, const QString &newName);
|
void MacroRenamed(const QString &oldName, const QString &newName);
|
||||||
void MacroSegmentOrderChanged();
|
void MacroSegmentOrderChanged();
|
||||||
void SegmentTempVarsChanged();
|
void SegmentTempVarsChanged(MacroSegment *);
|
||||||
void HighlightMacrosChanged(bool value);
|
void HighlightMacrosChanged(bool value);
|
||||||
|
|
||||||
void ConnectionAdded(const QString &);
|
void ConnectionAdded(const QString &);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public:
|
|||||||
MacroActionFactory() = delete;
|
MacroActionFactory() = delete;
|
||||||
|
|
||||||
EXPORT static bool Register(const std::string &id, MacroActionInfo);
|
EXPORT static bool Register(const std::string &id, MacroActionInfo);
|
||||||
static bool Deregister(const std::string &id);
|
EXPORT static bool Deregister(const std::string &id);
|
||||||
static std::shared_ptr<MacroAction> Create(const std::string &id,
|
static std::shared_ptr<MacroAction> Create(const std::string &id,
|
||||||
Macro *m);
|
Macro *m);
|
||||||
static QWidget *CreateWidget(const std::string &id, QWidget *parent,
|
static QWidget *CreateWidget(const std::string &id, QWidget *parent,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class MacroConditionFactory {
|
|||||||
public:
|
public:
|
||||||
MacroConditionFactory() = delete;
|
MacroConditionFactory() = delete;
|
||||||
EXPORT static bool Register(const std::string &, MacroConditionInfo);
|
EXPORT static bool Register(const std::string &, MacroConditionInfo);
|
||||||
static bool Deregister(const std::string &);
|
EXPORT static bool Deregister(const std::string &);
|
||||||
static std::shared_ptr<MacroCondition> Create(const std::string &,
|
static std::shared_ptr<MacroCondition> Create(const std::string &,
|
||||||
Macro *m);
|
Macro *m);
|
||||||
static QWidget *CreateWidget(const std::string &id, QWidget *parent,
|
static QWidget *CreateWidget(const std::string &id, QWidget *parent,
|
||||||
|
|||||||
@@ -56,9 +56,7 @@ MacroSegmentList::~MacroSegmentList()
|
|||||||
_autoScrollThread.join();
|
_autoScrollThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &[_, widgets] : _widgetCache) {
|
ClearWidgetCache();
|
||||||
clearWidgetVector(widgets);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool posIsInScrollbar(const QScrollBar *scrollbar, const QPoint &pos)
|
static bool posIsInScrollbar(const QScrollBar *scrollbar, const QPoint &pos)
|
||||||
@@ -146,7 +144,6 @@ void MacroSegmentList::SetCachingEnabled(bool enable)
|
|||||||
void MacroSegmentList::CacheCurrentWidgetsFor(const Macro *macro)
|
void MacroSegmentList::CacheCurrentWidgetsFor(const Macro *macro)
|
||||||
{
|
{
|
||||||
if (!_useCache) {
|
if (!_useCache) {
|
||||||
_widgetCache.clear();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +165,6 @@ void MacroSegmentList::CacheCurrentWidgetsFor(const Macro *macro)
|
|||||||
bool MacroSegmentList::PopulateWidgetsFromCache(const Macro *macro)
|
bool MacroSegmentList::PopulateWidgetsFromCache(const Macro *macro)
|
||||||
{
|
{
|
||||||
if (!_useCache) {
|
if (!_useCache) {
|
||||||
_widgetCache.clear();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,6 +336,13 @@ void MacroSegmentList::HideLastDropLine()
|
|||||||
_dropLineIdx = -1;
|
_dropLineIdx = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroSegmentList::ClearWidgetCache()
|
||||||
|
{
|
||||||
|
for (const auto &[_, widgets] : _widgetCache) {
|
||||||
|
clearWidgetVector(widgets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static bool isInUpperHalfOf(const QPoint &pos, const QRect &rect)
|
static bool isInUpperHalfOf(const QPoint &pos, const QRect &rect)
|
||||||
{
|
{
|
||||||
return QRect(rect.topLeft(),
|
return QRect(rect.topLeft(),
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ private:
|
|||||||
bool IsInListArea(const QPoint &) const;
|
bool IsInListArea(const QPoint &) const;
|
||||||
QRect GetContentItemRectWithPadding(int idx) const;
|
QRect GetContentItemRectWithPadding(int idx) const;
|
||||||
void HideLastDropLine();
|
void HideLastDropLine();
|
||||||
|
void ClearWidgetCache();
|
||||||
|
|
||||||
int _dragPosition = -1;
|
int _dragPosition = -1;
|
||||||
int _dropLineIdx = -1;
|
int _dropLineIdx = -1;
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ void MacroSegment::SetupTempVars()
|
|||||||
void MacroSegment::ClearAvailableTempvars()
|
void MacroSegment::ClearAvailableTempvars()
|
||||||
{
|
{
|
||||||
_tempVariables.clear();
|
_tempVariables.clear();
|
||||||
NotifyUIAboutTempVarChange();
|
NotifyUIAboutTempVarChange(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::shared_ptr<MacroSegment>
|
static std::shared_ptr<MacroSegment>
|
||||||
@@ -149,7 +149,7 @@ void MacroSegment::AddTempvar(const std::string &id, const std::string &name,
|
|||||||
|
|
||||||
TempVariable var(id, name, description, sharedSegment);
|
TempVariable var(id, name, description, sharedSegment);
|
||||||
_tempVariables.emplace_back(std::move(var));
|
_tempVariables.emplace_back(std::move(var));
|
||||||
NotifyUIAboutTempVarChange();
|
NotifyUIAboutTempVarChange(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroSegment::SetTempVarValue(const std::string &id,
|
void MacroSegment::SetTempVarValue(const std::string &id,
|
||||||
|
|||||||
@@ -76,12 +76,16 @@ void MacroSelection::ShowAllMacros()
|
|||||||
|
|
||||||
void MacroSelection::MacroRemove(const QString &name)
|
void MacroSelection::MacroRemove(const QString &name)
|
||||||
{
|
{
|
||||||
|
const bool currentSelectionWasRemoved = name == currentText();
|
||||||
|
|
||||||
int idx = findText(name);
|
int idx = findText(name);
|
||||||
if (idx == -1) {
|
if (idx == -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
removeItem(idx);
|
removeItem(idx);
|
||||||
setCurrentIndex(-1);
|
if (currentSelectionWasRemoved) {
|
||||||
|
setCurrentIndex(-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroSelection::MacroRename(const QString &oldName, const QString &newName)
|
void MacroSelection::MacroRename(const QString &oldName, const QString &newName)
|
||||||
|
|||||||
@@ -207,13 +207,11 @@ void AdvSceneSwitcher::RemoveMacro(std::shared_ptr<Macro> ¯o)
|
|||||||
clearWidgetCache(macro.get());
|
clearWidgetCache(macro.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently shown macro will always be part of the macros to be
|
// Don't cache widgets for about to be deleted macros
|
||||||
// removed, so, we clear the widgets as they should not be cached
|
MacroSegmentList::SetCachingEnabled(false);
|
||||||
ui->conditionsList->Clear();
|
|
||||||
ui->actionsList->Clear();
|
|
||||||
ui->elseActionsList->Clear();
|
|
||||||
|
|
||||||
ui->macros->Remove(macro);
|
ui->macros->Remove(macro);
|
||||||
|
MacroSegmentList::SetCachingEnabled(!switcher->disableMacroWidgetCache);
|
||||||
|
|
||||||
emit MacroRemoved(name);
|
emit MacroRemoved(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1301,10 +1301,10 @@ QSize MacroTreeDelegate::sizeHint(const QStyleOptionViewItem &option,
|
|||||||
QWidget *item = tree->indexWidget(index);
|
QWidget *item = tree->indexWidget(index);
|
||||||
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return (QSize(0, 0));
|
return QStyledItemDelegate::sizeHint(option, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (QSize(option.widget->minimumWidth(), item->height()));
|
return QSize(item->sizeHint());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
class SliderIgnoreScroll : public QSlider {
|
class ADVSS_EXPORT SliderIgnoreScroll : public QSlider {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -9,15 +9,14 @@
|
|||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
class DurationSelection : public QWidget {
|
class ADVSS_EXPORT DurationSelection : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
EXPORT DurationSelection(QWidget *parent = nullptr,
|
DurationSelection(QWidget *parent = nullptr,
|
||||||
bool showUnitSelection = true,
|
bool showUnitSelection = true, double minValue = 0.0);
|
||||||
double minValue = 0.0);
|
void SetDuration(const Duration &);
|
||||||
EXPORT void SetDuration(const Duration &);
|
|
||||||
Duration GetDuration() const { return _current; }
|
Duration GetDuration() const { return _current; }
|
||||||
EXPORT QDoubleSpinBox *SpinBox() { return _duration->SpinBox(); }
|
QDoubleSpinBox *SpinBox() { return _duration->SpinBox(); }
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void _DurationChanged(const NumberVariable<double> &value);
|
void _DurationChanged(const NumberVariable<double> &value);
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ EXPORT void StopPlugin();
|
|||||||
EXPORT void StartPlugin();
|
EXPORT void StartPlugin();
|
||||||
EXPORT bool PluginIsRunning();
|
EXPORT bool PluginIsRunning();
|
||||||
EXPORT int GetIntervalValue();
|
EXPORT int GetIntervalValue();
|
||||||
void AddStartStep(std::function<void()>);
|
EXPORT void AddStartStep(std::function<void()>);
|
||||||
void AddStopStep(std::function<void()>);
|
EXPORT void AddStopStep(std::function<void()>);
|
||||||
void RunStartSteps();
|
void RunStartSteps();
|
||||||
void RunStopSteps();
|
void RunStopSteps();
|
||||||
void RunIntervalResetSteps();
|
void RunIntervalResetSteps();
|
||||||
|
|||||||
@@ -5,15 +5,15 @@
|
|||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
class ResizingPlainTextEdit : public QPlainTextEdit {
|
class ADVSS_EXPORT ResizingPlainTextEdit : public QPlainTextEdit {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ResizingPlainTextEdit(QWidget *parent, const int scrollAt = 10,
|
ResizingPlainTextEdit(QWidget *parent, const int scrollAt = 10,
|
||||||
const int minLines = 3,
|
const int minLines = 3,
|
||||||
const int paddingLines = 2);
|
const int paddingLines = 2);
|
||||||
virtual ~ResizingPlainTextEdit(){};
|
virtual ~ResizingPlainTextEdit(){};
|
||||||
EXPORT int maxLength();
|
int maxLength();
|
||||||
EXPORT void setMaxLength(int maxLength);
|
void setMaxLength(int maxLength);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||||
|
|||||||
@@ -274,8 +274,9 @@ TempVariableSelection::TempVariableSelection(QWidget *parent)
|
|||||||
SLOT(HighlightChanged(int)));
|
SLOT(HighlightChanged(int)));
|
||||||
QWidget::connect(window(), SIGNAL(MacroSegmentOrderChanged()), this,
|
QWidget::connect(window(), SIGNAL(MacroSegmentOrderChanged()), this,
|
||||||
SLOT(MacroSegmentsChanged()));
|
SLOT(MacroSegmentsChanged()));
|
||||||
QWidget::connect(window(), SIGNAL(SegmentTempVarsChanged()), this,
|
QWidget::connect(window(),
|
||||||
SLOT(SegmentTempVarsChanged()));
|
SIGNAL(SegmentTempVarsChanged(MacroSegment *)), this,
|
||||||
|
SLOT(SegmentTempVarsChanged(MacroSegment *)));
|
||||||
|
|
||||||
auto layout = new QHBoxLayout();
|
auto layout = new QHBoxLayout();
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
@@ -314,9 +315,15 @@ void TempVariableSelection::MacroSegmentsChanged()
|
|||||||
SetVariable(currentSelection);
|
SetVariable(currentSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TempVariableSelection::SegmentTempVarsChanged()
|
void TempVariableSelection::SegmentTempVarsChanged(MacroSegment *segment)
|
||||||
{
|
{
|
||||||
MacroSegmentsChanged();
|
const auto currentSegment = GetSegment();
|
||||||
|
const auto currentMacro = currentSegment ? currentSegment->GetMacro()
|
||||||
|
: nullptr;
|
||||||
|
const auto changeMacro = segment ? segment->GetMacro() : nullptr;
|
||||||
|
if (currentMacro == changeMacro) {
|
||||||
|
MacroSegmentsChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TempVariableSelection::HighlightChanged(int idx)
|
void TempVariableSelection::HighlightChanged(int idx)
|
||||||
@@ -427,13 +434,11 @@ QString TempVariableSelection::SetupInfoLabel()
|
|||||||
MacroSegment *TempVariableSelection::GetSegment() const
|
MacroSegment *TempVariableSelection::GetSegment() const
|
||||||
{
|
{
|
||||||
const QWidget *widget = this;
|
const QWidget *widget = this;
|
||||||
{
|
while (widget) {
|
||||||
while (widget) {
|
if (qobject_cast<const MacroSegmentEdit *>(widget)) {
|
||||||
if (qobject_cast<const MacroSegmentEdit *>(widget)) {
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
widget = widget->parentWidget();
|
|
||||||
}
|
}
|
||||||
|
widget = widget->parentWidget();
|
||||||
}
|
}
|
||||||
if (!widget) {
|
if (!widget) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -442,17 +447,18 @@ MacroSegment *TempVariableSelection::GetSegment() const
|
|||||||
return segmentWidget->Data().get();
|
return segmentWidget->Data().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifyUIAboutTempVarChange()
|
void NotifyUIAboutTempVarChange(MacroSegment *segment)
|
||||||
{
|
{
|
||||||
obs_queue_task(
|
obs_queue_task(
|
||||||
OBS_TASK_UI,
|
OBS_TASK_UI,
|
||||||
[](void *) {
|
[](void *segment) {
|
||||||
if (!SettingsWindowIsOpened()) {
|
if (!SettingsWindowIsOpened()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AdvSceneSwitcher::window->SegmentTempVarsChanged();
|
AdvSceneSwitcher::window->SegmentTempVarsChanged(
|
||||||
|
(MacroSegment *)segment);
|
||||||
},
|
},
|
||||||
nullptr, false);
|
segment, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public:
|
|||||||
private slots:
|
private slots:
|
||||||
void SelectionIdxChanged(int);
|
void SelectionIdxChanged(int);
|
||||||
void MacroSegmentsChanged();
|
void MacroSegmentsChanged();
|
||||||
void SegmentTempVarsChanged();
|
void SegmentTempVarsChanged(MacroSegment *);
|
||||||
void HighlightChanged(int);
|
void HighlightChanged(int);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -104,6 +104,6 @@ private:
|
|||||||
AutoUpdateTooltipLabel *_info;
|
AutoUpdateTooltipLabel *_info;
|
||||||
};
|
};
|
||||||
|
|
||||||
void NotifyUIAboutTempVarChange();
|
void NotifyUIAboutTempVarChange(MacroSegment *);
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
class VariableTextEdit : public ResizingPlainTextEdit {
|
class ADVSS_EXPORT VariableTextEdit : public ResizingPlainTextEdit {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
EXPORT VariableTextEdit(QWidget *parent, const int scrollAt = 10,
|
VariableTextEdit(QWidget *parent, const int scrollAt = 10,
|
||||||
const int minLines = 3,
|
const int minLines = 3, const int paddingLines = 2);
|
||||||
const int paddingLines = 2);
|
virtual ~VariableTextEdit(){};
|
||||||
EXPORT void setPlainText(const QString &);
|
void setPlainText(const QString &);
|
||||||
EXPORT void setPlainText(const StringVariable &);
|
void setPlainText(const StringVariable &);
|
||||||
EXPORT void setToolTip(const QString &string);
|
void setToolTip(const QString &string);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ add_plugin(http)
|
|||||||
add_plugin(midi)
|
add_plugin(midi)
|
||||||
add_plugin(mqtt)
|
add_plugin(mqtt)
|
||||||
add_plugin(openvr)
|
add_plugin(openvr)
|
||||||
|
add_plugin(scripting)
|
||||||
add_plugin(stream-deck)
|
add_plugin(stream-deck)
|
||||||
add_plugin(twitch)
|
add_plugin(twitch)
|
||||||
add_plugin(usb)
|
add_plugin(usb)
|
||||||
|
|||||||
@@ -65,20 +65,47 @@ void MacroActionHttp::SetupTempVars()
|
|||||||
"AdvSceneSwitcher.tempVar.http.error.description"));
|
"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()
|
bool MacroActionHttp::PerformAction()
|
||||||
{
|
{
|
||||||
httplib::Client cli(_url);
|
const auto [host, path] = getURLInfo(_url);
|
||||||
|
|
||||||
|
httplib::Client cli(host);
|
||||||
setTimeout(cli, _timeout);
|
setTimeout(cli, _timeout);
|
||||||
const auto params = _setParams ? getParams(_params) : httplib::Params();
|
const auto params = _setParams ? getParams(_params) : httplib::Params();
|
||||||
const auto headers = _setHeaders ? getHeaders(_headers)
|
const auto headers = _setHeaders ? getHeaders(_headers)
|
||||||
: httplib::Headers();
|
: httplib::Headers();
|
||||||
|
|
||||||
// This copy is necessary for some reason for MacOS but not Linux or
|
|
||||||
// Windows.
|
|
||||||
// Without it all requests fail with "Invalid argument" or
|
|
||||||
// "Failed to write connection" when sending out the data.
|
|
||||||
std::string path = _path;
|
|
||||||
|
|
||||||
httplib::Result response;
|
httplib::Result response;
|
||||||
switch (_method) {
|
switch (_method) {
|
||||||
case MacroActionHttp::Method::GET:
|
case MacroActionHttp::Method::GET:
|
||||||
@@ -167,13 +194,12 @@ void MacroActionHttp::LogAction() const
|
|||||||
ablog(LOG_INFO,
|
ablog(LOG_INFO,
|
||||||
"sent HTTP request (%s) "
|
"sent HTTP request (%s) "
|
||||||
"to URL \"%s\" "
|
"to URL \"%s\" "
|
||||||
"to path \"%s\" "
|
|
||||||
"with content type \"%s\" "
|
"with content type \"%s\" "
|
||||||
"with body \"%s\" "
|
"with body \"%s\" "
|
||||||
"with headers \"%s\" "
|
"with headers \"%s\" "
|
||||||
"with parameters \"%s\" "
|
"with parameters \"%s\" "
|
||||||
"with timeout \"%s\"",
|
"with timeout \"%s\"",
|
||||||
methodToString(_method).data(), _url.c_str(), _path.c_str(),
|
methodToString(_method).data(), _url.c_str(),
|
||||||
_contentType.c_str(), _body.c_str(),
|
_contentType.c_str(), _body.c_str(),
|
||||||
_setHeaders ? stringListToString(_headers).c_str() : "-",
|
_setHeaders ? stringListToString(_headers).c_str() : "-",
|
||||||
_setParams ? stringListToString(_params).c_str() : "-",
|
_setParams ? stringListToString(_params).c_str() : "-",
|
||||||
@@ -184,7 +210,6 @@ bool MacroActionHttp::Save(obs_data_t *obj) const
|
|||||||
{
|
{
|
||||||
MacroAction::Save(obj);
|
MacroAction::Save(obj);
|
||||||
_url.Save(obj, "url");
|
_url.Save(obj, "url");
|
||||||
_path.Save(obj, "path");
|
|
||||||
_contentType.Save(obj, "contentType");
|
_contentType.Save(obj, "contentType");
|
||||||
_body.Save(obj, "body");
|
_body.Save(obj, "body");
|
||||||
obs_data_set_bool(obj, "setHeaders", _setHeaders);
|
obs_data_set_bool(obj, "setHeaders", _setHeaders);
|
||||||
@@ -200,7 +225,11 @@ bool MacroActionHttp::Load(obs_data_t *obj)
|
|||||||
{
|
{
|
||||||
MacroAction::Load(obj);
|
MacroAction::Load(obj);
|
||||||
_url.Load(obj, "url");
|
_url.Load(obj, "url");
|
||||||
_path.Load(obj, "path");
|
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");
|
_contentType.Load(obj, "contentType");
|
||||||
_body.Load(obj, "body");
|
_body.Load(obj, "body");
|
||||||
_setHeaders = obs_data_get_bool(obj, "setHeaders");
|
_setHeaders = obs_data_get_bool(obj, "setHeaders");
|
||||||
@@ -230,7 +259,6 @@ std::shared_ptr<MacroAction> MacroActionHttp::Copy() const
|
|||||||
void MacroActionHttp::ResolveVariablesToFixedValues()
|
void MacroActionHttp::ResolveVariablesToFixedValues()
|
||||||
{
|
{
|
||||||
_url.ResolveVariables();
|
_url.ResolveVariables();
|
||||||
_path.ResolveVariables();
|
|
||||||
_contentType.ResolveVariables();
|
_contentType.ResolveVariables();
|
||||||
_body.ResolveVariables();
|
_body.ResolveVariables();
|
||||||
_headers.ResolveVariables();
|
_headers.ResolveVariables();
|
||||||
@@ -263,7 +291,6 @@ MacroActionHttpEdit::MacroActionHttpEdit(
|
|||||||
QWidget *parent, std::shared_ptr<MacroActionHttp> entryData)
|
QWidget *parent, std::shared_ptr<MacroActionHttp> entryData)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
_url(new VariableLineEdit(this)),
|
_url(new VariableLineEdit(this)),
|
||||||
_path(new VariableLineEdit(this)),
|
|
||||||
_contentType(new VariableLineEdit(this)),
|
_contentType(new VariableLineEdit(this)),
|
||||||
_contentTypeLayout(new QHBoxLayout()),
|
_contentTypeLayout(new QHBoxLayout()),
|
||||||
_methods(new QComboBox()),
|
_methods(new QComboBox()),
|
||||||
@@ -306,7 +333,6 @@ void MacroActionHttpEdit::UpdateEntryData()
|
|||||||
}
|
}
|
||||||
|
|
||||||
_url->setText(_entryData->_url);
|
_url->setText(_entryData->_url);
|
||||||
_path->setText(_entryData->_path);
|
|
||||||
_contentType->setText(_entryData->_contentType);
|
_contentType->setText(_entryData->_contentType);
|
||||||
_body->setPlainText(_entryData->_body);
|
_body->setPlainText(_entryData->_body);
|
||||||
_setHeaders->setChecked(_entryData->_setHeaders);
|
_setHeaders->setChecked(_entryData->_setHeaders);
|
||||||
@@ -326,12 +352,6 @@ void MacroActionHttpEdit::URLChanged()
|
|||||||
emit(HeaderInfoChanged(_url->text()));
|
emit(HeaderInfoChanged(_url->text()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroActionHttpEdit::PathChanged()
|
|
||||||
{
|
|
||||||
GUARD_LOADING_AND_LOCK();
|
|
||||||
_entryData->_path = _path->text().toStdString();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MacroActionHttpEdit::ContentTypeChanged()
|
void MacroActionHttpEdit::ContentTypeChanged()
|
||||||
{
|
{
|
||||||
GUARD_LOADING_AND_LOCK();
|
GUARD_LOADING_AND_LOCK();
|
||||||
@@ -395,8 +415,6 @@ void MacroActionHttpEdit::SetWidgetSignalConnections()
|
|||||||
{
|
{
|
||||||
QWidget::connect(_url, SIGNAL(editingFinished()), this,
|
QWidget::connect(_url, SIGNAL(editingFinished()), this,
|
||||||
SLOT(URLChanged()));
|
SLOT(URLChanged()));
|
||||||
QWidget::connect(_path, SIGNAL(editingFinished()), this,
|
|
||||||
SLOT(PathChanged()));
|
|
||||||
QWidget::connect(_contentType, SIGNAL(editingFinished()), this,
|
QWidget::connect(_contentType, SIGNAL(editingFinished()), this,
|
||||||
SLOT(ContentTypeChanged()));
|
SLOT(ContentTypeChanged()));
|
||||||
QWidget::connect(_body, SIGNAL(textChanged()), this,
|
QWidget::connect(_body, SIGNAL(textChanged()), this,
|
||||||
@@ -420,18 +438,15 @@ void MacroActionHttpEdit::SetWidgetSignalConnections()
|
|||||||
void MacroActionHttpEdit::SetWidgetLayout()
|
void MacroActionHttpEdit::SetWidgetLayout()
|
||||||
{
|
{
|
||||||
const std::unordered_map<std::string, QWidget *> widgets = {
|
const std::unordered_map<std::string, QWidget *> widgets = {
|
||||||
{"{{url}}", _url},
|
{"{{url}}", _url}, {"{{contentType}}", _contentType},
|
||||||
{"{{path}}", _path},
|
{"{{method}}", _methods}, {"{{body}}", _body},
|
||||||
{"{{contentType}}", _contentType},
|
|
||||||
{"{{method}}", _methods},
|
|
||||||
{"{{body}}", _body},
|
|
||||||
{"{{timeout}}", _timeout},
|
{"{{timeout}}", _timeout},
|
||||||
};
|
};
|
||||||
|
|
||||||
auto actionLayout = new QHBoxLayout;
|
auto actionLayout = new QHBoxLayout;
|
||||||
PlaceWidgets(
|
PlaceWidgets(
|
||||||
obs_module_text("AdvSceneSwitcher.action.http.layout.method"),
|
obs_module_text("AdvSceneSwitcher.action.http.layout.method"),
|
||||||
actionLayout, widgets);
|
actionLayout, widgets, false);
|
||||||
|
|
||||||
PlaceWidgets(obs_module_text(
|
PlaceWidgets(obs_module_text(
|
||||||
"AdvSceneSwitcher.action.http.layout.contentType"),
|
"AdvSceneSwitcher.action.http.layout.contentType"),
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
StringVariable _url = "127.0.0.1:8080";
|
StringVariable _url = "127.0.0.1:8080";
|
||||||
StringVariable _path = "/";
|
|
||||||
StringVariable _body = obs_module_text("AdvSceneSwitcher.enterText");
|
StringVariable _body = obs_module_text("AdvSceneSwitcher.enterText");
|
||||||
StringVariable _contentType = "application/json";
|
StringVariable _contentType = "application/json";
|
||||||
bool _setHeaders = false;
|
bool _setHeaders = false;
|
||||||
@@ -68,7 +67,6 @@ public:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void URLChanged();
|
void URLChanged();
|
||||||
void PathChanged();
|
|
||||||
void BodyChanged();
|
void BodyChanged();
|
||||||
void ContentTypeChanged();
|
void ContentTypeChanged();
|
||||||
void MethodChanged(int);
|
void MethodChanged(int);
|
||||||
@@ -86,7 +84,6 @@ private:
|
|||||||
void SetWidgetVisibility();
|
void SetWidgetVisibility();
|
||||||
|
|
||||||
VariableLineEdit *_url;
|
VariableLineEdit *_url;
|
||||||
VariableLineEdit *_path;
|
|
||||||
VariableLineEdit *_contentType;
|
VariableLineEdit *_contentType;
|
||||||
QHBoxLayout *_contentTypeLayout;
|
QHBoxLayout *_contentTypeLayout;
|
||||||
QComboBox *_methods;
|
QComboBox *_methods;
|
||||||
|
|||||||
40
plugins/scripting/CMakeLists.txt
Normal file
40
plugins/scripting/CMakeLists.txt
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.14)
|
||||||
|
project(advanced-scene-switcher-scripting)
|
||||||
|
|
||||||
|
# --- End of section ---
|
||||||
|
|
||||||
|
add_library(${PROJECT_NAME} MODULE)
|
||||||
|
|
||||||
|
target_sources(
|
||||||
|
${PROJECT_NAME}
|
||||||
|
PRIVATE macro-action-script.cpp
|
||||||
|
macro-action-script.hpp
|
||||||
|
macro-action-script-inline.cpp
|
||||||
|
macro-action-script-inline.hpp
|
||||||
|
macro-condition-script.cpp
|
||||||
|
macro-condition-script.hpp
|
||||||
|
macro-condition-script-inline.cpp
|
||||||
|
macro-condition-script-inline.hpp
|
||||||
|
macro-script-handler.cpp
|
||||||
|
macro-script-handler.hpp
|
||||||
|
macro-segment-script.cpp
|
||||||
|
macro-segment-script.hpp
|
||||||
|
macro-segment-script-inline.cpp
|
||||||
|
macro-segment-script-inline.hpp)
|
||||||
|
|
||||||
|
target_sources(
|
||||||
|
${PROJECT_NAME}
|
||||||
|
PRIVATE utils/inline-script.cpp
|
||||||
|
utils/inline-script.hpp
|
||||||
|
utils/obs-script-helpers.cpp
|
||||||
|
utils/obs-script-helpers.hpp
|
||||||
|
utils/properties-view.cpp
|
||||||
|
utils/properties-view.hpp
|
||||||
|
utils/properties-view.moc.hpp)
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT_NAME}
|
||||||
|
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/utils")
|
||||||
|
|
||||||
|
setup_advss_plugin(${PROJECT_NAME})
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
|
||||||
|
install_advss_plugin(${PROJECT_NAME})
|
||||||
66
plugins/scripting/macro-action-script-inline.cpp
Normal file
66
plugins/scripting/macro-action-script-inline.cpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#include "macro-action-script-inline.hpp"
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
const std::string MacroActionScriptInline::_id = "script";
|
||||||
|
|
||||||
|
bool MacroActionScriptInline::_registered = MacroActionFactory::Register(
|
||||||
|
MacroActionScriptInline::_id,
|
||||||
|
{MacroActionScriptInline::Create, MacroActionScriptInlineEdit::Create,
|
||||||
|
"AdvSceneSwitcher.action.script"});
|
||||||
|
|
||||||
|
bool MacroActionScriptInline::PerformAction()
|
||||||
|
{
|
||||||
|
return _script.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroActionScriptInline::LogAction() const
|
||||||
|
{
|
||||||
|
ablog(LOG_INFO, "performing inline script action");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacroActionScriptInline::Save(obs_data_t *obj) const
|
||||||
|
{
|
||||||
|
MacroAction::Save(obj);
|
||||||
|
_script.Save(obj);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacroActionScriptInline::Load(obs_data_t *obj)
|
||||||
|
{
|
||||||
|
MacroAction::Load(obj);
|
||||||
|
_script.Load(obj);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<MacroAction> MacroActionScriptInline::Create(Macro *m)
|
||||||
|
{
|
||||||
|
return std::make_shared<MacroActionScriptInline>(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<MacroAction> MacroActionScriptInline::Copy() const
|
||||||
|
{
|
||||||
|
return std::make_shared<MacroActionScriptInline>(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroActionScriptInline::ResolveVariablesToFixedValues()
|
||||||
|
{
|
||||||
|
_script.ResolveVariablesToFixedValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
MacroActionScriptInlineEdit::MacroActionScriptInlineEdit(
|
||||||
|
QWidget *parent, std::shared_ptr<MacroActionScriptInline> entryData)
|
||||||
|
: MacroSegmentScriptInlineEdit(parent, entryData)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *
|
||||||
|
MacroActionScriptInlineEdit::Create(QWidget *parent,
|
||||||
|
std::shared_ptr<MacroAction> action)
|
||||||
|
{
|
||||||
|
return new MacroActionScriptInlineEdit(
|
||||||
|
parent,
|
||||||
|
std::dynamic_pointer_cast<MacroActionScriptInline>(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
39
plugins/scripting/macro-action-script-inline.hpp
Normal file
39
plugins/scripting/macro-action-script-inline.hpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "macro-action-edit.hpp"
|
||||||
|
#include "macro-segment-script-inline.hpp"
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
class MacroActionScriptInline : public MacroAction,
|
||||||
|
public MacroSegmentScriptInline {
|
||||||
|
public:
|
||||||
|
MacroActionScriptInline(Macro *m) : MacroAction(m) {}
|
||||||
|
|
||||||
|
bool PerformAction();
|
||||||
|
void LogAction() const;
|
||||||
|
|
||||||
|
bool Save(obs_data_t *obj) const;
|
||||||
|
bool Load(obs_data_t *obj);
|
||||||
|
|
||||||
|
std::string GetId() const { return _id; };
|
||||||
|
|
||||||
|
static std::shared_ptr<MacroAction> Create(Macro *m);
|
||||||
|
std::shared_ptr<MacroAction> Copy() const;
|
||||||
|
|
||||||
|
void ResolveVariablesToFixedValues();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool _registered;
|
||||||
|
static const std::string _id;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MacroActionScriptInlineEdit : public MacroSegmentScriptInlineEdit {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
MacroActionScriptInlineEdit(
|
||||||
|
QWidget *, std::shared_ptr<MacroActionScriptInline> = nullptr);
|
||||||
|
static QWidget *Create(QWidget *, std::shared_ptr<MacroAction>);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "macro-action-script.hpp"
|
#include "macro-action-script.hpp"
|
||||||
#include "layout-helpers.hpp"
|
#include "layout-helpers.hpp"
|
||||||
#include "macro-helpers.hpp"
|
#include "macro-helpers.hpp"
|
||||||
#include "properties-view.hpp"
|
|
||||||
#include "sync-helpers.hpp"
|
#include "sync-helpers.hpp"
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
51
plugins/scripting/macro-condition-script-inline.cpp
Normal file
51
plugins/scripting/macro-condition-script-inline.cpp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#include "macro-condition-script-inline.hpp"
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
const std::string MacroConditionScriptInline::_id = "script";
|
||||||
|
|
||||||
|
bool MacroConditionScriptInline::_registered =
|
||||||
|
MacroConditionFactory::Register(MacroConditionScriptInline::_id,
|
||||||
|
{MacroConditionScriptInline::Create,
|
||||||
|
MacroConditionScriptInlineEdit::Create,
|
||||||
|
"AdvSceneSwitcher.condition.script"});
|
||||||
|
|
||||||
|
bool MacroConditionScriptInline::CheckCondition()
|
||||||
|
{
|
||||||
|
return _script.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacroConditionScriptInline::Save(obs_data_t *obj) const
|
||||||
|
{
|
||||||
|
MacroCondition::Save(obj);
|
||||||
|
_script.Save(obj);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacroConditionScriptInline::Load(obs_data_t *obj)
|
||||||
|
{
|
||||||
|
MacroCondition::Load(obj);
|
||||||
|
_script.Load(obj);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<MacroCondition> MacroConditionScriptInline::Create(Macro *m)
|
||||||
|
{
|
||||||
|
return std::make_shared<MacroConditionScriptInline>(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
MacroConditionScriptInlineEdit::MacroConditionScriptInlineEdit(
|
||||||
|
QWidget *parent, std::shared_ptr<MacroConditionScriptInline> entryData)
|
||||||
|
: MacroSegmentScriptInlineEdit(parent, entryData)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *MacroConditionScriptInlineEdit::Create(
|
||||||
|
QWidget *parent, std::shared_ptr<MacroCondition> condition)
|
||||||
|
{
|
||||||
|
return new MacroConditionScriptInlineEdit(
|
||||||
|
parent, std::dynamic_pointer_cast<MacroConditionScriptInline>(
|
||||||
|
condition));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
36
plugins/scripting/macro-condition-script-inline.hpp
Normal file
36
plugins/scripting/macro-condition-script-inline.hpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "macro-condition-edit.hpp"
|
||||||
|
#include "macro-segment-script-inline.hpp"
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
class MacroConditionScriptInline : public MacroCondition,
|
||||||
|
public MacroSegmentScriptInline {
|
||||||
|
public:
|
||||||
|
MacroConditionScriptInline(Macro *m) : MacroCondition(m) {}
|
||||||
|
|
||||||
|
bool CheckCondition();
|
||||||
|
|
||||||
|
bool Save(obs_data_t *obj) const;
|
||||||
|
bool Load(obs_data_t *obj);
|
||||||
|
|
||||||
|
std::string GetId() const { return _id; };
|
||||||
|
|
||||||
|
static std::shared_ptr<MacroCondition> Create(Macro *m);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool _registered;
|
||||||
|
static const std::string _id;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MacroConditionScriptInlineEdit : public MacroSegmentScriptInlineEdit {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
MacroConditionScriptInlineEdit(
|
||||||
|
QWidget *,
|
||||||
|
std::shared_ptr<MacroConditionScriptInline> = nullptr);
|
||||||
|
static QWidget *Create(QWidget *, std::shared_ptr<MacroCondition>);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
198
plugins/scripting/macro-segment-script-inline.cpp
Normal file
198
plugins/scripting/macro-segment-script-inline.cpp
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
#include "macro-segment-script-inline.hpp"
|
||||||
|
#include "layout-helpers.hpp"
|
||||||
|
#include "obs-module-helper.hpp"
|
||||||
|
#include "sync-helpers.hpp"
|
||||||
|
#include "ui-helpers.hpp"
|
||||||
|
|
||||||
|
#include <QDesktopServices>
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
void MacroSegmentScriptInline::SetType(InlineScript::Type type)
|
||||||
|
{
|
||||||
|
_script.SetType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroSegmentScriptInline::SetLanguage(obs_script_lang language)
|
||||||
|
{
|
||||||
|
_script.SetLanguage(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroSegmentScriptInline::SetScript(const std::string &text)
|
||||||
|
{
|
||||||
|
_script.SetText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroSegmentScriptInline::SetPath(const std::string &path)
|
||||||
|
{
|
||||||
|
_script.SetPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void populateLanguageSelection(QComboBox *list)
|
||||||
|
{
|
||||||
|
list->addItem(
|
||||||
|
obs_module_text("AdvSceneSwitcher.script.language.python"),
|
||||||
|
OBS_SCRIPT_LANG_PYTHON);
|
||||||
|
list->addItem(obs_module_text("AdvSceneSwitcher.script.language.lua"),
|
||||||
|
OBS_SCRIPT_LANG_LUA);
|
||||||
|
list->setPlaceholderText(
|
||||||
|
obs_module_text("AdvSceneSwitcher.script.language.select"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void populateScriptTypeSelection(QComboBox *list)
|
||||||
|
{
|
||||||
|
list->addItem(obs_module_text("AdvSceneSwitcher.script.type.inline"),
|
||||||
|
InlineScript::INLINE);
|
||||||
|
list->addItem(obs_module_text("AdvSceneSwitcher.script.type.file"),
|
||||||
|
InlineScript::FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
MacroSegmentScriptInlineEdit::MacroSegmentScriptInlineEdit(
|
||||||
|
QWidget *parent, std::shared_ptr<MacroSegmentScriptInline> entryData)
|
||||||
|
: QWidget(parent),
|
||||||
|
_scriptType(new QComboBox(this)),
|
||||||
|
_language(new QComboBox(this)),
|
||||||
|
_script(new ScriptEditor(this)),
|
||||||
|
_path(new FileSelection(FileSelection::Type::WRITE, this)),
|
||||||
|
_openFile(new QPushButton(
|
||||||
|
obs_module_text("AdvSceneSwitcher.script.file.open"), this)),
|
||||||
|
_fileLayout(new QHBoxLayout()),
|
||||||
|
_entryData(entryData)
|
||||||
|
{
|
||||||
|
SetupLayout();
|
||||||
|
SetupWidgetConnections();
|
||||||
|
PopulateWidgets();
|
||||||
|
SetWidgetVisibility();
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroSegmentScriptInlineEdit::ScriptTypeChanged(int idx)
|
||||||
|
{
|
||||||
|
GUARD_LOADING_AND_LOCK();
|
||||||
|
_entryData->SetType(static_cast<InlineScript::Type>(
|
||||||
|
_scriptType->itemData(idx).toInt()));
|
||||||
|
SetWidgetVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroSegmentScriptInlineEdit::LanguageChanged(int idx)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
GUARD_LOADING_AND_LOCK();
|
||||||
|
_entryData->SetLanguage(static_cast<obs_script_lang>(
|
||||||
|
_language->itemData(idx).toInt()));
|
||||||
|
const QSignalBlocker b(_script);
|
||||||
|
_script->setPlainText(_entryData->GetScript());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_entryData->GetType() == InlineScript::Type::FILE) {
|
||||||
|
PathChanged(QString::fromStdString(_entryData->GetPath()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroSegmentScriptInlineEdit::ScriptChanged()
|
||||||
|
{
|
||||||
|
GUARD_LOADING_AND_LOCK();
|
||||||
|
_entryData->SetScript(_script->toPlainText().toStdString());
|
||||||
|
adjustSize();
|
||||||
|
updateGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroSegmentScriptInlineEdit::PathChanged(const QString &path)
|
||||||
|
{
|
||||||
|
GUARD_LOADING_AND_LOCK();
|
||||||
|
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
_entryData->SetPath(path.toStdString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Script language will be detected by OBS based on file extension so
|
||||||
|
// adjust it if necessary
|
||||||
|
QString pathAdjusted = path;
|
||||||
|
if (_entryData->GetLanguage() == OBS_SCRIPT_LANG_PYTHON &&
|
||||||
|
!path.endsWith(".py")) {
|
||||||
|
if (path.endsWith(".lua")) {
|
||||||
|
pathAdjusted.chop(4);
|
||||||
|
}
|
||||||
|
pathAdjusted += ".py";
|
||||||
|
}
|
||||||
|
if (_entryData->GetLanguage() == OBS_SCRIPT_LANG_LUA &&
|
||||||
|
!path.endsWith(".lua")) {
|
||||||
|
if (path.endsWith(".py")) {
|
||||||
|
pathAdjusted.chop(3);
|
||||||
|
}
|
||||||
|
pathAdjusted += ".lua";
|
||||||
|
}
|
||||||
|
|
||||||
|
const QSignalBlocker b(_path);
|
||||||
|
_path->SetPath(pathAdjusted);
|
||||||
|
_entryData->SetPath(pathAdjusted.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroSegmentScriptInlineEdit::PopulateWidgets()
|
||||||
|
{
|
||||||
|
populateLanguageSelection(_language);
|
||||||
|
populateScriptTypeSelection(_scriptType);
|
||||||
|
|
||||||
|
if (!_entryData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scriptType->setCurrentIndex(
|
||||||
|
_scriptType->findData(_entryData->GetType()));
|
||||||
|
_language->setCurrentIndex(
|
||||||
|
_language->findData(_entryData->GetLanguage()));
|
||||||
|
_script->setPlainText(_entryData->GetScript());
|
||||||
|
_path->SetPath(_entryData->GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroSegmentScriptInlineEdit::SetupWidgetConnections()
|
||||||
|
{
|
||||||
|
QWidget::connect(_scriptType, SIGNAL(currentIndexChanged(int)), this,
|
||||||
|
SLOT(ScriptTypeChanged(int)));
|
||||||
|
QWidget::connect(_language, SIGNAL(currentIndexChanged(int)), this,
|
||||||
|
SLOT(LanguageChanged(int)));
|
||||||
|
QWidget::connect(_script, SIGNAL(ScriptChanged()), this,
|
||||||
|
SLOT(ScriptChanged()));
|
||||||
|
QWidget::connect(_path, SIGNAL(PathChanged(const QString &)), this,
|
||||||
|
SLOT(PathChanged(const QString &)));
|
||||||
|
QWidget::connect(_openFile, &QPushButton::clicked, this, [this] {
|
||||||
|
QUrl fileUrl = QUrl::fromLocalFile(
|
||||||
|
QString::fromStdString(_entryData->GetPath()));
|
||||||
|
if (!QDesktopServices::openUrl(fileUrl)) {
|
||||||
|
DisplayMessage(obs_module_text(
|
||||||
|
"AdvSceneSwitcher.script.file.open.failed"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroSegmentScriptInlineEdit::SetupLayout()
|
||||||
|
{
|
||||||
|
auto languageLayout = new QHBoxLayout();
|
||||||
|
PlaceWidgets(obs_module_text("AdvSceneSwitcher.script.language.layout"),
|
||||||
|
languageLayout, {{"{{language}}", _language}});
|
||||||
|
auto typeLayout = new QHBoxLayout();
|
||||||
|
PlaceWidgets(obs_module_text("AdvSceneSwitcher.script.type.layout"),
|
||||||
|
typeLayout, {{"{{scriptType}}", _scriptType}});
|
||||||
|
PlaceWidgets(obs_module_text("AdvSceneSwitcher.script.file.layout"),
|
||||||
|
_fileLayout,
|
||||||
|
{{"{{path}}", _path}, {"{{open}}", _openFile}}, false);
|
||||||
|
auto layout = new QVBoxLayout();
|
||||||
|
layout->addLayout(typeLayout);
|
||||||
|
layout->addLayout(languageLayout);
|
||||||
|
layout->addLayout(_fileLayout);
|
||||||
|
layout->addWidget(_script);
|
||||||
|
setLayout(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroSegmentScriptInlineEdit::SetWidgetVisibility()
|
||||||
|
{
|
||||||
|
_script->setVisible(_entryData->GetType() ==
|
||||||
|
InlineScript::Type::INLINE);
|
||||||
|
SetLayoutVisible(_fileLayout,
|
||||||
|
_entryData->GetType() == InlineScript::Type::FILE);
|
||||||
|
adjustSize();
|
||||||
|
updateGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
56
plugins/scripting/macro-segment-script-inline.hpp
Normal file
56
plugins/scripting/macro-segment-script-inline.hpp
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "file-selection.hpp"
|
||||||
|
#include "inline-script.hpp"
|
||||||
|
#include "variable-text-edit.hpp"
|
||||||
|
|
||||||
|
#include <QLayout>
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
class MacroSegmentScriptInline {
|
||||||
|
public:
|
||||||
|
InlineScript::Type GetType() const { return _script.GetType(); }
|
||||||
|
void SetType(InlineScript::Type);
|
||||||
|
obs_script_lang GetLanguage() const { return _script.GetLanguage(); }
|
||||||
|
void SetLanguage(obs_script_lang);
|
||||||
|
StringVariable GetScript() const { return _script.GetText(); }
|
||||||
|
void SetScript(const std::string &);
|
||||||
|
std::string GetPath() const { return _script.GetPath(); }
|
||||||
|
void SetPath(const std::string &);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
InlineScript _script;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MacroSegmentScriptInlineEdit : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
MacroSegmentScriptInlineEdit(
|
||||||
|
QWidget *, std::shared_ptr<MacroSegmentScriptInline> = nullptr);
|
||||||
|
virtual ~MacroSegmentScriptInlineEdit() = default;
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void ScriptTypeChanged(int);
|
||||||
|
void LanguageChanged(int);
|
||||||
|
void ScriptChanged();
|
||||||
|
void PathChanged(const QString &);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void PopulateWidgets();
|
||||||
|
void SetupWidgetConnections();
|
||||||
|
void SetupLayout();
|
||||||
|
void SetWidgetVisibility();
|
||||||
|
|
||||||
|
QComboBox *_scriptType;
|
||||||
|
QComboBox *_language;
|
||||||
|
ScriptEditor *_script;
|
||||||
|
FileSelection *_path;
|
||||||
|
QPushButton *_openFile;
|
||||||
|
QHBoxLayout *_fileLayout;
|
||||||
|
|
||||||
|
std::shared_ptr<MacroSegmentScriptInline> _entryData;
|
||||||
|
bool _loading = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
344
plugins/scripting/utils/inline-script.cpp
Normal file
344
plugins/scripting/utils/inline-script.cpp
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
#include "inline-script.hpp"
|
||||||
|
#include "log-helper.hpp"
|
||||||
|
#include "obs-module-helper.hpp"
|
||||||
|
|
||||||
|
#include <obs-module.h>
|
||||||
|
#include <obs.hpp>
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
static constexpr std::string_view signalName = "advss_run_temp_script";
|
||||||
|
|
||||||
|
std::atomic_uint64_t InlineScript::_instanceIdCounter = 0;
|
||||||
|
const std::string_view InlineScript::_defaultPythonScript =
|
||||||
|
"import obspython as obs\n"
|
||||||
|
"\n"
|
||||||
|
"def run():\n"
|
||||||
|
" obs.script_log(obs.LOG_WARNING, \"Hello from Python!\")\n"
|
||||||
|
" return True\n";
|
||||||
|
const std::string_view InlineScript::_defaultLUAScript =
|
||||||
|
"obs = obslua\n"
|
||||||
|
"\n"
|
||||||
|
"function run()\n"
|
||||||
|
" obs.script_log(obs.LOG_WARNING, \"Hello from LUA!\")\n"
|
||||||
|
" return true\n"
|
||||||
|
"end";
|
||||||
|
;
|
||||||
|
|
||||||
|
static bool setup()
|
||||||
|
{
|
||||||
|
auto sh = obs_get_signal_handler();
|
||||||
|
auto signalDecl =
|
||||||
|
std::string("void ") + signalName.data() + "(string id)";
|
||||||
|
signal_handler_add(sh, signalDecl.c_str());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool setupDone = setup();
|
||||||
|
|
||||||
|
static void cleanupScriptFile(const std::string &path)
|
||||||
|
{
|
||||||
|
const QFileInfo fileInfo(QString::fromStdString(path));
|
||||||
|
if (!fileInfo.isFile()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QFile file(fileInfo.absoluteFilePath());
|
||||||
|
if (!file.remove()) {
|
||||||
|
vblog(LOG_INFO, "failed to clean up script file %s",
|
||||||
|
fileInfo.absoluteFilePath().toStdString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<std::string>
|
||||||
|
getScriptTempFilePath(obs_script_lang language)
|
||||||
|
{
|
||||||
|
static int counter = 0;
|
||||||
|
++counter;
|
||||||
|
static const QString filenamePattern =
|
||||||
|
"scripting/advss-tmp-script%1.%2";
|
||||||
|
const QString filename = filenamePattern.arg(counter).arg(
|
||||||
|
language == OBS_SCRIPT_LANG_PYTHON ? "py" : "lua");
|
||||||
|
auto settingsFile =
|
||||||
|
obs_module_config_path(filename.toStdString().c_str());
|
||||||
|
|
||||||
|
if (!settingsFile) {
|
||||||
|
blog(LOG_WARNING,
|
||||||
|
"could not create temp script file! (obs_module_config_path)");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path = settingsFile;
|
||||||
|
bfree(settingsFile);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool createScriptFile(const char *settingsFile, const char *content)
|
||||||
|
{
|
||||||
|
const QFileInfo fileInfo(settingsFile);
|
||||||
|
const QString dirPath = fileInfo.absolutePath();
|
||||||
|
const QDir dir(dirPath);
|
||||||
|
if (!dir.exists() && !dir.mkpath(dirPath)) {
|
||||||
|
blog(LOG_WARNING, "could not create script file! (mkpath)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(settingsFile);
|
||||||
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto out = QTextStream(&file);
|
||||||
|
out << content;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineScript::InlineScript() : _instanceId(_instanceIdCounter++)
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineScript::InlineScript(const InlineScript &other)
|
||||||
|
: _language(other._language),
|
||||||
|
_textPython(other._textPython),
|
||||||
|
_textLUA(other._textLUA),
|
||||||
|
_instanceId(_instanceIdCounter++)
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineScript::Save(obs_data_t *data) const
|
||||||
|
{
|
||||||
|
OBSDataAutoRelease obj = obs_data_create();
|
||||||
|
obs_data_set_int(obj, "type", _type);
|
||||||
|
obs_data_set_int(obj, "language", _language);
|
||||||
|
_textPython.Save(obj, "scriptPython");
|
||||||
|
_textLUA.Save(obj, "scriptLUA");
|
||||||
|
obs_data_set_string(obj, "file", _file.c_str());
|
||||||
|
obs_data_set_obj(data, "script", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineScript::Load(obs_data_t *data)
|
||||||
|
{
|
||||||
|
OBSDataAutoRelease obj = obs_data_get_obj(data, "script");
|
||||||
|
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
|
||||||
|
_language =
|
||||||
|
static_cast<obs_script_lang>(obs_data_get_int(obj, "language"));
|
||||||
|
_textPython.Load(obj, "scriptPython");
|
||||||
|
_textLUA.Load(obj, "scriptLUA");
|
||||||
|
_file = obs_data_get_string(obj, "file");
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineScript::SetType(Type type)
|
||||||
|
{
|
||||||
|
_type = type;
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineScript::SetLanguage(obs_script_lang language)
|
||||||
|
{
|
||||||
|
_language = language;
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineScript::SetText(const std::string &text)
|
||||||
|
{
|
||||||
|
switch (_language) {
|
||||||
|
case OBS_SCRIPT_LANG_UNKNOWN:
|
||||||
|
break;
|
||||||
|
case OBS_SCRIPT_LANG_LUA:
|
||||||
|
_textLUA = text;
|
||||||
|
break;
|
||||||
|
case OBS_SCRIPT_LANG_PYTHON:
|
||||||
|
_textPython = text;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
const StringVariable &InlineScript::GetText() const
|
||||||
|
{
|
||||||
|
static const StringVariable defaultRet;
|
||||||
|
|
||||||
|
switch (_language) {
|
||||||
|
case OBS_SCRIPT_LANG_UNKNOWN:
|
||||||
|
break;
|
||||||
|
case OBS_SCRIPT_LANG_LUA:
|
||||||
|
return _textLUA;
|
||||||
|
case OBS_SCRIPT_LANG_PYTHON:
|
||||||
|
return _textPython;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return defaultRet;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineScript::SetPath(const std::string &path)
|
||||||
|
{
|
||||||
|
_file = path;
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InlineScript::Run()
|
||||||
|
{
|
||||||
|
static auto sh = obs_get_signal_handler();
|
||||||
|
|
||||||
|
if (_type == INLINE && _lastResolvedText != std::string(GetText())) {
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_type == FILE && _lastPath != _file) {
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cd = calldata_create();
|
||||||
|
calldata_set_string(cd, "id", GetID().c_str());
|
||||||
|
signal_handler_signal(sh, signalName.data(), cd);
|
||||||
|
bool result = calldata_bool(cd, "result");
|
||||||
|
calldata_destroy(cd);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineScript::ResolveVariablesToFixedValues()
|
||||||
|
{
|
||||||
|
_textPython.ResolveVariables();
|
||||||
|
_textLUA.ResolveVariables();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string preprocessScriptText(const std::string &text,
|
||||||
|
obs_script_lang language,
|
||||||
|
const std::string &id)
|
||||||
|
{
|
||||||
|
const std::string footerPython =
|
||||||
|
std::string("\n\n"
|
||||||
|
"## AUTO GENERATED ##\n"
|
||||||
|
"def script_load(settings):\n"
|
||||||
|
" def run_wrapper(data):\n"
|
||||||
|
" id = obs.calldata_string(data, \"id\")\n"
|
||||||
|
" if id == \"") +
|
||||||
|
id +
|
||||||
|
"\":\n"
|
||||||
|
" ret = run()\n"
|
||||||
|
" obs.calldata_set_bool(data, \"result\", ret)\n"
|
||||||
|
" sh = obs.obs_get_signal_handler()\n"
|
||||||
|
" obs.signal_handler_connect(sh, \"" +
|
||||||
|
signalName.data() + "\", run_wrapper)\n\n";
|
||||||
|
|
||||||
|
const std::string footerLUA =
|
||||||
|
std::string(
|
||||||
|
"\n\n"
|
||||||
|
"-- AUTO GENERATED --\n"
|
||||||
|
"function script_load(settings)\n"
|
||||||
|
" local run_wrapper = (function(data)\n"
|
||||||
|
" local id = obs.calldata_string(data, \"id\")\n"
|
||||||
|
" if id == \"") +
|
||||||
|
id +
|
||||||
|
"\" then\n"
|
||||||
|
" local ret = run()\n"
|
||||||
|
" obs.calldata_set_bool(data, \"result\", ret)\n"
|
||||||
|
" end\n"
|
||||||
|
" end)\n"
|
||||||
|
" local sh = obs.obs_get_signal_handler()\n"
|
||||||
|
" obs.signal_handler_connect(sh, \"" +
|
||||||
|
signalName.data() +
|
||||||
|
"\" , run_wrapper)\n"
|
||||||
|
"end\n";
|
||||||
|
|
||||||
|
std::string scriptText =
|
||||||
|
language == OBS_SCRIPT_LANG_PYTHON ? footerPython : footerLUA;
|
||||||
|
return text + scriptText;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineScript::SetupFile()
|
||||||
|
{
|
||||||
|
const auto path = GetLUACompatiblePath(_file);
|
||||||
|
_fileId = path;
|
||||||
|
|
||||||
|
if (path.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QFileInfo(QString::fromStdString(path)).exists()) {
|
||||||
|
const auto text = preprocessScriptText(
|
||||||
|
_language == OBS_SCRIPT_LANG_PYTHON
|
||||||
|
? _defaultPythonScript.data()
|
||||||
|
: _defaultLUAScript.data(),
|
||||||
|
_language, GetID());
|
||||||
|
(void)createScriptFile(_file.c_str(), text.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
_script = std::unique_ptr<obs_script_t, ScriptDeleter>(
|
||||||
|
CreateOBSScript(path.c_str(), nullptr), {});
|
||||||
|
_lastPath = _file;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineScript::SetupInline()
|
||||||
|
{
|
||||||
|
const StringVariable &text =
|
||||||
|
_language == OBS_SCRIPT_LANG_PYTHON ? _textPython : _textLUA;
|
||||||
|
const auto scriptText = preprocessScriptText(text, _language, GetID());
|
||||||
|
|
||||||
|
auto path_ = getScriptTempFilePath(_language);
|
||||||
|
if (!path_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto path = GetLUACompatiblePath(*path_);
|
||||||
|
if (!createScriptFile(path.c_str(), scriptText.c_str())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_script = std::unique_ptr<obs_script_t, ScriptDeleter>(
|
||||||
|
CreateOBSScript(path.c_str(), nullptr), {path});
|
||||||
|
_lastResolvedText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineScript::Setup()
|
||||||
|
{
|
||||||
|
_script.reset();
|
||||||
|
_lastResolvedText = "";
|
||||||
|
_lastPath = "";
|
||||||
|
|
||||||
|
if (_type == FILE) {
|
||||||
|
SetupFile();
|
||||||
|
} else {
|
||||||
|
SetupInline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string InlineScript::GetID() const
|
||||||
|
{
|
||||||
|
if (_type == FILE) {
|
||||||
|
return _fileId;
|
||||||
|
}
|
||||||
|
return std::to_string(_instanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineScript::ScriptDeleter::operator()(obs_script_t *script)
|
||||||
|
{
|
||||||
|
DestroyOBSScript(script);
|
||||||
|
if (!tempScriptPath.empty()) {
|
||||||
|
cleanupScriptFile(tempScriptPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptEditor::ScriptEditor(QWidget *parent) : VariableTextEdit(parent, 15, 5)
|
||||||
|
{
|
||||||
|
installEventFilter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptEditor::eventFilter(QObject *obj, QEvent *event)
|
||||||
|
{
|
||||||
|
if (event->type() == QEvent::FocusOut) {
|
||||||
|
emit ScriptChanged();
|
||||||
|
}
|
||||||
|
return QObject::eventFilter(obj, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
76
plugins/scripting/utils/inline-script.hpp
Normal file
76
plugins/scripting/utils/inline-script.hpp
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "obs-script-helpers.hpp"
|
||||||
|
#include "variable-text-edit.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <obs-data.h>
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
class InlineScript {
|
||||||
|
public:
|
||||||
|
InlineScript();
|
||||||
|
InlineScript(const InlineScript &);
|
||||||
|
|
||||||
|
enum Type { INLINE, FILE };
|
||||||
|
|
||||||
|
void Save(obs_data_t *) const;
|
||||||
|
void Load(obs_data_t *);
|
||||||
|
|
||||||
|
void SetType(Type);
|
||||||
|
Type GetType() const { return _type; }
|
||||||
|
void SetLanguage(obs_script_lang);
|
||||||
|
obs_script_lang GetLanguage() const { return _language; }
|
||||||
|
void SetText(const std::string &);
|
||||||
|
const StringVariable &GetText() const;
|
||||||
|
void SetPath(const std::string &);
|
||||||
|
const std::string &GetPath() const { return _file; }
|
||||||
|
|
||||||
|
bool Run();
|
||||||
|
|
||||||
|
void ResolveVariablesToFixedValues();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Setup();
|
||||||
|
void SetupFile();
|
||||||
|
void SetupInline();
|
||||||
|
std::string GetID() const;
|
||||||
|
|
||||||
|
Type _type = INLINE;
|
||||||
|
obs_script_lang _language = OBS_SCRIPT_LANG_PYTHON;
|
||||||
|
std::string _file;
|
||||||
|
StringVariable _textPython = _defaultPythonScript.data();
|
||||||
|
StringVariable _textLUA = _defaultLUAScript.data();
|
||||||
|
std::string _lastResolvedText;
|
||||||
|
std::string _lastPath;
|
||||||
|
std::string _fileId;
|
||||||
|
const uint64_t _instanceId;
|
||||||
|
|
||||||
|
struct ScriptDeleter {
|
||||||
|
void operator()(obs_script_t *);
|
||||||
|
std::string tempScriptPath;
|
||||||
|
};
|
||||||
|
std::unique_ptr<obs_script_t, ScriptDeleter> _script;
|
||||||
|
|
||||||
|
static std::atomic_uint64_t _instanceIdCounter;
|
||||||
|
static const std::string_view _defaultPythonScript;
|
||||||
|
static const std::string_view _defaultLUAScript;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScriptEditor : public VariableTextEdit {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ScriptEditor(QWidget *parent);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void ScriptChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
72
plugins/scripting/utils/obs-script-helpers.cpp
Normal file
72
plugins/scripting/utils/obs-script-helpers.cpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#include "obs-script-helpers.hpp"
|
||||||
|
#include "log-helper.hpp"
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QLibrary>
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
static const char *libName =
|
||||||
|
#if defined(WIN32)
|
||||||
|
"obs-scripting.dll";
|
||||||
|
#elif __APPLE__
|
||||||
|
"obs-scripting.dylib";
|
||||||
|
#else
|
||||||
|
"obs-scripting.so";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef obs_script_t *(*obs_script_create_t)(const char *, obs_data_t *);
|
||||||
|
typedef void (*obs_script_destroy_t)(obs_script_t *);
|
||||||
|
|
||||||
|
obs_script_create_t obs_script_create = nullptr;
|
||||||
|
obs_script_destroy_t obs_script_destroy = nullptr;
|
||||||
|
|
||||||
|
static bool setup()
|
||||||
|
{
|
||||||
|
QLibrary scriptingLib(libName);
|
||||||
|
|
||||||
|
obs_script_create =
|
||||||
|
(obs_script_create_t)scriptingLib.resolve("obs_script_create");
|
||||||
|
if (!obs_script_create) {
|
||||||
|
blog(LOG_WARNING,
|
||||||
|
"could not resolve obs_script_create symbol!");
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_script_destroy = (obs_script_destroy_t)scriptingLib.resolve(
|
||||||
|
"obs_script_destroy");
|
||||||
|
if (!obs_script_destroy) {
|
||||||
|
blog(LOG_WARNING,
|
||||||
|
"could not resolve obs_script_destroy symbol!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool setupDone = setup();
|
||||||
|
|
||||||
|
obs_script_t *CreateOBSScript(const char *path, obs_data_t *settings)
|
||||||
|
{
|
||||||
|
if (!obs_script_create) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return obs_script_create(path, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyOBSScript(obs_script_t *script)
|
||||||
|
{
|
||||||
|
if (!obs_script_destroy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
obs_script_destroy(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetLUACompatiblePath(const std::string &path)
|
||||||
|
{
|
||||||
|
// Can't use settingsFile here as LUA will complain if Windows style
|
||||||
|
// paths (C:\some\path) are used.
|
||||||
|
// QFileInfo::absoluteFilePath will convert those paths so LUA won't
|
||||||
|
// complain. (C:/some/path)
|
||||||
|
const QFileInfo fileInfo(QString::fromStdString(path));
|
||||||
|
return fileInfo.absoluteFilePath().toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
23
plugins/scripting/utils/obs-script-helpers.hpp
Normal file
23
plugins/scripting/utils/obs-script-helpers.hpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <obs-data.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Based on obs-scripting.h
|
||||||
|
|
||||||
|
struct obs_script;
|
||||||
|
typedef struct obs_script obs_script_t;
|
||||||
|
|
||||||
|
enum obs_script_lang {
|
||||||
|
OBS_SCRIPT_LANG_UNKNOWN,
|
||||||
|
OBS_SCRIPT_LANG_LUA,
|
||||||
|
OBS_SCRIPT_LANG_PYTHON
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
obs_script_t *CreateOBSScript(const char *path, obs_data_t *settings);
|
||||||
|
void DestroyOBSScript(obs_script_t *script);
|
||||||
|
std::string GetLUACompatiblePath(const std::string &path);
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
@@ -118,12 +118,17 @@ static void parseTags(const std::string &tags, IRCMessage &message)
|
|||||||
message.properties.timestamp = std::stoull(tagValue);
|
message.properties.timestamp = std::stoull(tagValue);
|
||||||
} else if (tagName == "turbo") {
|
} else if (tagName == "turbo") {
|
||||||
message.properties.isTurbo = tagValue == "1";
|
message.properties.isTurbo = tagValue == "1";
|
||||||
} else if (tagName == "user-id") {
|
} else if (tagName == "user-id" ||
|
||||||
|
tagName == "target-user-id" || tagName == "login") {
|
||||||
message.properties.userId = tagValue;
|
message.properties.userId = tagValue;
|
||||||
} else if (tagName == "user-type") {
|
} else if (tagName == "user-type") {
|
||||||
message.properties.userType = tagValue;
|
message.properties.userType = tagValue;
|
||||||
} else if (tagName == "vip") {
|
} else if (tagName == "vip") {
|
||||||
message.properties.isVIP = tagValue == "1";
|
message.properties.isVIP = tagValue == "1";
|
||||||
|
} else if (tagName == "ban-duration") {
|
||||||
|
message.properties.banDuration = std::stoull(tagValue);
|
||||||
|
} else if (tagName == "target-msg-id") {
|
||||||
|
message.properties.id = tagValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,34 +177,54 @@ static void parseCommand(const std::string &rawCommandComponent,
|
|||||||
if (commandParts.size() < 3) {
|
if (commandParts.size() < 3) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
message.type = IRCMessage::Type::OTHER;
|
||||||
message.command.parameters = (commandParts[2] == "ACK");
|
message.command.parameters = (commandParts[2] == "ACK");
|
||||||
} else if (message.command.command == "RECONNECT") {
|
} else if (message.command.command == "RECONNECT") {
|
||||||
blog(LOG_INFO,
|
blog(LOG_INFO,
|
||||||
"The Twitch IRC server is about to terminate the connection for maintenance.");
|
"The Twitch IRC server is about to terminate the connection for maintenance.");
|
||||||
|
message.type = IRCMessage::Type::OTHER;
|
||||||
} else if (message.command.command == "421") {
|
} else if (message.command.command == "421") {
|
||||||
// Unsupported command
|
// Unsupported command
|
||||||
|
message.type = IRCMessage::Type::OTHER;
|
||||||
return;
|
return;
|
||||||
} else if (message.command.command == "PING" ||
|
} else if (message.command.command == "PRIVMSG") {
|
||||||
message.command.command == "001" ||
|
|
||||||
message.command.command == "NOTICE" ||
|
|
||||||
message.command.command == "CLEARCHAT" ||
|
|
||||||
message.command.command == "HOSTTARGET" ||
|
|
||||||
message.command.command == "PRIVMSG") {
|
|
||||||
if (commandParts.size() < 2) {
|
if (commandParts.size() < 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
message.type = IRCMessage::Type::MESSAGE_RECEIVED;
|
||||||
|
message.command.parameters = commandParts[1];
|
||||||
|
} else if (message.command.command == "CLEARCHAT") {
|
||||||
|
if (commandParts.size() < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.type = IRCMessage::Type::MESSAGE_CLEARED;
|
||||||
|
message.command.parameters = commandParts[1];
|
||||||
|
} else if (message.command.command == "CLEARMSG") {
|
||||||
|
if (commandParts.size() < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.type = IRCMessage::Type::MESSAGE_REMOVED;
|
||||||
|
message.command.parameters = commandParts[1];
|
||||||
|
} else if (message.command.command == "PING" ||
|
||||||
|
message.command.command == "001" ||
|
||||||
|
message.command.command == "NOTICE" ||
|
||||||
|
message.command.command == "HOSTTARGET") {
|
||||||
|
if (commandParts.size() < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.type = IRCMessage::Type::OTHER;
|
||||||
message.command.parameters = commandParts[1];
|
message.command.parameters = commandParts[1];
|
||||||
} else if (message.command.command == "JOIN") {
|
} else if (message.command.command == "JOIN") {
|
||||||
if (commandParts.size() < 2) {
|
if (commandParts.size() < 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
message.properties.joinedChannel = true;
|
message.type = IRCMessage::Type::USER_JOIN;
|
||||||
message.command.parameters = commandParts[1];
|
message.command.parameters = commandParts[1];
|
||||||
} else if (message.command.command == "PART") {
|
} else if (message.command.command == "PART") {
|
||||||
if (commandParts.size() < 2) {
|
if (commandParts.size() < 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
message.properties.leftChannel = true;
|
message.type = IRCMessage::Type::USER_LEAVE;
|
||||||
message.command.parameters = commandParts[1];
|
message.command.parameters = commandParts[1];
|
||||||
} else if (message.command.command == "GLOBALUSERSTATE" ||
|
} else if (message.command.command == "GLOBALUSERSTATE" ||
|
||||||
message.command.command == "USERSTATE" ||
|
message.command.command == "USERSTATE" ||
|
||||||
@@ -213,7 +238,9 @@ static void parseCommand(const std::string &rawCommandComponent,
|
|||||||
message.command.command == "375" ||
|
message.command.command == "375" ||
|
||||||
message.command.command == "376") {
|
message.command.command == "376") {
|
||||||
// Do nothing for these cases for now
|
// Do nothing for these cases for now
|
||||||
|
message.type = IRCMessage::Type::OTHER;
|
||||||
} else {
|
} else {
|
||||||
|
message.type = IRCMessage::Type::UNKNOWN;
|
||||||
vblog(LOG_INFO, "Unexpected IRC command: %s",
|
vblog(LOG_INFO, "Unexpected IRC command: %s",
|
||||||
message.command.command.c_str());
|
message.command.command.c_str());
|
||||||
}
|
}
|
||||||
@@ -501,6 +528,7 @@ void TwitchChatConnection::Authenticate()
|
|||||||
if (!pass.has_value()) {
|
if (!pass.has_value()) {
|
||||||
blog(LOG_INFO,
|
blog(LOG_INFO,
|
||||||
"Joining Twitch chat failed due to invalid token!");
|
"Joining Twitch chat failed due to invalid token!");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
Send("PASS oauth:" + *pass);
|
Send("PASS oauth:" + *pass);
|
||||||
Send("NICK " + _token.GetName());
|
Send("NICK " + _token.GetName());
|
||||||
@@ -549,6 +577,18 @@ void TwitchChatConnection::HandleNewMessage(const IRCMessage &message)
|
|||||||
message.message.c_str());
|
message.message.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TwitchChatConnection::HandleRemoveMessage(const IRCMessage &message)
|
||||||
|
{
|
||||||
|
_messageDispatcher.DispatchMessage(message);
|
||||||
|
vblog(LOG_INFO, "Chat message was removed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchChatConnection::HandleClear(const IRCMessage &message)
|
||||||
|
{
|
||||||
|
_messageDispatcher.DispatchMessage(message);
|
||||||
|
vblog(LOG_INFO, "Chat was cleared");
|
||||||
|
}
|
||||||
|
|
||||||
void TwitchChatConnection::HandleWhisper(const IRCMessage &message)
|
void TwitchChatConnection::HandleWhisper(const IRCMessage &message)
|
||||||
{
|
{
|
||||||
_whisperDispatcher.DispatchMessage(message);
|
_whisperDispatcher.DispatchMessage(message);
|
||||||
@@ -609,6 +649,8 @@ void TwitchChatConnection::OnMessage(
|
|||||||
static constexpr std::string_view reconnectCommand = "RECONNECT";
|
static constexpr std::string_view reconnectCommand = "RECONNECT";
|
||||||
static constexpr std::string_view newMessageCommand = "PRIVMSG";
|
static constexpr std::string_view newMessageCommand = "PRIVMSG";
|
||||||
static constexpr std::string_view whisperCommand = "WHISPER";
|
static constexpr std::string_view whisperCommand = "WHISPER";
|
||||||
|
static constexpr std::string_view clearCommand = "CLEARCHAT";
|
||||||
|
static constexpr std::string_view removeCommand = "CLEARMSG";
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return;
|
return;
|
||||||
@@ -635,6 +677,10 @@ void TwitchChatConnection::OnMessage(
|
|||||||
HandlePart(message);
|
HandlePart(message);
|
||||||
} else if (message.command.command == newMessageCommand) {
|
} else if (message.command.command == newMessageCommand) {
|
||||||
HandleNewMessage(message);
|
HandleNewMessage(message);
|
||||||
|
} else if (message.command.command == clearCommand) {
|
||||||
|
HandleClear(message);
|
||||||
|
} else if (message.command.command == removeCommand) {
|
||||||
|
HandleRemoveMessage(message);
|
||||||
} else if (message.command.command == whisperCommand) {
|
} else if (message.command.command == whisperCommand) {
|
||||||
HandleWhisper(message);
|
HandleWhisper(message);
|
||||||
} else if (message.command.command == noticeCommand) {
|
} else if (message.command.command == noticeCommand) {
|
||||||
|
|||||||
@@ -15,10 +15,23 @@ namespace advss {
|
|||||||
using websocketpp::connection_hdl;
|
using websocketpp::connection_hdl;
|
||||||
|
|
||||||
struct IRCMessage {
|
struct IRCMessage {
|
||||||
|
enum class Type {
|
||||||
|
MESSAGE_RECEIVED,
|
||||||
|
MESSAGE_REMOVED,
|
||||||
|
MESSAGE_CLEARED,
|
||||||
|
USER_LEAVE,
|
||||||
|
USER_JOIN,
|
||||||
|
OTHER,
|
||||||
|
UNKNOWN,
|
||||||
|
};
|
||||||
|
|
||||||
|
Type type = Type::UNKNOWN;
|
||||||
|
|
||||||
struct Badge {
|
struct Badge {
|
||||||
std::string name;
|
std::string name;
|
||||||
bool enabled;
|
bool enabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
std::string badgeInfoString;
|
std::string badgeInfoString;
|
||||||
std::string badgesString;
|
std::string badgesString;
|
||||||
@@ -39,13 +52,12 @@ struct IRCMessage {
|
|||||||
std::string rootParentId;
|
std::string rootParentId;
|
||||||
std::string rootParentUserLogin;
|
std::string rootParentUserLogin;
|
||||||
bool isSubscriber = false;
|
bool isSubscriber = false;
|
||||||
unsigned long long timestamp;
|
unsigned long long timestamp = 0;
|
||||||
|
unsigned long long banDuration = 0;
|
||||||
bool isTurbo = false;
|
bool isTurbo = false;
|
||||||
std::string userId;
|
std::string userId;
|
||||||
std::string userType;
|
std::string userType;
|
||||||
bool isVIP = false;
|
bool isVIP = false;
|
||||||
bool joinedChannel = false;
|
|
||||||
bool leftChannel = false;
|
|
||||||
} properties;
|
} properties;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
@@ -99,6 +111,8 @@ private:
|
|||||||
void HandleJoin(const IRCMessage &);
|
void HandleJoin(const IRCMessage &);
|
||||||
void HandlePart(const IRCMessage &);
|
void HandlePart(const IRCMessage &);
|
||||||
void HandleNewMessage(const IRCMessage &);
|
void HandleNewMessage(const IRCMessage &);
|
||||||
|
void HandleRemoveMessage(const IRCMessage &);
|
||||||
|
void HandleClear(const IRCMessage &);
|
||||||
void HandleWhisper(const IRCMessage &);
|
void HandleWhisper(const IRCMessage &);
|
||||||
void HandleNotice(const IRCMessage &) const;
|
void HandleNotice(const IRCMessage &) const;
|
||||||
void HandleReconnect();
|
void HandleReconnect();
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ void MacroActionTwitch::GetUserInfo(const std::shared_ptr<TwitchToken> &token)
|
|||||||
httplib::Params params;
|
httplib::Params params;
|
||||||
switch (_userInfoQueryType) {
|
switch (_userInfoQueryType) {
|
||||||
case UserInfoQueryType::ID:
|
case UserInfoQueryType::ID:
|
||||||
params.insert({"id", std::to_string(_userId)});
|
params.insert({"id", std::to_string((uint64_t)_userId)});
|
||||||
break;
|
break;
|
||||||
case UserInfoQueryType::LOGIN:
|
case UserInfoQueryType::LOGIN:
|
||||||
params.insert({"login", _userLogin});
|
params.insert({"login", _userLogin});
|
||||||
@@ -825,7 +825,7 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
|
|||||||
_chatMessage(new VariableTextEdit(this)),
|
_chatMessage(new VariableTextEdit(this)),
|
||||||
_userInfoQueryType(new QComboBox(this)),
|
_userInfoQueryType(new QComboBox(this)),
|
||||||
_userLogin(new VariableLineEdit(this)),
|
_userLogin(new VariableLineEdit(this)),
|
||||||
_userId(new VariableSpinBox(this)),
|
_userId(new VariableDoubleSpinBox(this)),
|
||||||
_pointsReward(new TwitchPointsRewardWidget(this, false)),
|
_pointsReward(new TwitchPointsRewardWidget(this, false)),
|
||||||
_rewardVariable(new VariableSelection(this)),
|
_rewardVariable(new VariableSelection(this)),
|
||||||
_toggleRewardSelection(new QPushButton())
|
_toggleRewardSelection(new QPushButton())
|
||||||
@@ -984,7 +984,8 @@ void MacroActionTwitchEdit::SetWidgetProperties()
|
|||||||
populateAnnouncementColorSelection(_announcementColor);
|
populateAnnouncementColorSelection(_announcementColor);
|
||||||
populateUserQueryInfoTypeSelection(_userInfoQueryType);
|
populateUserQueryInfoTypeSelection(_userInfoQueryType);
|
||||||
|
|
||||||
_userId->setMaximum(999999999);
|
_userId->setMaximum(999999999999999);
|
||||||
|
_userId->setDecimals(0);
|
||||||
|
|
||||||
_toggleRewardSelection->setCheckable(true);
|
_toggleRewardSelection->setCheckable(true);
|
||||||
_toggleRewardSelection->setMaximumWidth(11);
|
_toggleRewardSelection->setMaximumWidth(11);
|
||||||
@@ -1030,8 +1031,8 @@ void MacroActionTwitchEdit::SetWidgetSignalConnections()
|
|||||||
SLOT(UserLoginChanged()));
|
SLOT(UserLoginChanged()));
|
||||||
QWidget::connect(
|
QWidget::connect(
|
||||||
_userId,
|
_userId,
|
||||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
|
||||||
this, SLOT(UserIdChanged(const NumberVariable<int> &)));
|
this, SLOT(UserIdChanged(const NumberVariable<double> &)));
|
||||||
QWidget::connect(
|
QWidget::connect(
|
||||||
_pointsReward,
|
_pointsReward,
|
||||||
SIGNAL(PointsRewardChanged(const TwitchPointsReward &)), this,
|
SIGNAL(PointsRewardChanged(const TwitchPointsReward &)), this,
|
||||||
@@ -1160,7 +1161,7 @@ void MacroActionTwitchEdit::ToggleRewardSelection(bool)
|
|||||||
SetWidgetVisibility();
|
SetWidgetVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroActionTwitchEdit::UserIdChanged(const NumberVariable<int> &value)
|
void MacroActionTwitchEdit::UserIdChanged(const NumberVariable<double> &value)
|
||||||
{
|
{
|
||||||
GUARD_LOADING_AND_LOCK();
|
GUARD_LOADING_AND_LOCK();
|
||||||
_entryData->_userId = value;
|
_entryData->_userId = value;
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ public:
|
|||||||
StringVariable _chatMessage;
|
StringVariable _chatMessage;
|
||||||
UserInfoQueryType _userInfoQueryType = UserInfoQueryType::LOGIN;
|
UserInfoQueryType _userInfoQueryType = UserInfoQueryType::LOGIN;
|
||||||
StringVariable _userLogin = "user login";
|
StringVariable _userLogin = "user login";
|
||||||
IntVariable _userId = 0;
|
DoubleVariable _userId = 0;
|
||||||
TwitchPointsReward _pointsReward;
|
TwitchPointsReward _pointsReward;
|
||||||
std::weak_ptr<Variable> _rewardVariable;
|
std::weak_ptr<Variable> _rewardVariable;
|
||||||
bool _useVariableForRewardSelection = false;
|
bool _useVariableForRewardSelection = false;
|
||||||
@@ -244,7 +244,7 @@ private slots:
|
|||||||
void ChatMessageChanged();
|
void ChatMessageChanged();
|
||||||
void UserInfoQueryTypeChanged(int);
|
void UserInfoQueryTypeChanged(int);
|
||||||
void UserLoginChanged();
|
void UserLoginChanged();
|
||||||
void UserIdChanged(const NumberVariable<int> &);
|
void UserIdChanged(const NumberVariable<double> &);
|
||||||
void PointsRewardChanged(const TwitchPointsReward &);
|
void PointsRewardChanged(const TwitchPointsReward &);
|
||||||
void RewardVariableChanged(const QString &);
|
void RewardVariableChanged(const QString &);
|
||||||
void ToggleRewardSelection(bool);
|
void ToggleRewardSelection(bool);
|
||||||
@@ -278,7 +278,9 @@ private:
|
|||||||
VariableTextEdit *_chatMessage;
|
VariableTextEdit *_chatMessage;
|
||||||
QComboBox *_userInfoQueryType;
|
QComboBox *_userInfoQueryType;
|
||||||
VariableLineEdit *_userLogin;
|
VariableLineEdit *_userLogin;
|
||||||
VariableSpinBox *_userId;
|
// QSpinBox uses int internally, which is too small for Twitch IDs, so
|
||||||
|
// we use QDoubleSpinBox instead
|
||||||
|
VariableDoubleSpinBox *_userId;
|
||||||
TwitchPointsRewardWidget *_pointsReward;
|
TwitchPointsRewardWidget *_pointsReward;
|
||||||
VariableSelection *_rewardVariable;
|
VariableSelection *_rewardVariable;
|
||||||
QPushButton *_toggleRewardSelection;
|
QPushButton *_toggleRewardSelection;
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ const static std::map<MacroConditionTwitch::Condition, std::string> conditionTyp
|
|||||||
"AdvSceneSwitcher.condition.twitch.type.polling.channel.category"},
|
"AdvSceneSwitcher.condition.twitch.type.polling.channel.category"},
|
||||||
{MacroConditionTwitch::Condition::CHAT_MESSAGE_RECEIVED,
|
{MacroConditionTwitch::Condition::CHAT_MESSAGE_RECEIVED,
|
||||||
"AdvSceneSwitcher.condition.twitch.type.chat.message"},
|
"AdvSceneSwitcher.condition.twitch.type.chat.message"},
|
||||||
|
{MacroConditionTwitch::Condition::CHAT_MESSAGE_REMOVED,
|
||||||
|
"AdvSceneSwitcher.condition.twitch.type.chat.messageRemoved"},
|
||||||
|
{MacroConditionTwitch::Condition::CHAT_CLEARED,
|
||||||
|
"AdvSceneSwitcher.condition.twitch.type.chat.cleared"},
|
||||||
{MacroConditionTwitch::Condition::CHAT_USER_JOINED,
|
{MacroConditionTwitch::Condition::CHAT_USER_JOINED,
|
||||||
"AdvSceneSwitcher.condition.twitch.type.chat.userJoined"},
|
"AdvSceneSwitcher.condition.twitch.type.chat.userJoined"},
|
||||||
{MacroConditionTwitch::Condition::CHAT_USER_LEFT,
|
{MacroConditionTwitch::Condition::CHAT_USER_LEFT,
|
||||||
@@ -416,87 +420,128 @@ static bool stringMatches(const RegexConfig ®ex, const std::string &string,
|
|||||||
|
|
||||||
bool MacroConditionTwitch::CheckChatMessages(TwitchToken &token)
|
bool MacroConditionTwitch::CheckChatMessages(TwitchToken &token)
|
||||||
{
|
{
|
||||||
if (!_chatConnection) {
|
if (!ChatConnectionIsSetup(token)) {
|
||||||
_chatConnection = TwitchChatConnection::GetChatConnection(
|
|
||||||
token, _channel);
|
|
||||||
if (!_chatConnection) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_chatBuffer = _chatConnection->RegisterForMessages();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!_chatBuffer->Empty()) {
|
return HandleChatEvents([this](const IRCMessage &message) -> bool {
|
||||||
auto message = _chatBuffer->ConsumeMessage();
|
if (message.type != IRCMessage::Type::MESSAGE_RECEIVED) {
|
||||||
if (!message) {
|
return false;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join and leave message don't have any message data
|
if (!_chatMessagePattern.Matches(message)) {
|
||||||
if (message->properties.leftChannel ||
|
return false;
|
||||||
message->properties.joinedChannel) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_chatMessagePattern.Matches(*message)) {
|
SetTempVarValue("id", message.properties.id);
|
||||||
continue;
|
SetTempVarValue("chat_message", message.message);
|
||||||
}
|
SetTempVarValue("user_id", message.properties.userId);
|
||||||
|
SetTempVarValue("user_login", message.source.nick);
|
||||||
SetTempVarValue("id", message->properties.id);
|
SetTempVarValue("user_name", message.properties.displayName);
|
||||||
SetTempVarValue("chat_message", message->message);
|
SetTempVarValue("user_type", message.properties.userType);
|
||||||
SetTempVarValue("user_id", message->properties.userId);
|
|
||||||
SetTempVarValue("user_login", message->source.nick);
|
|
||||||
SetTempVarValue("user_name", message->properties.displayName);
|
|
||||||
SetTempVarValue("user_type", message->properties.userType);
|
|
||||||
SetTempVarValue("reply_parent_id",
|
SetTempVarValue("reply_parent_id",
|
||||||
message->properties.replyParentId);
|
message.properties.replyParentId);
|
||||||
SetTempVarValue("reply_parent_message",
|
SetTempVarValue("reply_parent_message",
|
||||||
message->properties.replyParentBody);
|
message.properties.replyParentBody);
|
||||||
SetTempVarValue("reply_parent_user_id",
|
SetTempVarValue("reply_parent_user_id",
|
||||||
message->properties.replyParentUserId);
|
message.properties.replyParentUserId);
|
||||||
SetTempVarValue("reply_parent_user_login",
|
SetTempVarValue("reply_parent_user_login",
|
||||||
message->properties.replyParentUserLogin);
|
message.properties.replyParentUserLogin);
|
||||||
SetTempVarValue("reply_parent_user_name",
|
SetTempVarValue("reply_parent_user_name",
|
||||||
message->properties.replyParentDisplayName);
|
message.properties.replyParentDisplayName);
|
||||||
SetTempVarValue("root_parent_id",
|
SetTempVarValue("root_parent_id",
|
||||||
message->properties.rootParentId);
|
message.properties.rootParentId);
|
||||||
SetTempVarValue("root_parent_user_login",
|
SetTempVarValue("root_parent_user_login",
|
||||||
message->properties.rootParentUserLogin);
|
message.properties.rootParentUserLogin);
|
||||||
SetTempVarValue("badge_info",
|
SetTempVarValue("badge_info",
|
||||||
message->properties.badgeInfoString);
|
message.properties.badgeInfoString);
|
||||||
SetTempVarValue("badges", message->properties.badgesString);
|
SetTempVarValue("badges", message.properties.badgesString);
|
||||||
SetTempVarValue("bits",
|
SetTempVarValue("bits",
|
||||||
std::to_string(message->properties.bits));
|
std::to_string(message.properties.bits));
|
||||||
SetTempVarValue("color", message->properties.color);
|
SetTempVarValue("color", message.properties.color);
|
||||||
SetTempVarValue("emotes", message->properties.emotesString);
|
SetTempVarValue("emotes", message.properties.emotesString);
|
||||||
SetTempVarValue("timestamp",
|
SetTempVarValue("timestamp",
|
||||||
std::to_string(message->properties.timestamp));
|
std::to_string(message.properties.timestamp));
|
||||||
SetTempVarValue("is_emotes_only",
|
SetTempVarValue("is_emotes_only",
|
||||||
message->properties.isUsingOnlyEmotes
|
message.properties.isUsingOnlyEmotes ? "true"
|
||||||
? "true"
|
: "false");
|
||||||
: "false");
|
|
||||||
SetTempVarValue("is_first_message",
|
SetTempVarValue("is_first_message",
|
||||||
message->properties.isFirstMessage ? "true"
|
message.properties.isFirstMessage ? "true"
|
||||||
: "false");
|
: "false");
|
||||||
SetTempVarValue("is_mod",
|
SetTempVarValue("is_mod",
|
||||||
message->properties.isMod ? "true" : "false");
|
message.properties.isMod ? "true" : "false");
|
||||||
SetTempVarValue("is_subscriber",
|
SetTempVarValue("is_subscriber", message.properties.isSubscriber
|
||||||
message->properties.isSubscriber ? "true"
|
? "true"
|
||||||
: "false");
|
: "false");
|
||||||
SetTempVarValue("is_turbo",
|
SetTempVarValue("is_turbo",
|
||||||
message->properties.isTurbo ? "true" : "false");
|
message.properties.isTurbo ? "true" : "false");
|
||||||
SetTempVarValue("is_vip",
|
SetTempVarValue("is_vip",
|
||||||
message->properties.isVIP ? "true" : "false");
|
message.properties.isVIP ? "true" : "false");
|
||||||
|
|
||||||
if (_clearBufferOnMatch) {
|
|
||||||
_chatBuffer->Clear();
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
});
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroConditionTwitch::CheckChatUserJoinOrLeave(TwitchToken &token)
|
bool MacroConditionTwitch::CheckChatUserJoinOrLeave(TwitchToken &token)
|
||||||
|
{
|
||||||
|
if (!ChatConnectionIsSetup(token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HandleChatEvents([this](const IRCMessage &message) -> bool {
|
||||||
|
if ((_condition == Condition::CHAT_USER_JOINED &&
|
||||||
|
message.type != IRCMessage::Type::USER_JOIN) ||
|
||||||
|
(_condition == Condition::CHAT_USER_LEFT &&
|
||||||
|
message.type != IRCMessage::Type::USER_LEAVE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetTempVarValue("user_login", message.source.nick);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacroConditionTwitch::CheckChatClear(TwitchToken &token)
|
||||||
|
{
|
||||||
|
if (!ChatConnectionIsSetup(token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HandleChatEvents([this](const IRCMessage &message) -> bool {
|
||||||
|
if (message.type != IRCMessage::Type::MESSAGE_CLEARED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetTempVarValue("ban_duration",
|
||||||
|
std::to_string(message.properties.banDuration));
|
||||||
|
SetTempVarValue("login", message.message);
|
||||||
|
SetTempVarValue("user_id", message.properties.userId);
|
||||||
|
SetTempVarValue("timestamp",
|
||||||
|
std::to_string(message.properties.timestamp));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacroConditionTwitch::CheckChatMessageRemove(TwitchToken &token)
|
||||||
|
{
|
||||||
|
if (!ChatConnectionIsSetup(token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HandleChatEvents([this](const IRCMessage &message) -> bool {
|
||||||
|
if (message.type != IRCMessage::Type::MESSAGE_REMOVED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetTempVarValue("message", message.message);
|
||||||
|
SetTempVarValue("message_id", message.properties.id);
|
||||||
|
SetTempVarValue("login", message.properties.userId);
|
||||||
|
SetTempVarValue("timestamp",
|
||||||
|
std::to_string(message.properties.timestamp));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacroConditionTwitch::ChatConnectionIsSetup(TwitchToken &token)
|
||||||
{
|
{
|
||||||
if (!_chatConnection) {
|
if (!_chatConnection) {
|
||||||
_chatConnection = TwitchChatConnection::GetChatConnection(
|
_chatConnection = TwitchChatConnection::GetChatConnection(
|
||||||
@@ -507,22 +552,22 @@ bool MacroConditionTwitch::CheckChatUserJoinOrLeave(TwitchToken &token)
|
|||||||
_chatBuffer = _chatConnection->RegisterForMessages();
|
_chatBuffer = _chatConnection->RegisterForMessages();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacroConditionTwitch::HandleChatEvents(
|
||||||
|
const std::function<bool(const IRCMessage &)> &matchCb)
|
||||||
|
{
|
||||||
while (!_chatBuffer->Empty()) {
|
while (!_chatBuffer->Empty()) {
|
||||||
auto message = _chatBuffer->ConsumeMessage();
|
auto message = _chatBuffer->ConsumeMessage();
|
||||||
if (!message) {
|
if (!message) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((_condition == Condition::CHAT_USER_JOINED &&
|
if (!matchCb(*message)) {
|
||||||
!message->properties.joinedChannel) ||
|
|
||||||
(_condition == Condition::CHAT_USER_LEFT &&
|
|
||||||
!message->properties.leftChannel)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetTempVarValue("user_login", message->source.nick);
|
|
||||||
|
|
||||||
if (_clearBufferOnMatch) {
|
if (_clearBufferOnMatch) {
|
||||||
_chatBuffer->Clear();
|
_chatBuffer->Clear();
|
||||||
}
|
}
|
||||||
@@ -715,6 +760,20 @@ bool MacroConditionTwitch::CheckCondition()
|
|||||||
}
|
}
|
||||||
return CheckChatMessages(*token);
|
return CheckChatMessages(*token);
|
||||||
}
|
}
|
||||||
|
case Condition::CHAT_CLEARED: {
|
||||||
|
auto token = _token.lock();
|
||||||
|
if (!token) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return CheckChatClear(*token);
|
||||||
|
}
|
||||||
|
case Condition::CHAT_MESSAGE_REMOVED: {
|
||||||
|
auto token = _token.lock();
|
||||||
|
if (!token) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return CheckChatMessageRemove(*token);
|
||||||
|
}
|
||||||
case Condition::CHAT_USER_JOINED:
|
case Condition::CHAT_USER_JOINED:
|
||||||
case Condition::CHAT_USER_LEFT: {
|
case Condition::CHAT_USER_LEFT: {
|
||||||
auto token = _token.lock();
|
auto token = _token.lock();
|
||||||
@@ -871,6 +930,10 @@ bool MacroConditionTwitch::ConditionIsSupportedByToken()
|
|||||||
{Condition::CATEGORY_POLLING, {}},
|
{Condition::CATEGORY_POLLING, {}},
|
||||||
{Condition::CHAT_MESSAGE_RECEIVED,
|
{Condition::CHAT_MESSAGE_RECEIVED,
|
||||||
{{"chat:read"}, {"chat:edit"}}},
|
{{"chat:read"}, {"chat:edit"}}},
|
||||||
|
{Condition::CHAT_MESSAGE_REMOVED,
|
||||||
|
{{"chat:read"}, {"chat:edit"}}},
|
||||||
|
{Condition::CHAT_CLEARED,
|
||||||
|
{{"chat:read"}, {"chat:edit"}}},
|
||||||
{Condition::CHAT_USER_JOINED,
|
{Condition::CHAT_USER_JOINED,
|
||||||
{{"chat:read"}, {"chat:edit"}}},
|
{{"chat:read"}, {"chat:edit"}}},
|
||||||
{Condition::CHAT_USER_LEFT,
|
{Condition::CHAT_USER_LEFT,
|
||||||
@@ -1069,6 +1132,8 @@ void MacroConditionTwitch::SetupTempVars()
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (_condition != Condition::CHAT_MESSAGE_RECEIVED &&
|
if (_condition != Condition::CHAT_MESSAGE_RECEIVED &&
|
||||||
|
_condition != Condition::CHAT_MESSAGE_REMOVED &&
|
||||||
|
_condition != Condition::CHAT_CLEARED &&
|
||||||
_condition != Condition::CHAT_USER_JOINED &&
|
_condition != Condition::CHAT_USER_JOINED &&
|
||||||
_condition != Condition::CHAT_USER_LEFT &&
|
_condition != Condition::CHAT_USER_LEFT &&
|
||||||
_condition != Condition::RAID_INBOUND_EVENT &&
|
_condition != Condition::RAID_INBOUND_EVENT &&
|
||||||
@@ -1423,6 +1488,18 @@ void MacroConditionTwitch::SetupTempVars()
|
|||||||
setupTempVarHelper("is_turbo", ".chatReceive");
|
setupTempVarHelper("is_turbo", ".chatReceive");
|
||||||
setupTempVarHelper("is_vip", ".chatReceive");
|
setupTempVarHelper("is_vip", ".chatReceive");
|
||||||
break;
|
break;
|
||||||
|
case Condition::CHAT_MESSAGE_REMOVED:
|
||||||
|
setupTempVarHelper("message", ".chatRemove");
|
||||||
|
setupTempVarHelper("message_id", ".chatRemove");
|
||||||
|
setupTempVarHelper("login", ".chatRemove");
|
||||||
|
setupTempVarHelper("timestamp", ".chatRemove");
|
||||||
|
break;
|
||||||
|
case Condition::CHAT_CLEARED:
|
||||||
|
setupTempVarHelper("ban_duration", ".chatClear");
|
||||||
|
setupTempVarHelper("login", ".chatClear");
|
||||||
|
setupTempVarHelper("user_id", ".chatClear");
|
||||||
|
setupTempVarHelper("timestamp", ".chatClear");
|
||||||
|
break;
|
||||||
case Condition::CHAT_USER_JOINED:
|
case Condition::CHAT_USER_JOINED:
|
||||||
setupTempVarHelper("user_login", ".chatJoin");
|
setupTempVarHelper("user_login", ".chatJoin");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ public:
|
|||||||
|
|
||||||
// Chat
|
// Chat
|
||||||
CHAT_MESSAGE_RECEIVED = 500000,
|
CHAT_MESSAGE_RECEIVED = 500000,
|
||||||
|
CHAT_MESSAGE_REMOVED = 500030,
|
||||||
|
CHAT_CLEARED = 500060,
|
||||||
CHAT_USER_JOINED = 500100,
|
CHAT_USER_JOINED = 500100,
|
||||||
CHAT_USER_LEFT = 500200,
|
CHAT_USER_LEFT = 500200,
|
||||||
|
|
||||||
@@ -113,10 +115,16 @@ private:
|
|||||||
bool CheckChannelLiveEvents();
|
bool CheckChannelLiveEvents();
|
||||||
bool CheckChannelRewardChangeEvents();
|
bool CheckChannelRewardChangeEvents();
|
||||||
bool CheckChannelRewardRedemptionEvents();
|
bool CheckChannelRewardRedemptionEvents();
|
||||||
bool CheckChatMessages(TwitchToken &token);
|
|
||||||
bool HandleMatchingSubscriptionEvents(
|
bool HandleMatchingSubscriptionEvents(
|
||||||
const std::function<void(const Event &)> &matchCb);
|
const std::function<void(const Event &)> &matchCb);
|
||||||
|
|
||||||
|
bool CheckChatMessages(TwitchToken &token);
|
||||||
bool CheckChatUserJoinOrLeave(TwitchToken &token);
|
bool CheckChatUserJoinOrLeave(TwitchToken &token);
|
||||||
|
bool CheckChatClear(TwitchToken &token);
|
||||||
|
bool CheckChatMessageRemove(TwitchToken &token);
|
||||||
|
bool ChatConnectionIsSetup(TwitchToken &token);
|
||||||
|
bool HandleChatEvents(
|
||||||
|
const std::function<bool(const IRCMessage &)> &matchCb);
|
||||||
|
|
||||||
void RegisterEventSubscription();
|
void RegisterEventSubscription();
|
||||||
void ResetSubscription();
|
void ResetSubscription();
|
||||||
|
|||||||
Reference in New Issue
Block a user