SceneSwitcher/plugins/twitch/channel-selection.cpp
WarmUpTill 7d0332dd0e Restructure library and plugins
The "core" macro conditions and actions have been extracted out to the
"base" plugin.

The library now mostly contains functionality which is required across
all plugins and (e.g. definitions for macro segments).

The goal is to reduce the complexity and cross-dependencies and group
the source files in a better way.

This should relsove the "library limit of 65535 objects exceeded" build
issue occuring in some Windows build environments.
2024-01-27 14:10:34 +01:00

264 lines
7.4 KiB
C++

#include "channel-selection.hpp"
#include "twitch-helpers.hpp"
#include "token.hpp"
#include <QLayout>
#include <QDesktopServices>
#include <obs-module-helper.hpp>
#include <nlohmann/json.hpp>
namespace advss {
bool ChannelLiveInfo::IsLive() const
{
return type == "live";
}
void TwitchChannel::Load(obs_data_t *obj)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, "channel");
_name.Load(data, "name");
}
void TwitchChannel::Save(obs_data_t *obj) const
{
OBSDataAutoRelease data = obs_data_create();
_name.Save(data, "name");
obs_data_set_obj(obj, "channel", data);
}
std::string TwitchChannel::GetUserID(const TwitchToken &token) const
{
static std::map<std::string, std::string> userIDCache;
auto it = userIDCache.find(std::string(_name));
if (it != userIDCache.end()) {
return it->second;
}
auto res = SendGetRequest(token, "https://api.twitch.tv",
"/helix/users", {{"login", _name}});
if (res.status == 400) {
userIDCache[_name] = "invalid";
blog(LOG_INFO, "ignoring invalid channel selection '%s'!",
_name.c_str());
return "invalid";
}
if (res.status != 200) {
blog(LOG_INFO, "failed to get Twitch user id from token! (%d)",
res.status);
return "invalid";
}
OBSDataArrayAutoRelease array = obs_data_get_array(res.data, "data");
size_t count = obs_data_array_count(array);
for (size_t i = 0; i < count; i++) {
OBSDataAutoRelease arrayObj = obs_data_array_item(array, i);
std::string id = obs_data_get_string(arrayObj, "id");
userIDCache[_name] = id;
return id;
}
userIDCache[_name] = "invalid";
return "invalid";
}
static QDate parseRFC3339Date(const std::string &rfc3339Date)
{
QString rfc3339QString = QString::fromStdString(rfc3339Date);
QRegularExpression regex("(\\d{4})-(\\d{2})-(\\d{2})");
QRegularExpressionMatch match = regex.match(rfc3339QString);
if (match.hasMatch()) {
int year = match.captured(1).toInt();
int month = match.captured(2).toInt();
int day = match.captured(3).toInt();
QDate date(year, month, day);
return date;
} else {
return QDate();
}
}
static std::vector<std::string>
getStringArrayHelper(const OBSDataAutoRelease &data, const std::string &value)
{
std::vector<std::string> result;
auto jsonStr = obs_data_get_json(data);
if (!jsonStr) {
return result;
}
try {
auto json = nlohmann::json::parse(jsonStr);
auto array = json[value];
if (!array.is_array()) {
return result;
}
for (const auto &element : array) {
std::string strElement = element.get<std::string>();
result.emplace_back(strElement);
}
} catch (const nlohmann::json::exception &) {
}
return result;
}
std::optional<ChannelLiveInfo>
TwitchChannel::GetLiveInfo(const TwitchToken &token)
{
auto id = GetUserID(token);
if (!IsValid(id)) {
return {};
}
httplib::Params params = {
{"first", "1"}, {"after", ""}, {"user_id", id}};
auto result = SendGetRequest(token, "https://api.twitch.tv",
"/helix/streams", params, true);
if (result.status != 200) {
return {};
}
OBSDataArrayAutoRelease array = obs_data_get_array(result.data, "data");
size_t count = obs_data_array_count(array);
if (count == 0) {
return {};
}
OBSDataAutoRelease data = obs_data_array_item(array, 0);
ChannelLiveInfo info;
info.id = obs_data_get_string(data, "id");
info.user_id = obs_data_get_string(data, "user_id");
info.user_login = obs_data_get_string(data, "user_login");
info.user_name = obs_data_get_string(data, "user_name");
info.game_id = obs_data_get_string(data, "game_id");
info.game_name = obs_data_get_string(data, "game_name");
info.type = obs_data_get_string(data, "type");
info.title = obs_data_get_string(data, "title");
info.tags = getStringArrayHelper(data, "tags");
info.viewer_count = obs_data_get_int(data, "viewer_count");
info.started_at =
parseRFC3339Date(obs_data_get_string(data, "started_at"));
info.language = obs_data_get_string(data, "language");
info.thumbnail_url = obs_data_get_string(data, "thumbnail_url");
info.is_mature = obs_data_get_bool(data, "is_mature");
return info;
}
std::optional<ChannelInfo> TwitchChannel::GetInfo(const TwitchToken &token)
{
auto id = GetUserID(token);
if (!IsValid(id)) {
return {};
}
httplib::Params params = {
{"first", "1"}, {"after", ""}, {"broadcaster_id", id}};
auto result = SendGetRequest(token, "https://api.twitch.tv",
"/helix/channels", params, true);
if (result.status != 200) {
return {};
}
OBSDataArrayAutoRelease array = obs_data_get_array(result.data, "data");
size_t count = obs_data_array_count(array);
if (count == 0) {
return {};
}
OBSDataAutoRelease data = obs_data_array_item(array, 0);
ChannelInfo info;
info.broadcaster_id = obs_data_get_string(data, "broadcaster_id");
info.broadcaster_login = obs_data_get_string(data, "broadcaster_login");
info.broadcaster_name = obs_data_get_string(data, "broadcaster_name");
info.broadcaster_language =
obs_data_get_string(data, "broadcaster_language");
info.game_name = obs_data_get_string(data, "game_name");
info.game_id = obs_data_get_string(data, "game_id");
info.title = obs_data_get_string(data, "title");
info.delay = obs_data_get_int(data, "delay");
info.tags = getStringArrayHelper(data, "tags");
info.content_classification_labels =
getStringArrayHelper(data, "content_classification_labels");
info.is_branded_content = obs_data_get_bool(data, "is_branded_content");
return info;
}
bool TwitchChannel::IsValid(const std::string &id) const
{
return id != "invalid" && !id.empty();
}
TwitchChannelSelection::TwitchChannelSelection(QWidget *parent)
: QWidget(parent),
_channelName(new VariableLineEdit(this)),
_openChannel(new QPushButton(obs_module_text(
"AdvSceneSwitcher.twitch.selection.channel.open")))
{
QWidget::connect(_channelName, SIGNAL(editingFinished()), this,
SLOT(SelectionChanged()));
QWidget::connect(_openChannel, SIGNAL(pressed()), this,
SLOT(OpenChannel()));
QWidget::connect(this, SIGNAL(ChannelChanged(const TwitchChannel &)),
this,
SLOT(SetOpenChannelState(const TwitchChannel &)));
auto layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_channelName);
layout->addWidget(_openChannel);
setLayout(layout);
}
void TwitchChannelSelection::SetChannel(const TwitchChannel &channel)
{
_channelName->setText(channel._name);
emit ChannelChanged(channel);
}
void TwitchChannelSelection::SetToken(const std::weak_ptr<TwitchToken> &token)
{
_token = token;
}
void TwitchChannelSelection::OpenChannel()
{
// Resolve potential variables
StringVariable temp = _channelName->text().toStdString();
QDesktopServices::openUrl("https://www.twitch.tv/" +
QString::fromStdString(temp));
}
void TwitchChannelSelection::SetOpenChannelState(const TwitchChannel &channel)
{
auto token = _token.lock();
if (!token) {
_openChannel->setToolTip(obs_module_text(
"AdvSceneSwitcher.twitch.selection.channel.open.tooltip.noAccount"));
_openChannel->setDisabled(false);
return;
}
if (channel.GetUserID(*token) != "invalid") {
_openChannel->setToolTip(obs_module_text(
"AdvSceneSwitcher.twitch.selection.channel.open.tooltip.details"));
_openChannel->setDisabled(false);
return;
}
_openChannel->setToolTip(obs_module_text(
"AdvSceneSwitcher.twitch.selection.channel.open.tooltip.noChannel"));
_openChannel->setDisabled(true);
}
void TwitchChannelSelection::SelectionChanged()
{
TwitchChannel channel;
channel._name = _channelName->text().toStdString();
emit ChannelChanged(channel);
}
} // namespace advss