feat: add initial experimental Twitch Eventsub support (#5837)
Co-authored-by: nerix <nerixdev@outlook.de>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
|
||||
namespace chatterino::eventsub::lib {
|
||||
|
||||
struct AsISO8601 {
|
||||
};
|
||||
|
||||
boost::json::result_for<std::chrono::system_clock::time_point,
|
||||
boost::json::value>::type
|
||||
tag_invoke(
|
||||
boost::json::try_value_to_tag<std::chrono::system_clock::time_point>,
|
||||
const boost::json::value &jvRoot, const AsISO8601 &);
|
||||
|
||||
} // namespace chatterino::eventsub::lib
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/system/error_category.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chatterino::eventsub::lib::error {
|
||||
|
||||
class ApplicationErrorCategory final : public boost::system::error_category
|
||||
{
|
||||
const std::string innerMessage;
|
||||
|
||||
public:
|
||||
ApplicationErrorCategory(const char *_innerMessage)
|
||||
: innerMessage(_innerMessage)
|
||||
{
|
||||
}
|
||||
|
||||
explicit ApplicationErrorCategory(std::string _innerMessage)
|
||||
: innerMessage(std::move(_innerMessage))
|
||||
{
|
||||
}
|
||||
|
||||
const char *name() const noexcept override
|
||||
{
|
||||
return "Application JSON error";
|
||||
}
|
||||
std::string message(int /*ev*/) const override
|
||||
{
|
||||
return this->innerMessage;
|
||||
}
|
||||
};
|
||||
|
||||
const ApplicationErrorCategory EXPECTED_OBJECT{"Expected object"};
|
||||
const ApplicationErrorCategory MISSING_KEY{"Missing key"};
|
||||
|
||||
} // namespace chatterino::eventsub::lib::error
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/json/object.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
namespace chatterino::eventsub::lib {
|
||||
|
||||
template <typename T>
|
||||
std::optional<T> readMember(const boost::json::object &obj,
|
||||
std::string_view key)
|
||||
{
|
||||
const auto *it = obj.find(key);
|
||||
|
||||
if (it == obj.end())
|
||||
{
|
||||
// No member with the key found
|
||||
std::cerr << "No member with the key " << key << " found\n";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto result = boost::json::try_value_to<T>(it->value());
|
||||
if (!result.has_value())
|
||||
{
|
||||
std::cerr << key << " could not be deserialized to the desired type\n";
|
||||
// Member could not be serialized to this type
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return result.value();
|
||||
}
|
||||
|
||||
} // namespace chatterino::eventsub::lib
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "twitch-eventsub-ws/messages/metadata.hpp"
|
||||
#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-moderate-v2.hpp"
|
||||
#include "twitch-eventsub-ws/payloads/channel-update-v1.hpp"
|
||||
#include "twitch-eventsub-ws/payloads/session-welcome.hpp"
|
||||
#include "twitch-eventsub-ws/payloads/stream-offline-v1.hpp"
|
||||
#include "twitch-eventsub-ws/payloads/stream-online-v1.hpp"
|
||||
|
||||
namespace chatterino::eventsub::lib {
|
||||
|
||||
class Listener
|
||||
{
|
||||
public:
|
||||
virtual ~Listener() = default;
|
||||
|
||||
virtual void onSessionWelcome(
|
||||
messages::Metadata metadata,
|
||||
payload::session_welcome::Payload payload) = 0;
|
||||
|
||||
virtual void onNotification(messages::Metadata metadata,
|
||||
const boost::json::value &jv) = 0;
|
||||
|
||||
// Subscription types
|
||||
virtual void onChannelBan(messages::Metadata metadata,
|
||||
payload::channel_ban::v1::Payload payload) = 0;
|
||||
|
||||
virtual void onStreamOnline(
|
||||
messages::Metadata metadata,
|
||||
payload::stream_online::v1::Payload payload) = 0;
|
||||
|
||||
virtual void onStreamOffline(
|
||||
messages::Metadata metadata,
|
||||
payload::stream_offline::v1::Payload payload) = 0;
|
||||
|
||||
virtual void onChannelChatNotification(
|
||||
messages::Metadata metadata,
|
||||
payload::channel_chat_notification::v1::Payload payload) = 0;
|
||||
|
||||
virtual void onChannelUpdate(
|
||||
messages::Metadata metadata,
|
||||
payload::channel_update::v1::Payload payload) = 0;
|
||||
|
||||
virtual void onChannelChatMessage(
|
||||
messages::Metadata metadata,
|
||||
payload::channel_chat_message::v1::Payload payload) = 0;
|
||||
|
||||
virtual void onChannelModerate(
|
||||
messages::Metadata metadata,
|
||||
payload::channel_moderate::v2::Payload payload) = 0;
|
||||
|
||||
// Add your new subscription types above this line
|
||||
};
|
||||
|
||||
} // namespace chatterino::eventsub::lib
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "twitch-eventsub-ws/errors.hpp"
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace chatterino::eventsub::lib::messages {
|
||||
|
||||
/*
|
||||
{
|
||||
"metadata": {
|
||||
"message_id": "40cc68b8-dc5b-a46e-0388-a7c9193eec5e",
|
||||
"message_type": "session_welcome",
|
||||
"message_timestamp": "2023-05-14T12:31:47.995298776Z"
|
||||
"subscription_type": "channel.unban", // only included on message_type=notification
|
||||
"subscription_version": "1" // only included on message_type=notification
|
||||
},
|
||||
"payload": ...
|
||||
}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Metadata {
|
||||
const std::string messageID;
|
||||
const std::string messageType;
|
||||
// TODO: should this be chronofied?
|
||||
const std::string messageTimestamp;
|
||||
|
||||
const std::optional<std::string> subscriptionType;
|
||||
const std::optional<std::string> subscriptionVersion;
|
||||
};
|
||||
|
||||
// DESERIALIZATION DEFINITION START
|
||||
boost::json::result_for<Metadata, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Metadata>, const boost::json::value &jvRoot);
|
||||
// DESERIALIZATION DEFINITION END
|
||||
|
||||
} // namespace chatterino::eventsub::lib::messages
|
||||
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "twitch-eventsub-ws/payloads/subscription.hpp"
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
namespace chatterino::eventsub::lib::payload::channel_ban::v1 {
|
||||
|
||||
/*
|
||||
{
|
||||
"metadata": ...,
|
||||
"payload": {
|
||||
"subscription": {
|
||||
"id": "4aa632e0-fca3-590b-e981-bbd12abdb3fe",
|
||||
"status": "enabled",
|
||||
"type": "channel.ban",
|
||||
"version": "1",
|
||||
"condition": {
|
||||
"broadcaster_user_id": "74378979"
|
||||
},
|
||||
"transport": {
|
||||
"method": "websocket",
|
||||
"session_id": "38de428e_b11f07be"
|
||||
},
|
||||
"created_at": "2023-05-20T12:30:55.518375571Z",
|
||||
"cost": 0
|
||||
},
|
||||
"event": {
|
||||
"banned_at": "2023-05-20T12:30:55.518375571Z",
|
||||
"broadcaster_user_id": "74378979",
|
||||
"broadcaster_user_login": "testBroadcaster",
|
||||
"broadcaster_user_name": "testBroadcaster",
|
||||
"ends_at": "2023-05-20T12:40:55.518375571Z",
|
||||
"is_permanent": false,
|
||||
"moderator_user_id": "29024944",
|
||||
"moderator_user_login": "CLIModerator",
|
||||
"moderator_user_name": "CLIModerator",
|
||||
"reason": "This is a test event",
|
||||
"user_id": "40389552",
|
||||
"user_login": "testFromUser",
|
||||
"user_name": "testFromUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Event {
|
||||
// User ID (e.g. 117166826) of the user who's channel the event took place in
|
||||
std::string broadcasterUserID;
|
||||
// User Login (e.g. testaccount_420) of the user who's channel the event took place in
|
||||
std::string broadcasterUserLogin;
|
||||
// User Name (e.g. 테스트계정420) of the user who's channel the event took place in
|
||||
std::string broadcasterUserName;
|
||||
|
||||
// User ID (e.g. 117166826) of the user who took the action
|
||||
std::string moderatorUserID;
|
||||
// User Login (e.g. testaccount_420) of the user who took the action
|
||||
std::string moderatorUserLogin;
|
||||
// User Name (e.g. 테스트계정420) of the user who took the action
|
||||
std::string moderatorUserName;
|
||||
|
||||
// User ID (e.g. 117166826) of the user who was timed out or banned
|
||||
std::string userID;
|
||||
// User Login (e.g. testaccount_420) of the user who was timed out or banned
|
||||
std::string userLogin;
|
||||
// User Name (e.g. 테스트계정420) of the user who was timed out or banned
|
||||
std::string userName;
|
||||
|
||||
// Reason given for the timeout or ban.
|
||||
// If no reason was specified, this string is empty
|
||||
std::string reason;
|
||||
|
||||
// Set to true if this was a ban.
|
||||
// If this is false, this event describes a timeout
|
||||
bool isPermanent;
|
||||
|
||||
// Time point when the timeout or ban took place
|
||||
/// json_tag=AsISO8601
|
||||
std::chrono::system_clock::time_point bannedAt;
|
||||
|
||||
// Time point when the timeout will end
|
||||
/// json_tag=AsISO8601
|
||||
std::optional<std::chrono::system_clock::time_point> endsAt;
|
||||
|
||||
// Returns the duration of the timeout
|
||||
// If this event describes a ban, the value returned won't make sense
|
||||
std::chrono::system_clock::duration timeoutDuration() const;
|
||||
};
|
||||
|
||||
struct Payload {
|
||||
const subscription::Subscription subscription;
|
||||
|
||||
const Event event;
|
||||
};
|
||||
|
||||
// DESERIALIZATION DEFINITION START
|
||||
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);
|
||||
// DESERIALIZATION DEFINITION END
|
||||
|
||||
} // namespace chatterino::eventsub::lib::payload::channel_ban::v1
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
#pragma once
|
||||
|
||||
#include "twitch-eventsub-ws/payloads/subscription.hpp"
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
{
|
||||
...,
|
||||
"event": {
|
||||
"broadcaster_user_id": "117166826",
|
||||
"broadcaster_user_login": "testaccount_420",
|
||||
"broadcaster_user_name": "테스트계정420",
|
||||
"chatter_user_id": "100135110",
|
||||
"chatter_user_login": "streamelements",
|
||||
"chatter_user_name": "StreamElements",
|
||||
"message_id": "7732e85c-4543-464f-9d84-533e73f71459",
|
||||
"message": {
|
||||
"text": "https://youtu.be/v515yo0Ad_M",
|
||||
"fragments": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "https://youtu.be/v515yo0Ad_M",
|
||||
"cheermote": null,
|
||||
"emote": null,
|
||||
"mention": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"color": "#5B99FF",
|
||||
"badges": [
|
||||
{
|
||||
"set_id": "moderator",
|
||||
"id": "1",
|
||||
"info": ""
|
||||
},
|
||||
{
|
||||
"set_id": "partner",
|
||||
"id": "1",
|
||||
"info": ""
|
||||
}
|
||||
],
|
||||
"message_type": "text",
|
||||
"cheer": null,
|
||||
"reply": null,
|
||||
"channel_points_custom_reward_id": null
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
namespace chatterino::eventsub::lib::payload::channel_chat_message::v1 {
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Badge {
|
||||
std::string setID;
|
||||
std::string id;
|
||||
std::string info;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Cheermote {
|
||||
std::string prefix;
|
||||
int bits;
|
||||
int tier;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Emote {
|
||||
std::string id;
|
||||
std::string emoteSetID;
|
||||
std::string ownerID;
|
||||
std::vector<std::string> format;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Mention {
|
||||
std::string userID;
|
||||
std::string userName;
|
||||
std::string userLogin;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct MessageFragment {
|
||||
std::string type;
|
||||
std::string text;
|
||||
std::optional<Cheermote> cheermote;
|
||||
std::optional<Emote> emote;
|
||||
std::optional<Mention> mention;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Message {
|
||||
std::string text;
|
||||
std::vector<MessageFragment> fragments;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Cheer {
|
||||
int bits;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Reply {
|
||||
std::string parentMessageID;
|
||||
std::string parentUserID;
|
||||
std::string parentUserLogin;
|
||||
std::string parentUserName;
|
||||
std::string parentMessageBody;
|
||||
|
||||
std::string threadMessageID;
|
||||
std::string threadUserID;
|
||||
std::string threadUserLogin;
|
||||
std::string threadUserName;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Event {
|
||||
// Broadcaster of the channel the message was sent in
|
||||
std::string broadcasterUserID;
|
||||
std::string broadcasterUserLogin;
|
||||
std::string broadcasterUserName;
|
||||
|
||||
// User who sent the message
|
||||
std::string chatterUserID;
|
||||
std::string chatterUserLogin;
|
||||
std::string chatterUserName;
|
||||
|
||||
// Color of the user who sent the message
|
||||
std::string color;
|
||||
|
||||
std::vector<Badge> badges;
|
||||
|
||||
std::string messageID;
|
||||
std::string messageType;
|
||||
Message message;
|
||||
|
||||
std::optional<Cheer> cheer;
|
||||
std::optional<Reply> reply;
|
||||
std::optional<std::string> channelPointsCustomRewardID;
|
||||
};
|
||||
|
||||
struct Payload {
|
||||
const subscription::Subscription subscription;
|
||||
|
||||
const Event event;
|
||||
};
|
||||
|
||||
// DESERIALIZATION DEFINITION START
|
||||
boost::json::result_for<Badge, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Badge>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Cheermote, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Cheermote>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Emote, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Emote>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Mention, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Mention>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<MessageFragment, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<MessageFragment>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Message, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Message>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Cheer, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Cheer>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Reply, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Reply>, 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);
|
||||
// DESERIALIZATION DEFINITION END
|
||||
|
||||
} // namespace chatterino::eventsub::lib::payload::channel_chat_message::v1
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
#pragma once
|
||||
|
||||
#include "twitch-eventsub-ws/payloads/subscription.hpp"
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino::eventsub::lib::payload::channel_chat_notification::v1 {
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Badge {
|
||||
std::string setID;
|
||||
std::string id;
|
||||
std::string info;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Cheermote {
|
||||
std::string prefix;
|
||||
int bits;
|
||||
int tier;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Emote {
|
||||
std::string id;
|
||||
std::string emoteSetID;
|
||||
std::string ownerID;
|
||||
std::vector<std::string> format;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Mention {
|
||||
std::string userID;
|
||||
std::string userName;
|
||||
std::string userLogin;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct MessageFragment {
|
||||
std::string type;
|
||||
std::string text;
|
||||
std::optional<Cheermote> cheermote;
|
||||
std::optional<Emote> emote;
|
||||
std::optional<Mention> mention;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Subcription {
|
||||
std::string subTier;
|
||||
bool isPrime;
|
||||
int durationMonths;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Resubscription {
|
||||
int cumulativeMonths;
|
||||
int durationMonths;
|
||||
std::optional<int> streakMonths;
|
||||
std::string subTier;
|
||||
bool isPrime;
|
||||
bool isGift;
|
||||
bool gifterIsAnonymous;
|
||||
std::optional<std::string> gifterUserID;
|
||||
std::optional<std::string> gifterUserName;
|
||||
std::optional<std::string> gifterUserLogin;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct GiftSubscription {
|
||||
int durationMonths;
|
||||
std::optional<int> cumulativeTotal;
|
||||
std::optional<int> streakMonths;
|
||||
std::string recipientUserID;
|
||||
std::string recipientUserName;
|
||||
std::string recipientUserLogin;
|
||||
std::string subTier;
|
||||
std::optional<std::string> communityGiftID;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct CommunityGiftSubscription {
|
||||
std::string id;
|
||||
int total;
|
||||
std::string subTier;
|
||||
std::optional<int> cumulativeTotal;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct GiftPaidUpgrade {
|
||||
bool gifterIsAnonymous;
|
||||
std::optional<std::string> gifterUserID;
|
||||
std::optional<std::string> gifterUserName;
|
||||
std::optional<std::string> gifterUserLogin;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct PrimePaidUpgrade {
|
||||
std::string subTier;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Raid {
|
||||
std::string userID;
|
||||
std::string userName;
|
||||
std::string userLogin;
|
||||
int viewerCount;
|
||||
std::string profileImageURL;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Unraid {
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct PayItForward {
|
||||
bool gifterIsAnonymous;
|
||||
std::optional<std::string> gifterUserID;
|
||||
std::optional<std::string> gifterUserName;
|
||||
std::optional<std::string> gifterUserLogin;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Announcement {
|
||||
std::string color;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct CharityDonationAmount {
|
||||
int value;
|
||||
int decimalPlaces;
|
||||
std::string currency;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct CharityDonation {
|
||||
std::string charityName;
|
||||
CharityDonationAmount amount;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct BitsBadgeTier {
|
||||
int tier;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Message {
|
||||
std::string text;
|
||||
std::vector<MessageFragment> fragments;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Event {
|
||||
std::string broadcasterUserID;
|
||||
std::string broadcasterUserLogin;
|
||||
std::string broadcasterUserName;
|
||||
std::string chatterUserID;
|
||||
std::string chatterUserLogin;
|
||||
std::string chatterUserName;
|
||||
bool chatterIsAnonymous;
|
||||
std::string color;
|
||||
std::vector<Badge> badges;
|
||||
std::string systemMessage;
|
||||
std::string messageID;
|
||||
Message message;
|
||||
std::string noticeType;
|
||||
std::optional<Subcription> sub;
|
||||
std::optional<Resubscription> resub;
|
||||
std::optional<GiftSubscription> subGift;
|
||||
std::optional<CommunityGiftSubscription> communitySubGift;
|
||||
std::optional<GiftPaidUpgrade> giftPaidUpgrade;
|
||||
std::optional<PrimePaidUpgrade> primePaidUpgrade;
|
||||
std::optional<Raid> raid;
|
||||
std::optional<Unraid> unraid;
|
||||
std::optional<PayItForward> payItForward;
|
||||
std::optional<Announcement> announcement;
|
||||
std::optional<CharityDonation> charityDonation;
|
||||
std::optional<BitsBadgeTier> bitsBadgeTier;
|
||||
};
|
||||
|
||||
struct Payload {
|
||||
const subscription::Subscription subscription;
|
||||
|
||||
const Event event;
|
||||
};
|
||||
|
||||
// DESERIALIZATION DEFINITION START
|
||||
boost::json::result_for<Badge, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Badge>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Cheermote, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Cheermote>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Emote, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Emote>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Mention, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Mention>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<MessageFragment, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<MessageFragment>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Subcription, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Subcription>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Resubscription, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Resubscription>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<GiftSubscription, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<GiftSubscription>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<CommunityGiftSubscription, boost::json::value>::type
|
||||
tag_invoke(boost::json::try_value_to_tag<CommunityGiftSubscription>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<GiftPaidUpgrade, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<GiftPaidUpgrade>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<PrimePaidUpgrade, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<PrimePaidUpgrade>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Raid, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Raid>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Unraid, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Unraid>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<PayItForward, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<PayItForward>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Announcement, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Announcement>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<CharityDonationAmount, boost::json::value>::type
|
||||
tag_invoke(boost::json::try_value_to_tag<CharityDonationAmount>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<CharityDonation, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<CharityDonation>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<BitsBadgeTier, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<BitsBadgeTier>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Message, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Message>, 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);
|
||||
// DESERIALIZATION DEFINITION END
|
||||
|
||||
} // namespace chatterino::eventsub::lib::payload::channel_chat_notification::v1
|
||||
@@ -0,0 +1,452 @@
|
||||
#pragma once
|
||||
|
||||
#include "twitch-eventsub-ws/payloads/subscription.hpp"
|
||||
#include "twitch-eventsub-ws/string.hpp"
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chatterino::eventsub::lib::payload::channel_moderate::v2 {
|
||||
|
||||
/* follower mode enabled
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"followers","followers":{"follow_duration_minutes":0},"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/* follower mode disabled
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"followersoff","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/* follower mode set to 7d
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"followers","followers":{"follow_duration_minutes":10080},"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Followers {
|
||||
int followDurationMinutes;
|
||||
};
|
||||
|
||||
/* slow mode set to 30s
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"slow","followers":null,"slow":{"wait_time_seconds":30},"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/* slow mode off
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"slowoff","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Slow {
|
||||
int waitTimeSeconds;
|
||||
};
|
||||
|
||||
/* User is VIP'ed
|
||||
{
|
||||
"subscription": {
|
||||
"id": "ef1d3e66-0001-4cdc-8e74-8746f9243704",
|
||||
"status": "enabled",
|
||||
"type": "channel.moderate",
|
||||
"version": "2",
|
||||
"condition": {
|
||||
"broadcaster_user_id": "11148817",
|
||||
"moderator_user_id": "117166826"
|
||||
},
|
||||
"transport": {
|
||||
"method": "websocket",
|
||||
"session_id": "AgoQBV1S6bGuR7e2hs04k4J1EhIGY2VsbC1j"
|
||||
},
|
||||
"created_at": "2025-02-01T11:55:30.114060889Z",
|
||||
"cost": 0
|
||||
},
|
||||
"event": {
|
||||
"broadcaster_user_id": "11148817",
|
||||
"broadcaster_user_login": "pajlada",
|
||||
"broadcaster_user_name": "pajlada",
|
||||
"source_broadcaster_user_id": null,
|
||||
"source_broadcaster_user_login": null,
|
||||
"source_broadcaster_user_name": null,
|
||||
"moderator_user_id": "11148817",
|
||||
"moderator_user_login": "pajlada",
|
||||
"moderator_user_name": "pajlada",
|
||||
"action": "vip",
|
||||
"followers": null,
|
||||
"slow": null,
|
||||
"vip": {
|
||||
"user_id": "159849156",
|
||||
"user_login": "bajlada",
|
||||
"user_name": "BajLada"
|
||||
},
|
||||
"unvip": null,
|
||||
"mod": null,
|
||||
"unmod": null,
|
||||
"ban": null,
|
||||
"unban": null,
|
||||
"timeout": null,
|
||||
"untimeout": null,
|
||||
"raid": null,
|
||||
"unraid": null,
|
||||
"delete": null,
|
||||
"automod_terms": null,
|
||||
"unban_request": null,
|
||||
"warn": null,
|
||||
"shared_chat_ban": null,
|
||||
"shared_chat_unban": null,
|
||||
"shared_chat_timeout": null,
|
||||
"shared_chat_untimeout": null,
|
||||
"shared_chat_delete": null
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Vip {
|
||||
String userID;
|
||||
String userLogin;
|
||||
String userName;
|
||||
};
|
||||
|
||||
/* user is unvipped
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"unvip","followers":null,"slow":null,"vip":null,"unvip":{"user_id":"159849156","user_login":"bajlada","user_name":"BajLada"},"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Unvip {
|
||||
String userID;
|
||||
String userLogin;
|
||||
String userName;
|
||||
};
|
||||
|
||||
/* user is modded
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"mod","followers":null,"slow":null,"vip":null,"unvip":null,"mod":{"user_id":"159849156","user_login":"bajlada","user_name":"BajLada"},"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Mod {
|
||||
std::string userID;
|
||||
std::string userLogin;
|
||||
std::string userName;
|
||||
};
|
||||
|
||||
/* user is unmodded
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"unmod","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":{"user_id":"159849156","user_login":"bajlada","user_name":"BajLada"},"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Unmod {
|
||||
std::string userID;
|
||||
std::string userLogin;
|
||||
std::string userName;
|
||||
};
|
||||
|
||||
/* user is banned with reason
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"ban","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":{"user_id":"70948394","user_login":"weeb123","user_name":"WEEB123","reason":"this is the reason"},"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/* user is banned without reason
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"ban","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":{"user_id":"70948394","user_login":"weeb123","user_name":"WEEB123","reason":""},"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Ban {
|
||||
std::string userID;
|
||||
std::string userLogin;
|
||||
std::string userName;
|
||||
// TODO: Verify that we handle null here
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
/* user is unbanned
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"unban","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":{"user_id":"70948394","user_login":"weeb123","user_name":"WEEB123"},"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Unban {
|
||||
std::string userID;
|
||||
std::string userLogin;
|
||||
std::string userName;
|
||||
};
|
||||
|
||||
/* user is timed out without reason
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"timeout","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":{"user_id":"70948394","user_login":"weeb123","user_name":"WEEB123","reason":"","expires_at":"2025-02-01T12:11:02.684499409Z"},"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/* user is timed out with reason
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"timeout","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":{"user_id":"70948394","user_login":"weeb123","user_name":"WEEB123","reason":"this is the reason","expires_at":"2025-02-01T12:11:15.859552916Z"},"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Timeout {
|
||||
std::string userID;
|
||||
std::string userLogin;
|
||||
std::string userName;
|
||||
// TODO: Verify that we handle null here
|
||||
std::string reason;
|
||||
// TODO: This should be a timestamp?
|
||||
std::string expiresAt;
|
||||
};
|
||||
|
||||
/* user is untimeouted
|
||||
{"subscription":{"id":"86d99e53-2837-40cf-bc6e-c6e00698919c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQZgfnyKxXQ32hpzCWF4aCGBIGY2VsbC1j"},"created_at":"2025-02-01T12:02:16.005321321Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"untimeout","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":{"user_id":"70948394","user_login":"weeb123","user_name":"WEEB123"},"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Untimeout {
|
||||
std::string userID;
|
||||
std::string userLogin;
|
||||
std::string userName;
|
||||
};
|
||||
|
||||
/* channel is raided (from bajlada to pajlada)
|
||||
{"subscription":{"id":"e7b45c7a-9b4d-4101-8d7d-92e8945c26fa","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"159849156","moderator_user_id":"159849156"},"transport":{"method":"websocket","session_id":"AgoQEPRIfB3SQTCSkJM2zznvNxIGY2VsbC1j"},"created_at":"2025-02-01T12:12:45.685831769Z","cost":0},"event":{"broadcaster_user_id":"159849156","broadcaster_user_login":"bajlada","broadcaster_user_name":"BajLada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"159849156","moderator_user_login":"bajlada","moderator_user_name":"BajLada","action":"raid","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":{"user_id":"11148817","user_login":"pajlada","user_name":"pajlada","viewer_count":0},"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Raid {
|
||||
std::string userID;
|
||||
std::string userLogin;
|
||||
std::string userName;
|
||||
|
||||
int viewerCount;
|
||||
};
|
||||
|
||||
/* raid from bajlada to pajlada was cancelled
|
||||
{"subscription":{"id":"e7b45c7a-9b4d-4101-8d7d-92e8945c26fa","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"159849156","moderator_user_id":"159849156"},"transport":{"method":"websocket","session_id":"AgoQEPRIfB3SQTCSkJM2zznvNxIGY2VsbC1j"},"created_at":"2025-02-01T12:12:45.685831769Z","cost":0},"event":{"broadcaster_user_id":"159849156","broadcaster_user_login":"bajlada","broadcaster_user_name":"BajLada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"159849156","moderator_user_login":"bajlada","moderator_user_name":"BajLada","action":"unraid","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":{"user_id":"11148817","user_login":"pajlada","user_name":"pajlada"},"delete":null,"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Unraid {
|
||||
std::string userID;
|
||||
std::string userLogin;
|
||||
std::string userName;
|
||||
};
|
||||
|
||||
/* message deleted
|
||||
{"subscription":{"id":"4284c08c-402a-43a8-8537-1e75f38f562c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQfS3RYz3MSqOophais4HEjxIGY2VsbC1j"},"created_at":"2025-02-01T12:15:13.083098352Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"delete","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":{"user_id":"232490245","user_login":"namtheweebs","user_name":"NaMTheWeebs","message_id":"6c9bd28b-779b-4607-b66f-5c336e1ae29e","message_body":"bajlada raid NomNom"},"automod_terms":null,"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Delete {
|
||||
std::string userID;
|
||||
std::string userLogin;
|
||||
std::string userName;
|
||||
std::string messageID;
|
||||
std::string messageBody;
|
||||
};
|
||||
|
||||
/* automodded message approved
|
||||
{"subscription":{"id":"0ccc8f11-7e77-40cf-84a9-25ab934c30fb","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"117166826","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQfS3RYz3MSqOophais4HEjxIGY2VsbC1j"},"created_at":"2025-02-01T12:15:13.092673205Z","cost":0},"event":{"broadcaster_user_id":"117166826","broadcaster_user_login":"testaccount_420","broadcaster_user_name":"테스트계정420","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"117166826","moderator_user_login":"testaccount_420","moderator_user_name":"테스트계정420","action":"add_permitted_term","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":{"action":"add","list":"permitted","terms":["cock cock cock penis sex cock"],"from_automod":true},"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/* automodded message denied
|
||||
{"subscription":{"id":"0ccc8f11-7e77-40cf-84a9-25ab934c30fb","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"117166826","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQfS3RYz3MSqOophais4HEjxIGY2VsbC1j"},"created_at":"2025-02-01T12:15:13.092673205Z","cost":0},"event":{"broadcaster_user_id":"117166826","broadcaster_user_login":"testaccount_420","broadcaster_user_name":"테스트계정420","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"117166826","moderator_user_login":"testaccount_420","moderator_user_name":"테스트계정420","action":"add_blocked_term","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":{"action":"add","list":"blocked","terms":["boobies"],"from_automod":true},"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
|
||||
{"subscription":{"id":"0ccc8f11-7e77-40cf-84a9-25ab934c30fb","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"117166826","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQfS3RYz3MSqOophais4HEjxIGY2VsbC1j"},"created_at":"2025-02-01T12:15:13.092673205Z","cost":0},"event":{"broadcaster_user_id":"117166826","broadcaster_user_login":"testaccount_420","broadcaster_user_name":"테스트계정420","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"117166826","moderator_user_login":"testaccount_420","moderator_user_name":"테스트계정420","action":"add_blocked_term","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":{"action":"add","list":"blocked","terms":["cock cock cock penis sex cock penis penis cock"],"from_automod":true},"unban_request":null,"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct AutomodTerms {
|
||||
// either add or remove
|
||||
std::string action;
|
||||
// either blocked or permitted
|
||||
std::string list;
|
||||
|
||||
std::vector<std::string> terms;
|
||||
bool fromAutomod;
|
||||
};
|
||||
|
||||
/* unban request approved
|
||||
{"subscription":{"id":"4284c08c-402a-43a8-8537-1e75f38f562c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQfS3RYz3MSqOophais4HEjxIGY2VsbC1j"},"created_at":"2025-02-01T12:15:13.083098352Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"approve_unban_request","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":{"is_approved":true,"user_id":"159849156","user_login":"bajlada","user_name":"BajLada","moderator_message":"you have been granted mercy"},"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/* unban request denied
|
||||
{"subscription":{"id":"4284c08c-402a-43a8-8537-1e75f38f562c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQfS3RYz3MSqOophais4HEjxIGY2VsbC1j"},"created_at":"2025-02-01T12:15:13.083098352Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"deny_unban_request","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":{"is_approved":false,"user_id":"975285839","user_login":"selenatormapguy","user_name":"selenatormapguy","moderator_message":""},"warn":null,"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct UnbanRequest {
|
||||
bool isApproved;
|
||||
|
||||
std::string userID;
|
||||
std::string userLogin;
|
||||
std::string userName;
|
||||
|
||||
std::string moderatorMessage;
|
||||
};
|
||||
|
||||
/* freetext warn from chatterino
|
||||
{"subscription":{"id":"4284c08c-402a-43a8-8537-1e75f38f562c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQfS3RYz3MSqOophais4HEjxIGY2VsbC1j"},"created_at":"2025-02-01T12:15:13.083098352Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"warn","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":{"user_id":"159849156","user_login":"bajlada","user_name":"BajLada","reason":"this is a test warning from chatterino","chat_rules_cited":null},"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/* warning from web ui
|
||||
{"subscription":{"id":"4284c08c-402a-43a8-8537-1e75f38f562c","status":"enabled","type":"channel.moderate","version":"2","condition":{"broadcaster_user_id":"11148817","moderator_user_id":"117166826"},"transport":{"method":"websocket","session_id":"AgoQfS3RYz3MSqOophais4HEjxIGY2VsbC1j"},"created_at":"2025-02-01T12:15:13.083098352Z","cost":0},"event":{"broadcaster_user_id":"11148817","broadcaster_user_login":"pajlada","broadcaster_user_name":"pajlada","source_broadcaster_user_id":null,"source_broadcaster_user_login":null,"source_broadcaster_user_name":null,"moderator_user_id":"11148817","moderator_user_login":"pajlada","moderator_user_name":"pajlada","action":"warn","followers":null,"slow":null,"vip":null,"unvip":null,"mod":null,"unmod":null,"ban":null,"unban":null,"timeout":null,"untimeout":null,"raid":null,"unraid":null,"delete":null,"automod_terms":null,"unban_request":null,"warn":{"user_id":"159849156","user_login":"bajlada","user_name":"BajLada","reason":"and a custom reason","chat_rules_cited":["Rule 2"]},"shared_chat_ban":null,"shared_chat_unban":null,"shared_chat_timeout":null,"shared_chat_untimeout":null,"shared_chat_delete":null}}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Warn {
|
||||
std::string userID;
|
||||
std::string userLogin;
|
||||
std::string userName;
|
||||
std::string reason;
|
||||
|
||||
// TODO: Verify we handle null for this as an empty vector
|
||||
std::vector<std::string> chatRulesCited;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
enum class Action : uint8_t {
|
||||
Ban,
|
||||
Timeout,
|
||||
Unban,
|
||||
Untimeout,
|
||||
Clear,
|
||||
Emoteonly,
|
||||
Emoteonlyoff,
|
||||
Followers,
|
||||
Followersoff,
|
||||
Uniquechat,
|
||||
Uniquechatoff,
|
||||
Slow,
|
||||
Slowoff,
|
||||
Subscribers,
|
||||
Subscribersoff,
|
||||
Unraid,
|
||||
/// json_rename=delete
|
||||
DeleteMessage,
|
||||
/// clangd currently "inherits" all future comments to all future enum constants
|
||||
/// so after using something like json_rename we need to ensure it doesn't spread
|
||||
Unvip,
|
||||
Vip,
|
||||
Raid,
|
||||
AddBlockedTerm,
|
||||
AddPermittedTerm,
|
||||
RemoveBlockedTerm,
|
||||
RemovePermittedTerm,
|
||||
Mod,
|
||||
Unmod,
|
||||
ApproveUnbanRequest,
|
||||
DenyUnbanRequest,
|
||||
Warn,
|
||||
SharedChatBan,
|
||||
SharedChatTimeout,
|
||||
SharedChatUnban,
|
||||
SharedChatUntimeout,
|
||||
SharedChatDelete,
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Event {
|
||||
/// User ID (e.g. 117166826) of the user who's channel the event took place in
|
||||
String broadcasterUserID;
|
||||
/// User Login (e.g. testaccount_420) of the user who's channel the event took place in
|
||||
String broadcasterUserLogin;
|
||||
/// User Name (e.g. 테스트계정420) of the user who's channel the event took place in
|
||||
String broadcasterUserName;
|
||||
|
||||
/// For Shared Chat events, the user ID (e.g. 117166826) of the user who's channel the event took place in
|
||||
std::optional<std::string> sourceBroadcasterUserID;
|
||||
/// For Shared Chat events, the user Login (e.g. testaccount_420) of the user who's channel the event took place in
|
||||
std::optional<std::string> sourceBroadcasterUserLogin;
|
||||
/// For Shared Chat events, the user Name (e.g. 테스트계정420) of the user who's channel the event took place in
|
||||
std::optional<std::string> sourceBroadcasterUserName;
|
||||
|
||||
/// User ID (e.g. 117166826) of the user who took the action
|
||||
String moderatorUserID;
|
||||
/// User Login (e.g. testaccount_420) of the user who took the action
|
||||
String moderatorUserLogin;
|
||||
/// User Name (e.g. 테스트계정420) of the user who took the action
|
||||
String moderatorUserName;
|
||||
|
||||
// TODO: enum?
|
||||
/// The action that took place (e.g. "warn" or "ban")
|
||||
Action action;
|
||||
|
||||
std::optional<Followers> followers;
|
||||
std::optional<Slow> slow;
|
||||
std::optional<Vip> vip;
|
||||
std::optional<Unvip> unvip;
|
||||
std::optional<Unmod> unmod;
|
||||
std::optional<Ban> ban;
|
||||
std::optional<Unban> unban;
|
||||
std::optional<Timeout> timeout;
|
||||
std::optional<Untimeout> untimeout;
|
||||
std::optional<Raid> raid;
|
||||
std::optional<Unraid> unraid;
|
||||
/// json_rename=delete
|
||||
std::optional<Delete> deleteMessage;
|
||||
std::optional<AutomodTerms> automodTerms;
|
||||
std::optional<UnbanRequest> unbanRequest;
|
||||
std::optional<Warn> warn;
|
||||
std::optional<Ban> sharedChatBan;
|
||||
std::optional<Unban> sharedChatUnban;
|
||||
std::optional<Timeout> sharedChatTimeout;
|
||||
std::optional<Untimeout> sharedChatUntimeout;
|
||||
std::optional<Delete> sharedChatDelete;
|
||||
};
|
||||
|
||||
struct Payload {
|
||||
subscription::Subscription subscription;
|
||||
|
||||
Event event;
|
||||
};
|
||||
|
||||
// DESERIALIZATION DEFINITION START
|
||||
boost::json::result_for<Action, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Action>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Followers, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Followers>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Slow, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Slow>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Vip, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Vip>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Unvip, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Unvip>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Mod, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Mod>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Unmod, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Unmod>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Ban, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Ban>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Unban, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Unban>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Timeout, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Timeout>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Untimeout, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Untimeout>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Raid, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Raid>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Unraid, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Unraid>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Delete, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Delete>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<AutomodTerms, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<AutomodTerms>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<UnbanRequest, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<UnbanRequest>,
|
||||
const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Warn, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Warn>, 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);
|
||||
// DESERIALIZATION DEFINITION END
|
||||
|
||||
} // namespace chatterino::eventsub::lib::payload::channel_moderate::v2
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "twitch-eventsub-ws/payloads/subscription.hpp"
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chatterino::eventsub::lib::payload::channel_update::v1 {
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Event {
|
||||
// The broadcaster's user ID
|
||||
const std::string broadcasterUserID;
|
||||
// The broadcaster's user login
|
||||
const std::string broadcasterUserLogin;
|
||||
// The broadcaster's user display name
|
||||
const std::string broadcasterUserName;
|
||||
|
||||
// The channel's stream title
|
||||
const std::string title;
|
||||
|
||||
// The channel's broadcast language
|
||||
const std::string language;
|
||||
|
||||
// The channels category ID
|
||||
const std::string categoryID;
|
||||
// The category name
|
||||
const std::string categoryName;
|
||||
|
||||
// A boolean identifying whether the channel is flagged as mature
|
||||
const bool isMature;
|
||||
};
|
||||
|
||||
struct Payload {
|
||||
const subscription::Subscription subscription;
|
||||
|
||||
const Event event;
|
||||
};
|
||||
|
||||
// DESERIALIZATION DEFINITION START
|
||||
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);
|
||||
// DESERIALIZATION DEFINITION END
|
||||
|
||||
} // namespace chatterino::eventsub::lib::payload::channel_update::v1
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "twitch-eventsub-ws/errors.hpp"
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chatterino::eventsub::lib::payload::session_welcome {
|
||||
|
||||
/*
|
||||
{
|
||||
"metadata": ...
|
||||
"payload": {
|
||||
"session": {
|
||||
"id": "44f8cbce_c7ee958a",
|
||||
"status": "connected",
|
||||
"keepalive_timeout_seconds": 10,
|
||||
"reconnect_url": null,
|
||||
"connected_at": "2023-05-14T12:31:47.995262791Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/// json_inner=session
|
||||
struct Payload {
|
||||
const std::string id;
|
||||
};
|
||||
|
||||
// DESERIALIZATION DEFINITION START
|
||||
boost::json::result_for<Payload, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Payload>, const boost::json::value &jvRoot);
|
||||
// DESERIALIZATION DEFINITION END
|
||||
|
||||
} // namespace chatterino::eventsub::lib::payload::session_welcome
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "twitch-eventsub-ws/payloads/subscription.hpp"
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chatterino::eventsub::lib::payload::stream_offline::v1 {
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Event {
|
||||
// The broadcaster's user ID
|
||||
const std::string broadcasterUserID;
|
||||
// The broadcaster's user login
|
||||
const std::string broadcasterUserLogin;
|
||||
// The broadcaster's user display name
|
||||
const std::string broadcasterUserName;
|
||||
};
|
||||
|
||||
struct Payload {
|
||||
const subscription::Subscription subscription;
|
||||
|
||||
const Event event;
|
||||
};
|
||||
|
||||
// DESERIALIZATION DEFINITION START
|
||||
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);
|
||||
// DESERIALIZATION DEFINITION END
|
||||
|
||||
} // namespace chatterino::eventsub::lib::payload::stream_offline::v1
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "twitch-eventsub-ws/payloads/subscription.hpp"
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chatterino::eventsub::lib::payload::stream_online::v1 {
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Event {
|
||||
// The ID of the stream
|
||||
const std::string id;
|
||||
|
||||
// The broadcaster's user ID
|
||||
const std::string broadcasterUserID;
|
||||
// The broadcaster's user login
|
||||
const std::string broadcasterUserLogin;
|
||||
// The broadcaster's user display name
|
||||
const std::string broadcasterUserName;
|
||||
|
||||
// The stream type (e.g. live, playlist, watch_party)
|
||||
const std::string type;
|
||||
|
||||
// The timestamp at which the stream went online
|
||||
// TODO: chronofy?
|
||||
const std::string startedAt;
|
||||
};
|
||||
|
||||
struct Payload {
|
||||
const subscription::Subscription subscription;
|
||||
|
||||
const Event event;
|
||||
};
|
||||
|
||||
// DESERIALIZATION DEFINITION START
|
||||
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);
|
||||
// DESERIALIZATION DEFINITION END
|
||||
|
||||
} // namespace chatterino::eventsub::lib::payload::stream_online::v1
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "twitch-eventsub-ws/payloads/subscription.hpp"
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chatterino::eventsub::lib::payload::subscription {
|
||||
|
||||
/*
|
||||
{
|
||||
"metadata": ...,
|
||||
"payload": {
|
||||
"subscription": {
|
||||
"id": "4aa632e0-fca3-590b-e981-bbd12abdb3fe",
|
||||
"status": "enabled",
|
||||
"type": "channel.ban",
|
||||
"version": "1",
|
||||
"condition": {
|
||||
"broadcaster_user_id": "74378979"
|
||||
},
|
||||
"transport": {
|
||||
"method": "websocket",
|
||||
"session_id": "38de428e_b11f07be"
|
||||
},
|
||||
"created_at": "2023-05-20T12:30:55.518375571Z",
|
||||
"cost": 0
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Transport {
|
||||
const std::string method;
|
||||
const std::string sessionID;
|
||||
};
|
||||
|
||||
/// json_transform=snake_case
|
||||
struct Subscription {
|
||||
const std::string id;
|
||||
const std::string status;
|
||||
const std::string type;
|
||||
const std::string version;
|
||||
|
||||
// TODO: How do we map condition here? vector of key/value pairs?
|
||||
|
||||
const Transport transport;
|
||||
|
||||
// TODO: chronofy?
|
||||
const std::string createdAt;
|
||||
const int cost;
|
||||
};
|
||||
|
||||
// DESERIALIZATION DEFINITION START
|
||||
boost::json::result_for<Transport, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Transport>, const boost::json::value &jvRoot);
|
||||
|
||||
boost::json::result_for<Subscription, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<Subscription>,
|
||||
const boost::json::value &jvRoot);
|
||||
// DESERIALIZATION DEFINITION END
|
||||
|
||||
} // namespace chatterino::eventsub::lib::payload::subscription
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/ssl.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/beast/websocket/ssl.hpp>
|
||||
#include <boost/json.hpp>
|
||||
|
||||
namespace chatterino::eventsub::lib {
|
||||
|
||||
class Listener;
|
||||
|
||||
/**
|
||||
* handleMessage takes the incoming message in the buffer, parses it
|
||||
* as JSON then forwards it to the listener, if applicable.
|
||||
*
|
||||
* This is called from the Session, and is only provided if you are interested
|
||||
* in building your own boost asio framework thing
|
||||
**/
|
||||
boost::system::error_code handleMessage(
|
||||
std::unique_ptr<Listener> &listener,
|
||||
const boost::beast::flat_buffer &buffer);
|
||||
|
||||
// Sends a WebSocket message and prints the response
|
||||
class Session : public std::enable_shared_from_this<Session>
|
||||
{
|
||||
boost::asio::ip::tcp::resolver resolver;
|
||||
boost::beast::websocket::stream<
|
||||
boost::beast::ssl_stream<boost::beast::tcp_stream>>
|
||||
ws;
|
||||
boost::beast::flat_buffer buffer;
|
||||
std::string host;
|
||||
std::string port;
|
||||
std::string path;
|
||||
std::string userAgent;
|
||||
std::unique_ptr<Listener> listener;
|
||||
|
||||
public:
|
||||
// Resolver and socket require an io_context
|
||||
explicit Session(boost::asio::io_context &ioc,
|
||||
boost::asio::ssl::context &ctx,
|
||||
std::unique_ptr<Listener> listener);
|
||||
|
||||
// Start the asynchronous operation
|
||||
void run(std::string _host, std::string _port, std::string _path,
|
||||
std::string _userAgent);
|
||||
|
||||
void close();
|
||||
|
||||
Listener *getListener();
|
||||
|
||||
private:
|
||||
void onResolve(boost::beast::error_code ec,
|
||||
boost::asio::ip::tcp::resolver::results_type results);
|
||||
|
||||
void onConnect(
|
||||
boost::beast::error_code ec,
|
||||
boost::asio::ip::tcp::resolver::results_type::endpoint_type ep);
|
||||
|
||||
void onSSLHandshake(boost::beast::error_code ec);
|
||||
|
||||
void onHandshake(boost::beast::error_code ec);
|
||||
|
||||
void onRead(boost::beast::error_code ec, std::size_t bytes_transferred);
|
||||
|
||||
void onClose(boost::beast::error_code ec);
|
||||
};
|
||||
|
||||
} // namespace chatterino::eventsub::lib
|
||||
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/json.hpp>
|
||||
#include <QString>
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
namespace chatterino::eventsub::lib {
|
||||
|
||||
template <class>
|
||||
constexpr bool DEPENDENT_FALSE = false; // workaround before CWG2518/P2593R1
|
||||
|
||||
/// String is a struct that holds either an std::string or a QString
|
||||
///
|
||||
/// The intended use is for it to receive an std::string that has been built by
|
||||
/// boost::json, and once it's been passed into a GUI appliciaton, it can use
|
||||
/// the `qt` function to convert the backing string to a QString,
|
||||
/// while we ensure the conversion is only done once.
|
||||
struct String {
|
||||
explicit String(std::string &&v)
|
||||
: backingString(std::move(v))
|
||||
{
|
||||
}
|
||||
|
||||
~String() = default;
|
||||
|
||||
String(const String &s) = delete;
|
||||
String(String &&s) = default;
|
||||
|
||||
String &operator=(const String &) = delete;
|
||||
String &operator=(String &&) = default;
|
||||
|
||||
/// Returns the string as a QString, modifying the backing string to ensure
|
||||
/// the copy only happens once.
|
||||
QString qt() const
|
||||
{
|
||||
return std::visit(
|
||||
[this](auto &&arg) -> QString {
|
||||
using T = std::decay_t<std::remove_cvref_t<decltype(arg)>>;
|
||||
if constexpr (std::is_same_v<T, std::string>)
|
||||
{
|
||||
auto qtString = QString::fromStdString(arg);
|
||||
this->backingString = qtString;
|
||||
return qtString;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, QString>)
|
||||
{
|
||||
return arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(DEPENDENT_FALSE<T>,
|
||||
"unknown type in variant");
|
||||
}
|
||||
},
|
||||
this->backingString);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::variant<std::string, QString> backingString;
|
||||
};
|
||||
|
||||
boost::json::result_for<String, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<String>, const boost::json::value &jvRoot);
|
||||
|
||||
} // namespace chatterino::eventsub::lib
|
||||
Reference in New Issue
Block a user