feat(eventsub): implement channel.chat.user_message_(hold/update) (#6008)

This commit is contained in:
pajlada
2025-03-01 17:00:40 +01:00
committed by GitHub
parent f4541b0208
commit c03b883f05
22 changed files with 1123 additions and 1 deletions
@@ -145,6 +145,24 @@ public:
benchmark::DoNotOptimize(&metadata);
benchmark::DoNotOptimize(&payload);
}
void onChannelChatUserMessageHold(
const messages::Metadata &metadata,
const payload::channel_chat_user_message_hold::v1::Payload &payload)
override
{
benchmark::DoNotOptimize(&metadata);
benchmark::DoNotOptimize(&payload);
}
void onChannelChatUserMessageUpdate(
const messages::Metadata &metadata,
const payload::channel_chat_user_message_update::v1::Payload &payload)
override
{
benchmark::DoNotOptimize(&metadata);
benchmark::DoNotOptimize(&payload);
}
// NOLINTEND(cppcoreguidelines-pro-type-const-cast)
};
@@ -6,6 +6,8 @@
#include "twitch-eventsub-ws/payloads/channel-ban-v1.hpp"
#include "twitch-eventsub-ws/payloads/channel-chat-message-v1.hpp"
#include "twitch-eventsub-ws/payloads/channel-chat-notification-v1.hpp"
#include "twitch-eventsub-ws/payloads/channel-chat-user-message-hold-v1.hpp"
#include "twitch-eventsub-ws/payloads/channel-chat-user-message-update-v1.hpp"
#include "twitch-eventsub-ws/payloads/channel-moderate-v2.hpp"
#include "twitch-eventsub-ws/payloads/channel-suspicious-user-message-v1.hpp"
#include "twitch-eventsub-ws/payloads/channel-suspicious-user-update-v1.hpp"
@@ -74,6 +76,16 @@ public:
const messages::Metadata &metadata,
const payload::channel_suspicious_user_update::v1::Payload
&payload) = 0;
virtual void onChannelChatUserMessageHold(
const messages::Metadata &metadata,
const payload::channel_chat_user_message_hold::v1::Payload
&payload) = 0;
virtual void onChannelChatUserMessageUpdate(
const messages::Metadata &metadata,
const payload::channel_chat_user_message_update::v1::Payload
&payload) = 0;
// Add your new subscription types above this line
};
@@ -0,0 +1,37 @@
#pragma once
#include "twitch-eventsub-ws/payloads/structured-message.hpp"
#include "twitch-eventsub-ws/payloads/subscription.hpp"
#include "twitch-eventsub-ws/string.hpp"
#include <boost/json.hpp>
namespace chatterino::eventsub::lib::payload::channel_chat_user_message_hold::
v1 {
// {"subscription":{"id":"94ab19f1-dd41-41e7-a5bc-f8f45bd4342a","status":"enabled","type":"channel.chat.user_message_hold","version":"1","condition":{"broadcaster_user_id":"117166826","user_id":"159849156"},"transport":{"method":"websocket","session_id":"AgoQP515xn0yRxGvyU29dY9grBIGY2VsbC1j"},"created_at":"2025-03-01T10:40:14.527982426Z","cost":0},"event":{"broadcaster_user_id":"117166826","broadcaster_user_login":"testaccount_420","broadcaster_user_name":"테스트계정420","user_id":"159849156","user_login":"bajlada","user_name":"BajLada","message_id":"e39e3c58-3d25-49fd-8c94-e776ef57a7f8","message":{"text":"penis penis penis","fragments":[{"type":"text","text":"penis penis penis","cheermote":null,"emote":null}]}}}
struct Event {
// Broadcaster of the channel the message was sent in
String broadcasterUserID;
String broadcasterUserLogin;
String broadcasterUserName;
// User who sent the message
String userID;
String userLogin;
String userName;
String messageID;
chat::Message message;
};
struct Payload {
subscription::Subscription subscription;
Event event;
};
#include "twitch-eventsub-ws/payloads/channel-chat-user-message-hold-v1.inc"
} // namespace chatterino::eventsub::lib::payload::channel_chat_user_message_hold::v1
@@ -0,0 +1,5 @@
boost::json::result_for<Event, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<Event>, const boost::json::value &jvRoot);
boost::json::result_for<Payload, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<Payload>, const boost::json::value &jvRoot);
@@ -0,0 +1,49 @@
#pragma once
#include "twitch-eventsub-ws/payloads/structured-message.hpp"
#include "twitch-eventsub-ws/payloads/subscription.hpp"
#include "twitch-eventsub-ws/string.hpp"
#include <boost/json.hpp>
namespace chatterino::eventsub::lib::payload::channel_chat_user_message_update::
v1 {
// message approved:
// {"subscription":{"id":"3a9fd3ec-f5b5-49d7-8624-2195b3ebfda9","status":"enabled","type":"channel.chat.user_message_update","version":"1","condition":{"broadcaster_user_id":"117166826","user_id":"159849156"},"transport":{"method":"websocket","session_id":"AgoQBFnyoXrLQxiCmkHTvJ8VNBIGY2VsbC1j"},"created_at":"2025-03-01T11:29:49.532375428Z","cost":0},"event":{"broadcaster_user_id":"117166826","broadcaster_user_login":"testaccount_420","broadcaster_user_name":"테스트계정420","user_id":"159849156","user_login":"bajlada","user_name":"BajLada","status":"approved","message_id":"5686ec52-e02c-4042-ac21-1dfd8cab0f9f","message":{"text":"penis ass penis","fragments":[{"type":"text","text":"penis ass penis","cheermote":null,"emote":null}]}}}
// message denied:
// {"subscription":{"id":"3a9fd3ec-f5b5-49d7-8624-2195b3ebfda9","status":"enabled","type":"channel.chat.user_message_update","version":"1","condition":{"broadcaster_user_id":"117166826","user_id":"159849156"},"transport":{"method":"websocket","session_id":"AgoQBFnyoXrLQxiCmkHTvJ8VNBIGY2VsbC1j"},"created_at":"2025-03-01T11:29:49.532375428Z","cost":0},"event":{"broadcaster_user_id":"117166826","broadcaster_user_login":"testaccount_420","broadcaster_user_name":"테스트계정420","user_id":"159849156","user_login":"bajlada","user_name":"BajLada","status":"denied","message_id":"fc90dc3b-0634-41c3-8365-f40719f076ab","message":{"text":"penis ass penis","fragments":[{"type":"text","text":"penis ","cheermote":null,"emote":null},{"type":"text","text":"ass penis","cheermote":null,"emote":null}]}}}
enum class Status : std::uint8_t {
Approved,
Denied,
Invalid,
};
struct Event {
// Broadcaster of the channel the message was sent in
String broadcasterUserID;
String broadcasterUserLogin;
String broadcasterUserName;
// User who sent the message
String userID;
String userLogin;
String userName;
Status status;
String messageID;
chat::Message message;
};
struct Payload {
subscription::Subscription subscription;
Event event;
};
#include "twitch-eventsub-ws/payloads/channel-chat-user-message-update-v1.inc"
} // namespace chatterino::eventsub::lib::payload::channel_chat_user_message_update::v1
@@ -0,0 +1,8 @@
boost::json::result_for<Status, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<Status>, const boost::json::value &jvRoot);
boost::json::result_for<Event, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<Event>, const boost::json::value &jvRoot);
boost::json::result_for<Payload, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<Payload>, const boost::json::value &jvRoot);
@@ -12,6 +12,8 @@ generate_json_impls(
include/twitch-eventsub-ws/payloads/channel-ban-v1.hpp
include/twitch-eventsub-ws/payloads/channel-chat-message-v1.hpp
include/twitch-eventsub-ws/payloads/channel-chat-notification-v1.hpp
include/twitch-eventsub-ws/payloads/channel-chat-user-message-hold-v1.hpp
include/twitch-eventsub-ws/payloads/channel-chat-user-message-update-v1.hpp
include/twitch-eventsub-ws/payloads/channel-moderate-v2.hpp
include/twitch-eventsub-ws/payloads/channel-suspicious-user-message-v1.hpp
include/twitch-eventsub-ws/payloads/channel-suspicious-user-update-v1.hpp
@@ -0,0 +1,186 @@
// WARNING: This file is automatically generated. Any changes will be lost.
#include "twitch-eventsub-ws/chrono.hpp" // IWYU pragma: keep
#include "twitch-eventsub-ws/detail/errors.hpp"
#include "twitch-eventsub-ws/detail/variant.hpp" // IWYU pragma: keep
#include "twitch-eventsub-ws/payloads/channel-chat-user-message-hold-v1.hpp"
#include <boost/json.hpp>
namespace chatterino::eventsub::lib::payload::channel_chat_user_message_hold::
v1 {
boost::json::result_for<Event, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<Event> /* tag */,
const boost::json::value &jvRoot)
{
if (!jvRoot.is_object())
{
EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
}
const auto &root = jvRoot.get_object();
const auto *jvbroadcasterUserID = root.if_contains("broadcaster_user_id");
if (jvbroadcasterUserID == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto broadcasterUserID =
boost::json::try_value_to<String>(*jvbroadcasterUserID);
if (broadcasterUserID.has_error())
{
return broadcasterUserID.error();
}
const auto *jvbroadcasterUserLogin =
root.if_contains("broadcaster_user_login");
if (jvbroadcasterUserLogin == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto broadcasterUserLogin =
boost::json::try_value_to<String>(*jvbroadcasterUserLogin);
if (broadcasterUserLogin.has_error())
{
return broadcasterUserLogin.error();
}
const auto *jvbroadcasterUserName =
root.if_contains("broadcaster_user_name");
if (jvbroadcasterUserName == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto broadcasterUserName =
boost::json::try_value_to<String>(*jvbroadcasterUserName);
if (broadcasterUserName.has_error())
{
return broadcasterUserName.error();
}
const auto *jvuserID = root.if_contains("user_id");
if (jvuserID == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto userID = boost::json::try_value_to<String>(*jvuserID);
if (userID.has_error())
{
return userID.error();
}
const auto *jvuserLogin = root.if_contains("user_login");
if (jvuserLogin == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);
if (userLogin.has_error())
{
return userLogin.error();
}
const auto *jvuserName = root.if_contains("user_name");
if (jvuserName == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto userName = boost::json::try_value_to<String>(*jvuserName);
if (userName.has_error())
{
return userName.error();
}
const auto *jvmessageID = root.if_contains("message_id");
if (jvmessageID == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto messageID = boost::json::try_value_to<String>(*jvmessageID);
if (messageID.has_error())
{
return messageID.error();
}
const auto *jvmessage = root.if_contains("message");
if (jvmessage == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto message = boost::json::try_value_to<chat::Message>(*jvmessage);
if (message.has_error())
{
return message.error();
}
return Event{
.broadcasterUserID = std::move(broadcasterUserID.value()),
.broadcasterUserLogin = std::move(broadcasterUserLogin.value()),
.broadcasterUserName = std::move(broadcasterUserName.value()),
.userID = std::move(userID.value()),
.userLogin = std::move(userLogin.value()),
.userName = std::move(userName.value()),
.messageID = std::move(messageID.value()),
.message = std::move(message.value()),
};
}
boost::json::result_for<Payload, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<Payload> /* tag */,
const boost::json::value &jvRoot)
{
if (!jvRoot.is_object())
{
EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
}
const auto &root = jvRoot.get_object();
const auto *jvsubscription = root.if_contains("subscription");
if (jvsubscription == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto subscription =
boost::json::try_value_to<subscription::Subscription>(*jvsubscription);
if (subscription.has_error())
{
return subscription.error();
}
const auto *jvevent = root.if_contains("event");
if (jvevent == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto event = boost::json::try_value_to<Event>(*jvevent);
if (event.has_error())
{
return event.error();
}
return Payload{
.subscription = std::move(subscription.value()),
.event = std::move(event.value()),
};
}
} // namespace chatterino::eventsub::lib::payload::channel_chat_user_message_hold::v1
@@ -0,0 +1,229 @@
// WARNING: This file is automatically generated. Any changes will be lost.
#include "twitch-eventsub-ws/chrono.hpp" // IWYU pragma: keep
#include "twitch-eventsub-ws/detail/errors.hpp"
#include "twitch-eventsub-ws/detail/variant.hpp" // IWYU pragma: keep
#include "twitch-eventsub-ws/payloads/channel-chat-user-message-update-v1.hpp"
#include <boost/json.hpp>
namespace chatterino::eventsub::lib::payload::channel_chat_user_message_update::
v1 {
boost::json::result_for<Status, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<Status> /* tag */,
const boost::json::value &jvRoot)
{
if (!jvRoot.is_string())
{
EVENTSUB_BAIL_HERE(error::Kind::ExpectedString);
}
std::string_view eString(jvRoot.get_string());
using namespace std::string_view_literals;
if (eString == "approved"sv)
{
return Status::Approved;
}
if (eString == "denied"sv)
{
return Status::Denied;
}
if (eString == "invalid"sv)
{
return Status::Invalid;
}
EVENTSUB_BAIL_HERE(error::Kind::UnknownEnumValue);
}
boost::json::result_for<Event, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<Event> /* tag */,
const boost::json::value &jvRoot)
{
if (!jvRoot.is_object())
{
EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
}
const auto &root = jvRoot.get_object();
const auto *jvbroadcasterUserID = root.if_contains("broadcaster_user_id");
if (jvbroadcasterUserID == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto broadcasterUserID =
boost::json::try_value_to<String>(*jvbroadcasterUserID);
if (broadcasterUserID.has_error())
{
return broadcasterUserID.error();
}
const auto *jvbroadcasterUserLogin =
root.if_contains("broadcaster_user_login");
if (jvbroadcasterUserLogin == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto broadcasterUserLogin =
boost::json::try_value_to<String>(*jvbroadcasterUserLogin);
if (broadcasterUserLogin.has_error())
{
return broadcasterUserLogin.error();
}
const auto *jvbroadcasterUserName =
root.if_contains("broadcaster_user_name");
if (jvbroadcasterUserName == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto broadcasterUserName =
boost::json::try_value_to<String>(*jvbroadcasterUserName);
if (broadcasterUserName.has_error())
{
return broadcasterUserName.error();
}
const auto *jvuserID = root.if_contains("user_id");
if (jvuserID == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto userID = boost::json::try_value_to<String>(*jvuserID);
if (userID.has_error())
{
return userID.error();
}
const auto *jvuserLogin = root.if_contains("user_login");
if (jvuserLogin == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto userLogin = boost::json::try_value_to<String>(*jvuserLogin);
if (userLogin.has_error())
{
return userLogin.error();
}
const auto *jvuserName = root.if_contains("user_name");
if (jvuserName == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto userName = boost::json::try_value_to<String>(*jvuserName);
if (userName.has_error())
{
return userName.error();
}
static_assert(
std::is_trivially_copyable_v<
std::remove_reference_t<decltype(std::declval<Event>().status)>>);
const auto *jvstatus = root.if_contains("status");
if (jvstatus == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto status = boost::json::try_value_to<Status>(*jvstatus);
if (status.has_error())
{
return status.error();
}
const auto *jvmessageID = root.if_contains("message_id");
if (jvmessageID == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto messageID = boost::json::try_value_to<String>(*jvmessageID);
if (messageID.has_error())
{
return messageID.error();
}
const auto *jvmessage = root.if_contains("message");
if (jvmessage == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto message = boost::json::try_value_to<chat::Message>(*jvmessage);
if (message.has_error())
{
return message.error();
}
return Event{
.broadcasterUserID = std::move(broadcasterUserID.value()),
.broadcasterUserLogin = std::move(broadcasterUserLogin.value()),
.broadcasterUserName = std::move(broadcasterUserName.value()),
.userID = std::move(userID.value()),
.userLogin = std::move(userLogin.value()),
.userName = std::move(userName.value()),
.status = status.value(),
.messageID = std::move(messageID.value()),
.message = std::move(message.value()),
};
}
boost::json::result_for<Payload, boost::json::value>::type tag_invoke(
boost::json::try_value_to_tag<Payload> /* tag */,
const boost::json::value &jvRoot)
{
if (!jvRoot.is_object())
{
EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
}
const auto &root = jvRoot.get_object();
const auto *jvsubscription = root.if_contains("subscription");
if (jvsubscription == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto subscription =
boost::json::try_value_to<subscription::Subscription>(*jvsubscription);
if (subscription.has_error())
{
return subscription.error();
}
const auto *jvevent = root.if_contains("event");
if (jvevent == nullptr)
{
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
}
auto event = boost::json::try_value_to<Event>(*jvevent);
if (event.has_error())
{
return event.error();
}
return Payload{
.subscription = std::move(subscription.value()),
.event = std::move(event.value()),
};
}
} // namespace chatterino::eventsub::lib::payload::channel_chat_user_message_update::v1
+26
View File
@@ -212,6 +212,32 @@ namespace {
return boost::system::error_code{};
},
},
{
{"channel.chat.user_message_hold", "1"},
[](const auto &metadata, const auto &jv, auto &listener) {
auto oPayload = parsePayload<
payload::channel_chat_user_message_hold::v1::Payload>(jv);
if (!oPayload)
{
return oPayload.error();
}
listener->onChannelChatUserMessageHold(metadata, *oPayload);
return boost::system::error_code{};
},
},
{
{"channel.chat.user_message_update", "1"},
[](const auto &metadata, const auto &jv, auto &listener) {
auto oPayload = parsePayload<
payload::channel_chat_user_message_update::v1::Payload>(jv);
if (!oPayload)
{
return oPayload.error();
}
listener->onChannelChatUserMessageUpdate(metadata, *oPayload);
return boost::system::error_code{};
},
},
// Add your new subscription types above this line
};
@@ -129,6 +129,18 @@ class NoOpListener : public chatterino::eventsub::lib::Listener
override
{
}
void onChannelChatUserMessageHold(
const messages::Metadata &metadata,
const payload::channel_chat_user_message_hold::v1::Payload &payload)
override
{
}
void onChannelChatUserMessageUpdate(
const messages::Metadata &metadata,
const payload::channel_chat_user_message_update::v1::Payload &payload)
override
{
}
};
} // namespace