Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Commands Editor: Add field for redemption trigger #170

Open
ObserverHerb opened this issue Mar 13, 2024 · 3 comments
Open

Commands Editor: Add field for redemption trigger #170

ObserverHerb opened this issue Mar 13, 2024 · 3 comments
Labels
bug Something isn't working

Comments

@ObserverHerb
Copy link
Owner

Add a text field that allows entering the redemption name for commands that are triggered by redemptions.

@ObserverHerb ObserverHerb added the bug Something isn't working label Mar 13, 2024
@ObserverHerb ObserverHerb added this to the Patch 2.01 milestone Mar 13, 2024
@ObserverHerb
Copy link
Owner Author

Can I query for the channel's list of redemptions and populate a list in real-time?

@ObserverHerb
Copy link
Owner Author

This is suspended since I dropped Twitch affiliate.

@ObserverHerb ObserverHerb removed this from the Patch 2.01 milestone Oct 1, 2024
@ObserverHerb
Copy link
Owner Author

ObserverHerb commented Oct 2, 2024

diff --git a/bot.cpp b/bot.cpp
index 2df34aa..b99ca3b 100644
--- a/bot.cpp
+++ b/bot.cpp
@@ -46,11 +46,12 @@ const char *TWITCH_API_OPERATION_EMOTE_ONLY="emote only";
 const char *TWITCH_API_OPERATION_STREAM_TITLE="stream title";
 const char *TWITCH_API_OPERATION_STREAM_CATEGORY="stream category";
 const char *TWITCH_API_OPERATION_LOAD_BADGES="badges";
-const char *TWITCH_API_OPERATION_SHOUTOUT="shoutout";
+const char *TWITCH_API_OPERATION_FETCH_REDEMPTIONS="redemption list";
 const char *TWITCH_API_ERROR_TEMPLATE_INCOMPLETE="Response from requesting %1 was incomplete";
 const char *TWITCH_API_ERROR_TEMPLATE_UNKNOWN="Something went wrong obtaining %1";
 const char *TWITCH_API_ERROR_TEMPLATE_JSON_PARSE="Error parsing %1 JSON: %2";
 const char *TWITCH_API_ERROR_AUTH="Auth token or client ID missing or invalid";
+const char *TWITCH_API_ERROR_SERVER="Twitch choked";
 const char16_t *CHAT_BADGE_BROADCASTER=u"broadcaster";
 const char16_t *CHAT_BADGE_MODERATOR=u"moderator";
 const char16_t *CHAT_TAG_DISPLAY_NAME=u"display-name";
@@ -136,6 +137,11 @@ Bot::Bot(Music::Player &musicPlayer,Security &security,QObject *parent) : QObjec
 	connect(&vibeKeeper,&Music::Player::Print,this,&Bot::Print);
 }
 
+void Bot::Initialize()
+{
+	FetchRedemptionList();
+}
+
 void Bot::DeclareCommand(const Command &&command,NativeCommandFlag flag)
 {
 	commands.insert({{command.Name(),command}});
@@ -606,6 +612,61 @@ void Bot::RestoreMusic()
 	vibeKeeper.DuckVolume(false);
 }
 
+void Bot::FetchRedemptionList()
+{
+	Network::Request::Send({Twitch::Endpoint(Twitch::ENDPOINT_REDEMPTION_LIST)},Network::Method::GET,[this](QNetworkReply *reply) {
+		switch (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt())
+		{
+		case 400:
+			emit Print(u"Missing information or too many IDs specified"_s,TWITCH_API_OPERATION_FETCH_REDEMPTIONS);
+			return;
+		case 401:
+			emit Print(TWITCH_API_ERROR_AUTH,TWITCH_API_OPERATION_FETCH_REDEMPTIONS);
+			return;
+		case 403:
+			emit Print(u"The broadcaster does not qualify for redemptions"_s,TWITCH_API_OPERATION_FETCH_REDEMPTIONS);
+			return;
+		case 404:
+			emit Print(u"Specified redemptions were not found"_s,TWITCH_API_OPERATION_FETCH_REDEMPTIONS);
+			return;
+		case 500:
+			emit Print(TWITCH_API_ERROR_SERVER,TWITCH_API_OPERATION_FETCH_REDEMPTIONS);
+			return;
+		}
+
+		if (reply->error())
+		{
+			emit Print(u"Failed to obtain list of redemptions for unknown reason"_s,TWITCH_API_OPERATION_FETCH_REDEMPTIONS);
+			return;
+		}
+
+		const JSON::ParseResult parsedJSON=JSON::Parse(reply->readAll());
+		if (!parsedJSON)
+		{
+			emit Print(QString(TWITCH_API_ERROR_TEMPLATE_JSON_PARSE).arg(TWITCH_API_OPERATION_FETCH_REDEMPTIONS,parsedJSON.error));
+			return;
+		}
+
+		const QJsonObject object=parsedJSON().object();
+		auto jsonFieldData=object.find(JSON::Keys::DATA);
+		if (jsonFieldData == object.end()) return;
+		QStringList redemptionNames;
+		for (const QJsonValue &redemptions : jsonFieldData->toArray())
+		{
+			const QJsonObject objectRedemption=redemptions.toObject();
+			auto jsonFieldRedemptionName=objectRedemption.find("title");
+			if (jsonFieldRedemptionName == objectRedemption.end()) continue;
+			redemptionNames.append(jsonFieldRedemptionName->toString());
+		}
+		emit RedemptionList(redemptionNames);
+	},{
+		{QUERY_PARAMETER_BROADCASTER_ID,security.AdministratorID()}
+	},{
+		{NETWORK_HEADER_AUTHORIZATION,security.Bearer(security.OAuthToken())},
+		{NETWORK_HEADER_CLIENT_ID,security.ClientID()}
+	});
+}
+
 void Bot::DispatchArrival(const QString &login)
 {
 	if (auto viewer=viewers.find(login); viewer != viewers.end())
@@ -1103,6 +1164,8 @@ void Bot::DispatchShoutout(Command command)
 
 void Bot::DispatchShoutout(const QString &streamer)
 {
+	static const char *TWITCH_API_OPERATION_SHOUTOUT="shoutout";
+
 	Viewer::Remote *profile=new Viewer::Remote(security,streamer);
 	connect(profile,&Viewer::Remote::Recognized,profile,[this](const Viewer::Local &profile) {
 		// native Twitch shoutout
diff --git a/bot.h b/bot.h
index d9c4d79..a0b68ed 100644
--- a/bot.h
+++ b/bot.h
@@ -133,6 +133,7 @@ protected:
 	void AdjustVibeVolume(Command command);
 	void StreamTitle(const QString &title);
 	void StreamCategory(const QString &category);
+	void FetchRedemptionList();
 signals:
 	void Print(const QString &message,const QString operation=QString(),const QString subsystem=QString("bot core"));
 	void ChatMessage(std::shared_ptr<Chat::Message> message);
@@ -160,7 +161,9 @@ signals:
 	void AnnounceTextWall(const QString &message,const QString &audioPath);
 	void AnnounceDeniedCommand(const QString &videoPath);
 	void Welcomed(const QString &user);
+	void RedemptionList(const QStringList &redemptions);
 public slots:
+	void Initialize();
 	void ParseChatMessage(const QString &prefix,const QString &source,const QStringList &parameters,const QString &message);
 	void DispatchCommandViaSubsystem(JSON::SignalPayload *response,const QString &name,const QString &login);
 	void Ping();
diff --git a/main.cpp b/main.cpp
index 89ecd6e..4234385 100644
--- a/main.cpp
+++ b/main.cpp
@@ -250,6 +250,9 @@ int main(int argc,char *argv[])
 		celeste.connect(&celeste,&Bot::Pulse,&pulsar,QOverload<const QString&,const QString&>::of(&Pulsar::Pulse));
 		celeste.connect(&celeste,&Bot::Welcomed,&metrics,&UI::Metrics::Dialog::Acknowledged);
 		celeste.connect(&celeste,&Bot::Panic,&window,&Window::ShowPanicText);
+		celeste.connect(&celeste,&Bot::RedemptionList,&celeste,[](const QStringList &redemptionNames) {
+			UI::Commands::Entry::redemptionNames=redemptionNames;
+		});
 		celeste.connect(&celeste,&Bot::Panic,&celeste,[&celeste]() {
 			celeste.disconnect();
 		});
@@ -300,6 +303,7 @@ int main(int argc,char *argv[])
 		});
 		channel->connect(channel,&Channel::Denied,&security,&Security::AuthorizeUser);
 		security.connect(&security,&Security::Initialized,channel,&Channel::Connect);
+		security.connect(&security,&Security::Initialized,&celeste,&Bot::Initialize);
 		application.connect(&application,&QApplication::aboutToQuit,[&log,&socket,channel]() {
 			socket.connect(&socket,&IRCSocket::disconnected,&log,&Log::Archive);
 			channel->disconnect(); // stops attempting to reconnect by removing all connections to signals
diff --git a/security.cpp b/security.cpp
index 92b65d4..73d2f67 100644
--- a/security.cpp
+++ b/security.cpp
@@ -231,7 +231,7 @@ void Security::ObtainAdministratorProfile()
 	Viewer::Remote *profile=new Viewer::Remote(*this,settingAdministrator);
 	connect(profile,&Viewer::Remote::Recognized,profile,[this](Viewer::Local profile) {
 		administratorID=profile.ID();
-		emit Initialize();
+		Initialize();
 	});
 	connect(profile,&Viewer::Remote::Unrecognized,this,[this]() {
 		AuthorizeUser();
diff --git a/security.h b/security.h
index f96024d..71fbd5f 100644
--- a/security.h
+++ b/security.h
@@ -53,6 +53,7 @@ private:
 	bool tokensInitialized;
 	QTimer tokenValidationTimer;
 	bool authorizing;
+	void Initialize();
 signals:
 	void TokenRequestFailed();
 	void Listening();
@@ -66,5 +67,4 @@ private slots:
 	void RewireConnected();
 	void RewireError(QMqttClient::ClientError error);
 	void RewireMessage(QMqttMessage messasge);
-	void Initialize();
 };
diff --git a/twitch.h b/twitch.h
index 2692d15..8b65856 100644
--- a/twitch.h
+++ b/twitch.h
@@ -15,6 +15,7 @@ namespace Twitch
 	inline const char *ENDPOINT_BADGES="chat/badges/global";
 	inline const char *ENDPOINT_SHOUTOUTS="chat/shoutouts";
 	inline const char *ENDPOINT_USERS="users";
+	inline const char *ENDPOINT_REDEMPTION_LIST="channel_points/custom_rewards";
 	inline const char *ENDPOINT_EVENTSUB="eventsub/subscriptions";
 	inline const char *ENDPOINT_EVENTSUB_SUBSCRIPTIONS="eventsub/subscriptions";
 
diff --git a/widgets.cpp b/widgets.cpp
index 1a45f06..8785328 100644
--- a/widgets.cpp
+++ b/widgets.cpp
@@ -310,6 +310,8 @@ namespace UI
 			QDialog::hideEvent(event);
 		}
 
+		QStringList Entry::redemptionNames;
+
 		Entry::Entry(Feedback::Error &errorReport,QWidget *parent) : QWidget(parent),
 			layout(this),
 			details(this),
@@ -325,6 +327,7 @@ namespace UI
 			random(u"Choose Random Media"_s,std::bind_front(&Entry::SetUpRandomCheckBox,this),&details),
 			duplicates(u"Allow Duplicates"_s,std::bind_front(&Entry::SetUpDuplicatesCheckBox,this),&details),
 			protect(u"Protect"_s,std::bind_front(&Entry::SetUpProtectCheckBox,this),&details),
+			redemption(u"Redemption"_s,std::bind_front(&Entry::SetUpRedemptionList,this),&details),
 			message(u"Message"_s,std::bind_front(&Entry::SetUpMessageTextEdit,this),&details),
 			errorReport(errorReport)
 		{
@@ -531,6 +534,7 @@ namespace UI
 				protect.Hide();
 				duplicates.Hide();
 				random.Hide();
+				redemption.Hide();
 				message.Hide();
 				browse.Hide();
 				aliases.Hide();
@@ -546,6 +550,7 @@ namespace UI
 				protect.Show();
 				duplicates.Show();
 				random.Show();
+				redemption.Show();
 				message.Show();
 				browse.Show();
 				aliases.Show();
@@ -629,6 +634,14 @@ namespace UI
 			detailsLayout.addWidget(widget,3,3,1,1);
 		}
 
+		void Entry::SetUpRedemptionList(QComboBox *widget)
+		{
+			widget->setEditable(true);
+			widget->lineEdit()->setPlaceholderText("Redemption");
+			widget->addItems(redemptionNames);
+			detailsLayout.addWidget(widget,4,0,1,4);
+		}
+
 		void Entry::SetUpMessageTextEdit(QTextEdit *widget)
 		{
 			widget->setPlaceholderText(u"Message to display in announcement"_s);
@@ -637,7 +650,7 @@ namespace UI
 			connect(widget,&QTextEdit::textChanged,this,&Entry::ValidateMessage);
 			connect(widget,&QTextEdit::textChanged,this,&Entry::UpdateMessage);
 			widget->viewport()->installEventFilter(this);
-			detailsLayout.addWidget(widget,4,0,1,4);
+			detailsLayout.addWidget(widget,5,0,1,4);
 		}
 
 		void Entry::SetUpBrowseButton(QPushButton *widget)
diff --git a/widgets.h b/widgets.h
index e1e3f7a..c26967c 100644
--- a/widgets.h
+++ b/widgets.h
@@ -343,6 +343,7 @@ namespace UI
 			QString Message() const;
 			bool Protected() const;
 			void ToggleFold();
+			static QStringList redemptionNames;
 		protected:
 			QGridLayout layout;
 			QFrame details;
@@ -358,6 +359,7 @@ namespace UI
 			EphemeralWidget<QCheckBox> random;
 			EphemeralWidget<QCheckBox> duplicates;
 			EphemeralWidget<QCheckBox> protect;
+			EphemeralWidget<QComboBox> redemption;
 			EphemeralWidget<QTextEdit> message;
 			Feedback::Error &errorReport;
 			void UpdateName();
@@ -380,6 +382,7 @@ namespace UI
 			void SetUpProtectCheckBox(QCheckBox *widget);
 			void SetUpRandomCheckBox(QCheckBox *widget);
 			void SetUpDuplicatesCheckBox(QCheckBox *widget);
+			void SetUpRedemptionList(QComboBox *widget);
 			void SetUpMessageTextEdit(QTextEdit *widget);
 			void SetUpBrowseButton(QPushButton *widget);
 			void SetUpAliasesButton(QPushButton *widget);

It appears I already had a POC for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant