mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-04-21 01:27:24 -05:00
Add option to set Twitch stream tags
This commit is contained in:
parent
ea4a951554
commit
058e941a46
|
|
@ -1201,6 +1201,7 @@ AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}"
|
|||
AdvSceneSwitcher.action.twitch="Twitch"
|
||||
AdvSceneSwitcher.action.twitch.type.channel.info.title.set="Set stream title"
|
||||
AdvSceneSwitcher.action.twitch.type.channel.info.category.set="Set stream category"
|
||||
AdvSceneSwitcher.action.twitch.type.channel.info.tags.set="Set stream tags"
|
||||
AdvSceneSwitcher.action.twitch.type.raid.start="Start raid"
|
||||
AdvSceneSwitcher.action.twitch.type.commercial.start="Start commercial"
|
||||
AdvSceneSwitcher.action.twitch.type.marker.create="Create stream marker"
|
||||
|
|
@ -1228,6 +1229,14 @@ AdvSceneSwitcher.action.twitch.announcement.orange="Orange"
|
|||
AdvSceneSwitcher.action.twitch.announcement.purple="Purple"
|
||||
AdvSceneSwitcher.action.twitch.user.getInfo.queryType.id="User Id"
|
||||
AdvSceneSwitcher.action.twitch.user.getInfo.queryType.login="User login"
|
||||
AdvSceneSwitcher.action.twitch.tags.add="Add Channel Tag"
|
||||
AdvSceneSwitcher.action.twitch.tags.getCurrent="Get current channel tags"
|
||||
AdvSceneSwitcher.action.twitch.tags.invalid="Invalid Tag"
|
||||
AdvSceneSwitcher.action.twitch.tags.invalid.info="Tags must be 1–%1 characters long, contain only letters or numbers, and have no spaces or special characters."
|
||||
AdvSceneSwitcher.action.twitch.tags.duplicate="Duplicate Tag"
|
||||
AdvSceneSwitcher.action.twitch.tags.duplicate.info="This tag is already in the list."
|
||||
AdvSceneSwitcher.action.twitch.tags.limit="Tag Limit Reached"
|
||||
AdvSceneSwitcher.action.twitch.tags.limit.info="You can only have up to %1 tags."
|
||||
AdvSceneSwitcher.action.clipboard="Clipboard"
|
||||
AdvSceneSwitcher.action.clipboard.type.copy.text="Copy text"
|
||||
AdvSceneSwitcher.action.clipboard.type.copy.image="Copy image"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class ADVSS_EXPORT ListEditor : public QWidget {
|
|||
|
||||
public:
|
||||
ListEditor(QWidget *parent = nullptr, bool reorder = true);
|
||||
int count() const { return _list->count(); };
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *);
|
||||
|
|
|
|||
|
|
@ -46,14 +46,17 @@ void StringList::ResolveVariables()
|
|||
}
|
||||
}
|
||||
|
||||
StringListEdit::StringListEdit(QWidget *parent, const QString &addString,
|
||||
const QString &addStringDescription,
|
||||
int maxStringSize, bool allowEmtpy)
|
||||
StringListEdit::StringListEdit(
|
||||
QWidget *parent, const QString &addString,
|
||||
const QString &addStringDescription, int maxStringSize,
|
||||
const std::function<bool(const std::string &)> &filter,
|
||||
const std::function<void(std::string &input)> &preprocess)
|
||||
: ListEditor(parent),
|
||||
_addString(addString),
|
||||
_addStringDescription(addStringDescription),
|
||||
_maxStringSize(maxStringSize),
|
||||
_allowEmpty(allowEmtpy)
|
||||
_filterCallback(filter),
|
||||
_preprocessCallback(preprocess)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -77,15 +80,19 @@ void StringListEdit::SetMaxStringSize(int size)
|
|||
|
||||
void StringListEdit::Add()
|
||||
{
|
||||
std::string name;
|
||||
std::string entry;
|
||||
bool accepted = NameDialog::AskForName(this, _addString,
|
||||
_addStringDescription, name, "",
|
||||
_addStringDescription, entry, "",
|
||||
_maxStringSize, false);
|
||||
|
||||
if (!accepted || (!_allowEmpty && name.empty())) {
|
||||
if (!accepted) {
|
||||
return;
|
||||
}
|
||||
StringVariable string = name;
|
||||
|
||||
_preprocessCallback(entry);
|
||||
if (_filterCallback(entry)) {
|
||||
return;
|
||||
}
|
||||
StringVariable string = entry;
|
||||
QVariant v = QVariant::fromValue(string);
|
||||
QListWidgetItem *item = new QListWidgetItem(
|
||||
QString::fromStdString(string.UnresolvedValue()), _list);
|
||||
|
|
@ -145,17 +152,21 @@ void StringListEdit::Down()
|
|||
|
||||
void StringListEdit::Clicked(QListWidgetItem *item)
|
||||
{
|
||||
std::string name;
|
||||
std::string entry;
|
||||
bool accepted = NameDialog::AskForName(this, _addString,
|
||||
_addStringDescription, name,
|
||||
_addStringDescription, entry,
|
||||
item->text(), _maxStringSize,
|
||||
false);
|
||||
|
||||
if (!accepted || (!_allowEmpty && name.empty())) {
|
||||
if (!accepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringVariable string = name;
|
||||
_preprocessCallback(entry);
|
||||
if (_filterCallback(entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringVariable string = entry;
|
||||
QVariant v = QVariant::fromValue(string);
|
||||
item->setText(QString::fromStdString(string.UnresolvedValue()));
|
||||
item->setData(Qt::UserRole, string);
|
||||
|
|
|
|||
|
|
@ -19,14 +19,20 @@ public:
|
|||
friend class StringListEdit;
|
||||
};
|
||||
|
||||
class ADVSS_EXPORT StringListEdit final : public ListEditor {
|
||||
class ADVSS_EXPORT StringListEdit : public ListEditor {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
StringListEdit(QWidget *parent, const QString &addString = "",
|
||||
const QString &addStringDescription = "",
|
||||
int maxStringSize = 170, bool allowEmtpy = false);
|
||||
StringListEdit(
|
||||
QWidget *parent, const QString &addString = "",
|
||||
const QString &addStringDescription = "",
|
||||
int maxStringSize = 170,
|
||||
const std::function<bool(const std::string &)> &filter =
|
||||
[](const std::string &) { return false; },
|
||||
const std::function<void(std::string &input)> &preprocess =
|
||||
[](std::string &) {});
|
||||
void SetStringList(const StringList &);
|
||||
StringList GetStringList() const { return _stringList; }
|
||||
void SetMaxStringSize(int);
|
||||
|
||||
private slots:
|
||||
|
|
@ -44,7 +50,8 @@ private:
|
|||
QString _addString;
|
||||
QString _addStringDescription;
|
||||
int _maxStringSize = 170;
|
||||
bool _allowEmpty = false;
|
||||
std::function<bool(const std::string &)> _filterCallback;
|
||||
std::function<void(std::string &)> _preprocessCallback;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
|
|
|||
|
|
@ -169,7 +169,8 @@ MacroActionHttpEdit::MacroActionHttpEdit(
|
|||
_headerListLayout(new QVBoxLayout()),
|
||||
_headerList(new StringListEdit(
|
||||
this, obs_module_text("AdvSceneSwitcher.action.http.headers"),
|
||||
obs_module_text("AdvSceneSwitcher.action.http.addHeader"))),
|
||||
obs_module_text("AdvSceneSwitcher.action.http.addHeader"),
|
||||
170, [](const std::string &input) { return input.empty(); })),
|
||||
_timeout(new DurationSelection(this, false))
|
||||
{
|
||||
populateMethodSelection(_methods);
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ ProcessConfigEdit::ProcessConfigEdit(QWidget *parent)
|
|||
this, obs_module_text("AdvSceneSwitcher.process.addArgument"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.process.addArgumentDescription"),
|
||||
4096, true)),
|
||||
4096)),
|
||||
_workingDirectory(new FileSelection(FileSelection::Type::FOLDER))
|
||||
{
|
||||
_advancedSettingsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
|
|
|||
|
|
@ -94,6 +94,8 @@ target_sources(
|
|||
macro-condition-twitch.hpp
|
||||
points-reward-selection.cpp
|
||||
points-reward-selection.hpp
|
||||
tag-selection.cpp
|
||||
tag-selection.hpp
|
||||
token.cpp
|
||||
token.hpp
|
||||
twitch-helpers.cpp
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ struct ChannelInfo {
|
|||
struct TwitchChannel {
|
||||
void Load(obs_data_t *obj);
|
||||
void Save(obs_data_t *obj) const;
|
||||
void SetName(const StringVariable &name) { _name = name; }
|
||||
StringVariable GetName() const { return _name; }
|
||||
std::string GetUserID(const TwitchToken &token) const;
|
||||
bool IsValid(const std::string &id) const;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ bool MacroActionTwitch::_registered = MacroActionFactory::Register(
|
|||
void MacroActionTwitch::ResolveVariablesToFixedValues()
|
||||
{
|
||||
_streamTitle.ResolveVariables();
|
||||
_tags.ResolveVariables();
|
||||
_markerDescription.ResolveVariables();
|
||||
_duration.ResolveVariables();
|
||||
_announcementMessage.ResolveVariables();
|
||||
|
|
@ -56,6 +57,8 @@ const static std::map<MacroActionTwitch::Action, std::string> actionTypes = {
|
|||
"AdvSceneSwitcher.action.twitch.type.channel.info.title.set"},
|
||||
{MacroActionTwitch::Action::CHANNEL_INFO_CATEGORY_SET,
|
||||
"AdvSceneSwitcher.action.twitch.type.channel.info.category.set"},
|
||||
{MacroActionTwitch::Action::CHANNEL_INFO_TAGS_SET,
|
||||
"AdvSceneSwitcher.action.twitch.type.channel.info.tags.set"},
|
||||
{MacroActionTwitch::Action::RAID_START,
|
||||
"AdvSceneSwitcher.action.twitch.type.raid.start"},
|
||||
{MacroActionTwitch::Action::COMMERCIAL_START,
|
||||
|
|
@ -517,6 +520,9 @@ bool MacroActionTwitch::PerformAction()
|
|||
case Action::CHANNEL_INFO_CATEGORY_SET:
|
||||
SetStreamCategory(token);
|
||||
break;
|
||||
case Action::CHANNEL_INFO_TAGS_SET:
|
||||
_tags.SetStreamTags(*token);
|
||||
break;
|
||||
case Action::RAID_START:
|
||||
StartRaid(token);
|
||||
break;
|
||||
|
|
@ -576,6 +582,7 @@ bool MacroActionTwitch::Save(obs_data_t *obj) const
|
|||
GetWeakTwitchTokenName(_token).c_str());
|
||||
_streamTitle.Save(obj, "streamTitle");
|
||||
_category.Save(obj);
|
||||
_tags.Save(obj);
|
||||
_markerDescription.Save(obj, "markerDescription");
|
||||
obs_data_set_bool(obj, "clipHasDelay", _clipHasDelay);
|
||||
_duration.Save(obj);
|
||||
|
|
@ -604,6 +611,7 @@ bool MacroActionTwitch::Load(obs_data_t *obj)
|
|||
_token = GetWeakTwitchTokenByName(obs_data_get_string(obj, "token"));
|
||||
_streamTitle.Load(obj, "streamTitle");
|
||||
_category.Load(obj);
|
||||
_tags.Load(obj);
|
||||
_markerDescription.Load(obj, "markerDescription");
|
||||
_clipHasDelay = obs_data_get_bool(obj, "clipHasDelay");
|
||||
_duration.Load(obj);
|
||||
|
|
@ -642,6 +650,8 @@ bool MacroActionTwitch::ActionIsSupportedByToken()
|
|||
{{"channel:manage:broadcast"}}},
|
||||
{Action::CHANNEL_INFO_CATEGORY_SET,
|
||||
{{"channel:manage:broadcast"}}},
|
||||
{Action::CHANNEL_INFO_TAGS_SET,
|
||||
{{"channel:manage:broadcast"}}},
|
||||
{Action::CHANNEL_INFO_LANGUAGE_SET,
|
||||
{{"channel:manage:broadcast"}}},
|
||||
{Action::CHANNEL_INFO_DELAY_SET,
|
||||
|
|
@ -815,6 +825,7 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
|
|||
_tokenWarning(new QLabel()),
|
||||
_streamTitle(new VariableLineEdit(this)),
|
||||
_category(new TwitchCategoryWidget(this)),
|
||||
_tags(new TagListWidget(this)),
|
||||
_markerDescription(new VariableLineEdit(this)),
|
||||
_clipHasDelay(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.clip.hasDelay"))),
|
||||
|
|
@ -840,6 +851,7 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
|
|||
mainLayout->addLayout(_layout);
|
||||
mainLayout->addWidget(_announcementMessage);
|
||||
mainLayout->addWidget(_chatMessage);
|
||||
mainLayout->addWidget(_tags);
|
||||
mainLayout->addWidget(_tokenWarning);
|
||||
setLayout(mainLayout);
|
||||
|
||||
|
|
@ -876,6 +888,7 @@ void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
|
|||
_category->SetToken(_entryData->_token);
|
||||
_channel->SetToken(_entryData->_token);
|
||||
_pointsReward->SetToken(_entryData->_token);
|
||||
_tags->SetToken(_entryData->_token);
|
||||
_entryData->ResetChatConnection();
|
||||
|
||||
SetWidgetVisibility();
|
||||
|
|
@ -930,6 +943,12 @@ void MacroActionTwitchEdit::CategoryChanged(const TwitchCategory &category)
|
|||
_entryData->_category = category;
|
||||
}
|
||||
|
||||
void MacroActionTwitchEdit::TagsChanged(const TwitchTagList &tags)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_tags = tags;
|
||||
}
|
||||
|
||||
void MacroActionTwitchEdit::MarkerDescriptionChanged()
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
|
|
@ -1010,6 +1029,8 @@ void MacroActionTwitchEdit::SetWidgetSignalConnections()
|
|||
QWidget::connect(_category,
|
||||
SIGNAL(CategoryChanged(const TwitchCategory &)), this,
|
||||
SLOT(CategoryChanged(const TwitchCategory &)));
|
||||
QWidget::connect(_tags, SIGNAL(TagListChanged(const TwitchTagList &)),
|
||||
this, SLOT(TagsChanged(const TwitchTagList &)));
|
||||
QWidget::connect(_markerDescription, SIGNAL(editingFinished()), this,
|
||||
SLOT(MarkerDescriptionChanged()));
|
||||
QObject::connect(_clipHasDelay, SIGNAL(stateChanged(int)), this,
|
||||
|
|
@ -1052,6 +1073,8 @@ void MacroActionTwitchEdit::SetWidgetVisibility()
|
|||
_category->setVisible(
|
||||
_entryData->GetAction() ==
|
||||
MacroActionTwitch::Action::CHANNEL_INFO_CATEGORY_SET);
|
||||
_tags->setVisible(_entryData->GetAction() ==
|
||||
MacroActionTwitch::Action::CHANNEL_INFO_TAGS_SET);
|
||||
_channel->setVisible(
|
||||
_entryData->GetAction() ==
|
||||
MacroActionTwitch::Action::RAID_START ||
|
||||
|
|
@ -1247,6 +1270,8 @@ void MacroActionTwitchEdit::UpdateEntryData()
|
|||
_streamTitle->setText(_entryData->_streamTitle);
|
||||
_category->SetToken(_entryData->_token);
|
||||
_category->SetCategory(_entryData->_category);
|
||||
_tags->SetTags(_entryData->_tags);
|
||||
_tags->SetToken(_entryData->_token);
|
||||
_markerDescription->setText(_entryData->_markerDescription);
|
||||
_clipHasDelay->setChecked(_entryData->_clipHasDelay);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "channel-selection.hpp"
|
||||
#include "chat-connection.hpp"
|
||||
#include "points-reward-selection.hpp"
|
||||
#include "tag-selection.hpp"
|
||||
|
||||
#include <variable-line-edit.hpp>
|
||||
#include <variable-text-edit.hpp>
|
||||
|
|
@ -27,7 +28,7 @@ public:
|
|||
CHANNEL_INFO_CATEGORY_SET = 20,
|
||||
CHANNEL_INFO_LANGUAGE_SET = 30,
|
||||
CHANNEL_INFO_DELAY_SET = 40,
|
||||
CHANNEL_INFO_TAGS_SET = 50, // TODO
|
||||
CHANNEL_INFO_TAGS_SET = 50,
|
||||
CHANNEL_INFO_CONTENT_LABELS_SET = 60, // TODO
|
||||
CHANNEL_INFO_BRANDED_CONTENT_ENABLE = 70,
|
||||
CHANNEL_INFO_BRANDED_CONTENT_DISABLE = 71,
|
||||
|
|
@ -168,6 +169,7 @@ public:
|
|||
StringVariable _streamTitle =
|
||||
obs_module_text("AdvSceneSwitcher.action.twitch.title.title");
|
||||
TwitchCategory _category;
|
||||
TwitchTagList _tags;
|
||||
StringVariable _markerDescription = obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.marker.description");
|
||||
bool _clipHasDelay = false;
|
||||
|
|
@ -235,6 +237,7 @@ private slots:
|
|||
void CheckToken();
|
||||
void StreamTitleChanged();
|
||||
void CategoryChanged(const TwitchCategory &);
|
||||
void TagsChanged(const TwitchTagList &tags);
|
||||
void MarkerDescriptionChanged();
|
||||
void ClipHasDelayChanged(int state);
|
||||
void DurationChanged(const Duration &);
|
||||
|
|
@ -269,6 +272,7 @@ private:
|
|||
QTimer _tokenCheckTimer;
|
||||
VariableLineEdit *_streamTitle;
|
||||
TwitchCategoryWidget *_category;
|
||||
TagListWidget *_tags;
|
||||
VariableLineEdit *_markerDescription;
|
||||
QCheckBox *_clipHasDelay;
|
||||
DurationSelection *_duration;
|
||||
|
|
|
|||
192
plugins/twitch/tag-selection.cpp
Normal file
192
plugins/twitch/tag-selection.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
#include "tag-selection.hpp"
|
||||
#include "channel-selection.hpp"
|
||||
#include "log-helper.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "twitch-helpers.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <QHBoxLayout>
|
||||
#include <QListWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
void TwitchTagList::Load(obs_data_t *obj)
|
||||
{
|
||||
StringList::Load(obj, "tags", "tag");
|
||||
}
|
||||
|
||||
void TwitchTagList::Save(obs_data_t *obj) const
|
||||
{
|
||||
StringList::Save(obj, "tags", "tag");
|
||||
}
|
||||
|
||||
void TwitchTagList::SetStreamTags(const TwitchToken &token) const
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["tags"] = toVector();
|
||||
|
||||
// Although the Twitch API doesn't mention it, a request only containing
|
||||
// an empty tags array will be denied.
|
||||
// So, we just set the title, too, to work around this limitation.
|
||||
if (isEmpty()) {
|
||||
TwitchChannel channel;
|
||||
channel.SetName(token.Name());
|
||||
const auto channelInfo = channel.GetInfo(token);
|
||||
if (!channelInfo) {
|
||||
return;
|
||||
}
|
||||
if (channelInfo->title.empty()) {
|
||||
return;
|
||||
}
|
||||
j["title"] = channelInfo->title;
|
||||
}
|
||||
|
||||
auto result = SendPatchRequest(token, "https://api.twitch.tv",
|
||||
"/helix/channels",
|
||||
{{"broadcaster_id", token.GetUserID()}},
|
||||
j.dump());
|
||||
|
||||
if (result.status != 204) {
|
||||
blog(LOG_INFO, "Failed to set stream tags! (%d)",
|
||||
result.status);
|
||||
}
|
||||
}
|
||||
|
||||
TagListWidget::TagListWidget(QWidget *parent)
|
||||
: StringListEdit(
|
||||
parent,
|
||||
obs_module_text("AdvSceneSwitcher.action.twitch.tags.add"),
|
||||
obs_module_text("AdvSceneSwitcher.action.twitch.tags.add"),
|
||||
_maxTagLength,
|
||||
[this](const std::string &input) { return Filter(input); },
|
||||
[](std::string &input) {
|
||||
input = QString::fromStdString(input)
|
||||
.trimmed()
|
||||
.toStdString();
|
||||
})
|
||||
{
|
||||
connect(this, SIGNAL(StringListChanged(const StringList &)), this,
|
||||
SLOT(StringListChangedWrapper(const StringList &)));
|
||||
|
||||
auto getCurrent = new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.tags.getCurrent"));
|
||||
connect(getCurrent, &QPushButton::clicked, this,
|
||||
&TagListWidget::GetCurrentClicked);
|
||||
_mainLayout->addWidget(getCurrent);
|
||||
|
||||
// TODO:
|
||||
// Figure out why the Twitch API always returns an empty tag list
|
||||
// Hide for now
|
||||
getCurrent->hide();
|
||||
}
|
||||
|
||||
void TagListWidget::SetTags(const TwitchTagList &tags)
|
||||
{
|
||||
SetStringList(tags);
|
||||
}
|
||||
|
||||
void TagListWidget::SetToken(const std::weak_ptr<TwitchToken> &t)
|
||||
{
|
||||
_token = t;
|
||||
}
|
||||
|
||||
void TagListWidget::GetCurrentClicked()
|
||||
{
|
||||
auto token = _token.lock();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
TwitchChannel channel;
|
||||
channel.SetName(token->Name());
|
||||
const auto channelInfo = channel.GetInfo(*token);
|
||||
if (!channelInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
TwitchTagList tags;
|
||||
for (const auto &tag : channelInfo->tags) {
|
||||
tags << tag;
|
||||
}
|
||||
|
||||
SetTags(tags);
|
||||
}
|
||||
|
||||
void TagListWidget::StringListChangedWrapper(const StringList &list)
|
||||
{
|
||||
emit TagListChanged(TwitchTagList{list});
|
||||
}
|
||||
|
||||
bool TagListWidget::Filter(const std::string &input)
|
||||
{
|
||||
const auto tag = QString::fromStdString(input);
|
||||
|
||||
if (!IsValidTag(tag)) {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.tags.invalid"),
|
||||
QString(obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.tags.invalid.info"))
|
||||
.arg(_maxTagLength));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ContainsTag(tag)) {
|
||||
QMessageBox::information(
|
||||
this,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.tags.duplicate"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.tags.duplicate.info"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count() >= _maxTags) {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.tags.limit"),
|
||||
QString(obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.tags.limit.info"))
|
||||
.arg(_maxTags));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TagListWidget::IsValidTag(const QString &tag)
|
||||
{
|
||||
if (tag.isEmpty() || tag.length() > _maxTagLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If string contains variables don't filter
|
||||
const StringVariable tmp = tag.toStdString();
|
||||
if (tmp.UnresolvedValue() != std::string(tmp)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QRegularExpression regex("^[A-Za-z0-9]+$"); // Only letters and digits
|
||||
return regex.match(tag).hasMatch();
|
||||
}
|
||||
|
||||
bool TagListWidget::ContainsTag(const QString &tag) const
|
||||
{
|
||||
const auto lowerTag = tag.toLower();
|
||||
const auto tags = GetStringList();
|
||||
for (const auto &tag : tags) {
|
||||
if (QString::fromStdString(tag.UnresolvedValue()).toLower() ==
|
||||
lowerTag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
47
plugins/twitch/tag-selection.hpp
Normal file
47
plugins/twitch/tag-selection.hpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
#include "variable-line-edit.hpp"
|
||||
#include "string-list.hpp"
|
||||
#include "token.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
class TwitchToken;
|
||||
|
||||
namespace advss {
|
||||
|
||||
struct TwitchTagList : public StringList {
|
||||
void Load(obs_data_t *obj);
|
||||
void Save(obs_data_t *obj) const;
|
||||
void SetStreamTags(const TwitchToken &token) const;
|
||||
};
|
||||
|
||||
class TagListWidget final : public StringListEdit {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagListWidget(QWidget *parent = nullptr);
|
||||
void SetTags(const TwitchTagList &tags);
|
||||
void SetToken(const std::weak_ptr<TwitchToken> &token);
|
||||
|
||||
private slots:
|
||||
void StringListChangedWrapper(const StringList &);
|
||||
void GetCurrentClicked();
|
||||
signals:
|
||||
void TagListChanged(const TwitchTagList &);
|
||||
|
||||
private:
|
||||
bool Filter(const std::string &input);
|
||||
static bool IsValidTag(const QString &tag);
|
||||
bool ContainsTag(const QString &tag) const;
|
||||
|
||||
std::weak_ptr<TwitchToken> _token;
|
||||
|
||||
static constexpr int _maxTags = 10;
|
||||
static constexpr int _maxTagLength = 25;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
|
@ -6,17 +6,11 @@
|
|||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace advss {
|
||||
namespace {
|
||||
|
||||
static constexpr std::string_view clientID = "ds5tt4ogliifsqc04mz3d3etnck3e5";
|
||||
static const int cacheTimeoutSeconds = 10;
|
||||
static std::atomic_bool apiIsThrottling = {false};
|
||||
|
||||
const char *GetClientID()
|
||||
{
|
||||
return clientID.data();
|
||||
}
|
||||
|
||||
class Args {
|
||||
public:
|
||||
Args(const std::string &uri, const std::string &path,
|
||||
|
|
@ -54,10 +48,10 @@ private:
|
|||
httplib::Headers _headers;
|
||||
};
|
||||
|
||||
}; // namespace advss
|
||||
}; // namespace
|
||||
|
||||
template<> struct std::hash<advss::Args> {
|
||||
inline std::size_t operator()(const advss::Args &args) const
|
||||
template<> struct std::hash<Args> {
|
||||
inline std::size_t operator()(const Args &args) const
|
||||
{
|
||||
static constexpr auto hash_combine = [](std::size_t &seed,
|
||||
std::size_t hashValue) {
|
||||
|
|
@ -85,6 +79,13 @@ template<> struct std::hash<advss::Args> {
|
|||
|
||||
namespace advss {
|
||||
|
||||
static constexpr std::string_view clientID = "ds5tt4ogliifsqc04mz3d3etnck3e5";
|
||||
|
||||
const char *GetClientID()
|
||||
{
|
||||
return clientID.data();
|
||||
}
|
||||
|
||||
struct CacheEntry {
|
||||
RequestResult result;
|
||||
std::chrono::system_clock::time_point cacheTime =
|
||||
|
|
@ -255,7 +256,7 @@ static RequestResult sendPostRequest(const TwitchToken &token,
|
|||
const std::string &uri,
|
||||
const std::string &path,
|
||||
const httplib::Params ¶ms,
|
||||
const OBSData &data)
|
||||
const std::string &data)
|
||||
{
|
||||
if (apiIsThrottling) {
|
||||
return {};
|
||||
|
|
@ -273,9 +274,8 @@ static RequestResult sendPostRequest(const TwitchToken &token,
|
|||
vblog(LOG_INFO, "Twitch POST request to %s began", url.c_str());
|
||||
|
||||
auto headers = getTokenRequestHeaders(*tokenStr);
|
||||
auto body = getRequestBody(data);
|
||||
auto response =
|
||||
cli.Post(pathWithParams, headers, body, "application/json");
|
||||
cli.Post(pathWithParams, headers, data, "application/json");
|
||||
|
||||
return processResult(response, __func__);
|
||||
}
|
||||
|
|
@ -284,6 +284,15 @@ RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
|
|||
const std::string &path,
|
||||
const httplib::Params ¶ms,
|
||||
const OBSData &data, bool useCache)
|
||||
{
|
||||
auto body = getRequestBody(data);
|
||||
return SendPostRequest(token, uri, path, params, body, useCache);
|
||||
}
|
||||
|
||||
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
|
||||
const std::string &path,
|
||||
const httplib::Params ¶ms,
|
||||
const std::string &data, bool useCache)
|
||||
{
|
||||
if (apiIsThrottling) {
|
||||
return {};
|
||||
|
|
@ -303,8 +312,7 @@ RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
|
|||
}
|
||||
|
||||
auto headers = getTokenRequestHeaders(*tokenStr);
|
||||
auto body = getRequestBody(data);
|
||||
Args args(uri, path, body, params, headers);
|
||||
Args args(uri, path, data, params, headers);
|
||||
|
||||
if (!useCache) {
|
||||
return sendPostRequest(token, uri, path, params, data);
|
||||
|
|
@ -326,7 +334,7 @@ static RequestResult sendPutRequest(const TwitchToken &token,
|
|||
const std::string &uri,
|
||||
const std::string &path,
|
||||
const httplib::Params ¶ms,
|
||||
const OBSData &data)
|
||||
const std::string &data)
|
||||
{
|
||||
if (apiIsThrottling) {
|
||||
return {};
|
||||
|
|
@ -344,9 +352,8 @@ static RequestResult sendPutRequest(const TwitchToken &token,
|
|||
vblog(LOG_INFO, "Twitch PUT request to %s began", url.c_str());
|
||||
|
||||
auto headers = getTokenRequestHeaders(*tokenStr);
|
||||
auto body = getRequestBody(data);
|
||||
auto response =
|
||||
cli.Put(pathWithParams, headers, body, "application/json");
|
||||
cli.Put(pathWithParams, headers, data, "application/json");
|
||||
|
||||
return processResult(response, __func__);
|
||||
}
|
||||
|
|
@ -355,6 +362,15 @@ RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
|
|||
const std::string &path,
|
||||
const httplib::Params ¶ms, const OBSData &data,
|
||||
bool useCache)
|
||||
{
|
||||
auto body = getRequestBody(data);
|
||||
return SendPutRequest(token, uri, path, params, body, useCache);
|
||||
}
|
||||
|
||||
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
|
||||
const std::string &path,
|
||||
const httplib::Params ¶ms,
|
||||
const std::string &data, bool useCache)
|
||||
{
|
||||
if (apiIsThrottling) {
|
||||
return {};
|
||||
|
|
@ -374,8 +390,7 @@ RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
|
|||
}
|
||||
|
||||
auto headers = getTokenRequestHeaders(*tokenStr);
|
||||
auto body = getRequestBody(data);
|
||||
Args args(uri, path, body, params, headers);
|
||||
Args args(uri, path, data, params, headers);
|
||||
|
||||
if (!useCache) {
|
||||
return sendPutRequest(token, uri, path, params, data);
|
||||
|
|
@ -397,7 +412,7 @@ static RequestResult sendPatchRequest(const TwitchToken &token,
|
|||
const std::string &uri,
|
||||
const std::string &path,
|
||||
const httplib::Params ¶ms,
|
||||
const OBSData &data)
|
||||
const std::string &data)
|
||||
{
|
||||
if (apiIsThrottling) {
|
||||
return {};
|
||||
|
|
@ -415,9 +430,8 @@ static RequestResult sendPatchRequest(const TwitchToken &token,
|
|||
vblog(LOG_INFO, "Twitch PATCH request to %s began", url.c_str());
|
||||
|
||||
auto headers = getTokenRequestHeaders(*tokenStr);
|
||||
auto body = getRequestBody(data);
|
||||
auto response =
|
||||
cli.Patch(pathWithParams, headers, body, "application/json");
|
||||
cli.Patch(pathWithParams, headers, data, "application/json");
|
||||
|
||||
return processResult(response, __func__);
|
||||
}
|
||||
|
|
@ -426,6 +440,15 @@ RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
|
|||
const std::string &path,
|
||||
const httplib::Params ¶ms,
|
||||
const OBSData &data, bool useCache)
|
||||
{
|
||||
auto body = getRequestBody(data);
|
||||
return SendPatchRequest(token, uri, path, params, body, useCache);
|
||||
}
|
||||
|
||||
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
|
||||
const std::string &path,
|
||||
const httplib::Params ¶ms,
|
||||
const std::string &data, bool useCache)
|
||||
{
|
||||
if (apiIsThrottling) {
|
||||
return {};
|
||||
|
|
@ -445,8 +468,7 @@ RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
|
|||
}
|
||||
|
||||
auto headers = getTokenRequestHeaders(*tokenStr);
|
||||
auto body = getRequestBody(data);
|
||||
Args args(uri, path, body, params, headers);
|
||||
Args args(uri, path, data, params, headers);
|
||||
|
||||
if (!useCache) {
|
||||
return sendPatchRequest(token, uri, path, params, data);
|
||||
|
|
|
|||
|
|
@ -21,20 +21,32 @@ RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
|
|||
const std::string &path,
|
||||
const httplib::Params ¶ms = {},
|
||||
bool useCache = false);
|
||||
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
|
||||
const std::string &path,
|
||||
const httplib::Params ¶ms,
|
||||
const OBSData &data, bool useCache = false);
|
||||
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
|
||||
const std::string &path,
|
||||
const httplib::Params ¶ms = {},
|
||||
const OBSData &data = nullptr,
|
||||
const std::string &data = "",
|
||||
bool useCache = false);
|
||||
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
|
||||
const std::string &path,
|
||||
const httplib::Params ¶ms, const OBSData &data,
|
||||
bool useCache = false);
|
||||
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
|
||||
const std::string &path,
|
||||
const httplib::Params ¶ms = {},
|
||||
const OBSData &data = nullptr,
|
||||
const std::string &data = "",
|
||||
bool useCache = false);
|
||||
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
|
||||
const std::string &path,
|
||||
const httplib::Params ¶ms,
|
||||
const OBSData &data, bool useCache = false);
|
||||
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
|
||||
const std::string &path,
|
||||
const httplib::Params ¶ms = {},
|
||||
const OBSData &data = nullptr,
|
||||
const std::string &data = "",
|
||||
bool useCache = false);
|
||||
|
||||
// Helper functions to set temp var values
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user