#include "channel-selection.hpp" #include "log-helper.hpp" #include "twitch-helpers.hpp" #include "token.hpp" #include #include #include #include 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 userIDCache; if (!token.IsValid()) { vblog(LOG_INFO, "%s() failed: token invalid", __func__); return "invalid"; } 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}}, true); 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 for channel! (%d)", res.status); return "invalid"; } OBSDataArrayAutoRelease array = obs_data_get_array(res.data, "data"); size_t count = obs_data_array_count(array); if (count == 0) { userIDCache[_name] = "invalid"; return "invalid"; } OBSDataAutoRelease arrayObj = obs_data_array_item(array, 0); const std::string id = obs_data_get_string(arrayObj, "id"); userIDCache[_name] = id; return id; } 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 getStringArrayHelper(const OBSDataAutoRelease &data, const std::string &value) { std::vector 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(); result.emplace_back(strElement); } } catch (const nlohmann::json::exception &) { } return result; } std::optional TwitchChannel::GetLiveInfo(const TwitchToken &token) const { 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 TwitchChannel::GetInfo(const TwitchToken &token) const { 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; } void TwitchChannel::ResolveVariables() { _name.ResolveVariables(); } bool TwitchChannel::IsValid(const std::string &id) { 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 &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