diff options
Diffstat (limited to 'src/discord')
-rw-r--r-- | src/discord/discord.cpp | 304 | ||||
-rw-r--r-- | src/discord/discord.hpp | 36 | ||||
-rw-r--r-- | src/discord/message.cpp | 6 | ||||
-rw-r--r-- | src/discord/message.hpp | 2 | ||||
-rw-r--r-- | src/discord/objects.cpp | 94 | ||||
-rw-r--r-- | src/discord/objects.hpp | 85 | ||||
-rw-r--r-- | src/discord/snowflake.cpp | 17 | ||||
-rw-r--r-- | src/discord/snowflake.hpp | 3 |
8 files changed, 541 insertions, 6 deletions
diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index b678de0..6819375 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1,8 +1,10 @@ +#include "abaddon.hpp" #include "discord.hpp" +#include "util.hpp" #include <cassert> #include <cinttypes> -#include "util.hpp" -#include "abaddon.hpp" + +using namespace std::string_literals; DiscordClient::DiscordClient(bool mem_store) : m_decompress_buf(InflateChunkSize) @@ -874,6 +876,108 @@ void DiscordClient::UnArchiveThread(Snowflake channel_id, sigc::slot<void(Discor }); } +void DiscordClient::MarkChannelAsRead(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback) { + if (m_unread.find(channel_id) == m_unread.end()) return; + const auto iter = m_last_message_id.find(channel_id); + if (iter == m_last_message_id.end()) return; + m_http.MakePOST("/channels/" + std::to_string(channel_id) + "/messages/" + std::to_string(iter->second) + "/ack", "{\"token\":null}", [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + +void DiscordClient::MarkGuildAsRead(Snowflake guild_id, sigc::slot<void(DiscordError code)> callback) { + AckBulkData data; + const auto channels = GetChannelsInGuild(guild_id); + for (const auto &[unread, mention_count] : m_unread) { + if (channels.find(unread) == channels.end()) continue; + + const auto iter = m_last_message_id.find(unread); + if (iter == m_last_message_id.end()) continue; + auto &e = data.ReadStates.emplace_back(); + e.ID = unread; + e.LastMessageID = iter->second; + } + + if (data.ReadStates.empty()) return; + + m_http.MakePOST("/read-states/ack-bulk", nlohmann::json(data).dump(), [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + +void DiscordClient::MuteChannel(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback) { + const auto channel = GetChannel(channel_id); + if (!channel.has_value()) return; + const auto guild_id_path = channel->GuildID.has_value() ? std::to_string(*channel->GuildID) : "@me"s; + nlohmann::json j; + j["channel_overrides"][std::to_string(channel_id)]["mute_config"] = MuteConfigData { std::nullopt, -1 }; + j["channel_overrides"][std::to_string(channel_id)]["muted"] = true; + m_http.MakePATCH("/users/@me/guilds/" + guild_id_path + "/settings", j.dump(), [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + +void DiscordClient::UnmuteChannel(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback) { + const auto channel = GetChannel(channel_id); + if (!channel.has_value()) return; + const auto guild_id_path = channel->GuildID.has_value() ? std::to_string(*channel->GuildID) : "@me"s; + nlohmann::json j; + j["channel_overrides"][std::to_string(channel_id)]["muted"] = false; + m_http.MakePATCH("/users/@me/guilds/" + guild_id_path + "/settings", j.dump(), [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + +void DiscordClient::MarkAllAsRead(sigc::slot<void(DiscordError code)> callback) { + AckBulkData data; + for (const auto &[unread, mention_count] : m_unread) { + const auto iter = m_last_message_id.find(unread); + if (iter == m_last_message_id.end()) continue; + auto &e = data.ReadStates.emplace_back(); + e.ID = unread; + e.LastMessageID = iter->second; + } + + if (data.ReadStates.empty()) return; + + m_http.MakePOST("/read-states/ack-bulk", nlohmann::json(data).dump(), [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + +void DiscordClient::MuteGuild(Snowflake id, sigc::slot<void(DiscordError code)> callback) { + m_http.MakePATCH("/users/@me/guilds/" + std::to_string(id) + "/settings", R"({"muted":true})", [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + +void DiscordClient::UnmuteGuild(Snowflake id, sigc::slot<void(DiscordError code)> callback) { + m_http.MakePATCH("/users/@me/guilds/" + std::to_string(id) + "/settings", R"({"muted":false})", [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + void DiscordClient::FetchPinned(Snowflake id, sigc::slot<void(std::vector<Message>, DiscordError code)> callback) { // return from db if we know the pins have already been requested if (m_channels_pinned_requested.find(id) != m_channels_pinned_requested.end()) { @@ -1060,6 +1164,34 @@ void DiscordClient::SetUserAgent(std::string agent) { m_websocket.SetUserAgent(agent); } +bool DiscordClient::IsChannelMuted(Snowflake id) const noexcept { + return m_muted_channels.find(id) != m_muted_channels.end(); +} + +bool DiscordClient::IsGuildMuted(Snowflake id) const noexcept { + return m_muted_guilds.find(id) != m_muted_guilds.end(); +} + +int DiscordClient::GetUnreadStateForChannel(Snowflake id) const noexcept { + const auto iter = m_unread.find(id); + if (iter == m_unread.end()) return -1; // todo: no magic number (who am i kidding ill never change this) + return iter->second; +} + +bool DiscordClient::GetUnreadStateForGuild(Snowflake id, int &total_mentions) const noexcept { + total_mentions = 0; + bool has_any_unread = false; + const auto channels = GetChannelsInGuild(id); + for (const auto channel_id : channels) { + const auto channel_unread = GetUnreadStateForChannel(channel_id); + if (!has_any_unread && channel_unread > -1 && !IsChannelMuted(channel_id)) + has_any_unread = true; + if (channel_unread > -1) + total_mentions += channel_unread; + } + return has_any_unread; +} + PresenceStatus DiscordClient::GetUserStatus(Snowflake id) const { auto it = m_user_to_status.find(id); if (it != m_user_to_status.end()) @@ -1290,6 +1422,12 @@ void DiscordClient::HandleGatewayMessage(std::string str) { case GatewayEvent::THREAD_MEMBER_LIST_UPDATE: { HandleGatewayThreadMemberListUpdate(m); } break; + case GatewayEvent::MESSAGE_ACK: { + HandleGatewayMessageAck(m); + } break; + case GatewayEvent::USER_GUILD_SETTINGS_UPDATE: { + HandleGatewayUserGuildSettingsUpdate(m); + } break; } } break; default: @@ -1411,6 +1549,10 @@ void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) { m_session_id = data.SessionID; m_user_data = data.SelfUser; m_user_settings = data.Settings; + + HandleReadyReadState(data); + HandleReadyGuildSettings(data); + m_signal_gateway_ready.emit(); } @@ -1419,6 +1561,12 @@ void DiscordClient::HandleGatewayMessageCreate(const GatewayMessage &msg) { StoreMessageData(data); if (data.GuildID.has_value()) AddUserToGuild(data.Author.ID, *data.GuildID); + m_last_message_id[data.ChannelID] = data.ID; + if (data.Author.ID != GetUserData().ID) + m_unread[data.ChannelID]; + if (data.DoesMention(GetUserData().ID)) { + m_unread[data.ChannelID]++; + } m_signal_message_create.emit(data); } @@ -1778,6 +1926,65 @@ void DiscordClient::HandleGatewayThreadMemberListUpdate(const GatewayMessage &ms m_signal_thread_member_list_update.emit(data); } +void DiscordClient::HandleGatewayMessageAck(const GatewayMessage &msg) { + MessageAckData data = msg.Data; + m_unread.erase(data.ChannelID); + m_signal_message_ack.emit(data); +} + +void DiscordClient::HandleGatewayUserGuildSettingsUpdate(const GatewayMessage &msg) { + UserGuildSettingsUpdateData data = msg.Data; + const auto channels = GetChannelsInGuild(data.Settings.GuildID); + std::set<Snowflake> now_muted_channels; + const auto now = Snowflake::FromNow(); + + const bool was_muted = IsGuildMuted(data.Settings.GuildID); + bool now_muted = false; + if (data.Settings.Muted) { + if (data.Settings.MuteConfig.EndTime.has_value()) { + const auto end = Snowflake::FromISO8601(*data.Settings.MuteConfig.EndTime); + if (end.IsValid() && end > now) + now_muted = true; + } else { + now_muted = true; + } + } + if (was_muted && !now_muted) { + m_muted_guilds.erase(data.Settings.GuildID); + m_signal_guild_unmuted.emit(data.Settings.GuildID); + } else if (!was_muted && now_muted) { + m_muted_guilds.insert(data.Settings.GuildID); + m_signal_guild_muted.emit(data.Settings.GuildID); + } + + for (const auto &override : data.Settings.ChannelOverrides) { + if (override.Muted) { + if (override.MuteConfig.EndTime.has_value()) { + const auto end = Snowflake::FromISO8601(*override.MuteConfig.EndTime); + if (end.IsValid() && end > now) + now_muted_channels.insert(override.ChannelID); + } else { + now_muted_channels.insert(override.ChannelID); + } + } + } + for (const auto &channel_id : channels) { + const bool was_muted = IsChannelMuted(channel_id); + const bool now_muted = now_muted_channels.find(channel_id) != now_muted_channels.end(); + if (now_muted) { + m_muted_channels.insert(channel_id); + if (!was_muted) { + m_signal_channel_muted.emit(channel_id); + } + } else { + m_muted_channels.erase(channel_id); + if (was_muted) { + m_signal_channel_unmuted.emit(channel_id); + } + } + } +} + void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) { ReadySupplementalData data = msg.Data; for (const auto &p : data.MergedPresences.Friends) { @@ -2105,6 +2312,77 @@ void DiscordClient::StoreMessageData(Message &msg) { StoreMessageData(**msg.ReferencedMessage); } +// some notes for myself +// a read channel is determined by checking if the channel object's last message id is equal to the read state's last message id +// channels without entries are also unread +// here the absence of an entry in m_unread indicates a read channel and the value is only the mention count since the message doesnt matter +// no entry.id cannot be a guild even though sometimes it looks like it +void DiscordClient::HandleReadyReadState(const ReadyEventData &data) { + for (const auto &guild : data.Guilds) + for (const auto &channel : *guild.Channels) + if (channel.LastMessageID.has_value() && (channel.Type == ChannelType::GUILD_TEXT || channel.Type == ChannelType::GUILD_NEWS)) + m_last_message_id[channel.ID] = *channel.LastMessageID; + for (const auto &channel : data.PrivateChannels) + if (channel.LastMessageID.has_value()) + m_last_message_id[channel.ID] = *channel.LastMessageID; + + for (const auto &entry : data.ReadState.Entries) { + const auto it = m_last_message_id.find(entry.ID); + if (it == m_last_message_id.end()) continue; + if (it->second > entry.LastMessageID) { + if (HasChannelPermission(GetUserData().ID, entry.ID, Permission::VIEW_CHANNEL)) + m_unread[entry.ID] = entry.MentionCount; + } + } + + // channels that arent in the read state are considered unread + for (const auto &guild : data.Guilds) { + if (!guild.JoinedAt.has_value()) continue; // doubt this can happen but whatever + const auto joined_at = Snowflake::FromISO8601(*guild.JoinedAt); + for (const auto &channel : *guild.Channels) { + if (channel.LastMessageID.has_value()) { + // unread messages from before you joined dont count as unread + if (*channel.LastMessageID < joined_at) continue; + if (std::find_if(data.ReadState.Entries.begin(), data.ReadState.Entries.end(), [id = channel.ID](const ReadStateEntry &e) { + return e.ID == id; + }) == data.ReadState.Entries.end()) { + // cant be unread if u cant even see the channel + // better to check here since HasChannelPermission hits the store + if (HasChannelPermission(GetUserData().ID, channel.ID, Permission::VIEW_CHANNEL)) + m_unread[channel.ID] = 0; + } + } + } + } +} + +void DiscordClient::HandleReadyGuildSettings(const ReadyEventData &data) { + const auto now = Snowflake::FromNow(); + for (const auto &entry : data.GuildSettings.Entries) { + // even if muted is true a guild/channel can be unmuted if the current time passes mute_config.end_time + if (entry.Muted) { + if (entry.MuteConfig.EndTime.has_value()) { + const auto end = Snowflake::FromISO8601(*entry.MuteConfig.EndTime); + if (end.IsValid() && end > now) + m_muted_guilds.insert(entry.GuildID); + } else { + m_muted_guilds.insert(entry.GuildID); + } + } + for (const auto &override : entry.ChannelOverrides) { + if (override.Muted) { + if (override.MuteConfig.EndTime.has_value()) { + const auto end = Snowflake::FromISO8601(*override.MuteConfig.EndTime); + if (end.IsValid() && end > now) + m_muted_channels.insert(override.ChannelID); + } else { + m_muted_channels.insert(override.ChannelID); + } + } + } + } +} + void DiscordClient::LoadEventMap() { m_event_map["READY"] = GatewayEvent::READY; m_event_map["MESSAGE_CREATE"] = GatewayEvent::MESSAGE_CREATE; @@ -2147,6 +2425,8 @@ void DiscordClient::LoadEventMap() { m_event_map["THREAD_MEMBER_UPDATE"] = GatewayEvent::THREAD_MEMBER_UPDATE; m_event_map["THREAD_UPDATE"] = GatewayEvent::THREAD_UPDATE; m_event_map["THREAD_MEMBER_LIST_UPDATE"] = GatewayEvent::THREAD_MEMBER_LIST_UPDATE; + m_event_map["MESSAGE_ACK"] = GatewayEvent::MESSAGE_ACK; + m_event_map["USER_GUILD_SETTINGS_UPDATE"] = GatewayEvent::USER_GUILD_SETTINGS_UPDATE; } DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() { @@ -2309,6 +2589,10 @@ DiscordClient::type_signal_thread_member_list_update DiscordClient::signal_threa return m_signal_thread_member_list_update; } +DiscordClient::type_signal_message_ack DiscordClient::signal_message_ack() { + return m_signal_message_ack; +} + DiscordClient::type_signal_added_to_thread DiscordClient::signal_added_to_thread() { return m_signal_added_to_thread; } @@ -2321,6 +2605,22 @@ DiscordClient::type_signal_message_sent DiscordClient::signal_message_sent() { return m_signal_message_sent; } +DiscordClient::type_signal_channel_muted DiscordClient::signal_channel_muted() { + return m_signal_channel_muted; +} + +DiscordClient::type_signal_channel_unmuted DiscordClient::signal_channel_unmuted() { + return m_signal_channel_unmuted; +} + +DiscordClient::type_signal_guild_muted DiscordClient::signal_guild_muted() { + return m_signal_guild_muted; +} + +DiscordClient::type_signal_guild_unmuted DiscordClient::signal_guild_unmuted() { + return m_signal_guild_unmuted; +} + DiscordClient::type_signal_message_send_fail DiscordClient::signal_message_send_fail() { return m_signal_message_send_fail; } diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 4010977..44f02bb 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -138,6 +138,13 @@ public: void LeaveThread(Snowflake channel_id, const std::string &location, sigc::slot<void(DiscordError code)> callback); void ArchiveThread(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback); void UnArchiveThread(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback); + void MarkChannelAsRead(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback); + void MarkGuildAsRead(Snowflake guild_id, sigc::slot<void(DiscordError code)> callback); + void MuteChannel(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback); + void UnmuteChannel(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback); + void MarkAllAsRead(sigc::slot<void(DiscordError code)> callback); + void MuteGuild(Snowflake id, sigc::slot<void(DiscordError code)> callback); + void UnmuteGuild(Snowflake id, sigc::slot<void(DiscordError code)> callback); bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const; bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const; @@ -182,6 +189,11 @@ public: void UpdateToken(std::string token); void SetUserAgent(std::string agent); + bool IsChannelMuted(Snowflake id) const noexcept; + bool IsGuildMuted(Snowflake id) const noexcept; + int GetUnreadStateForChannel(Snowflake id) const noexcept; + bool GetUnreadStateForGuild(Snowflake id, int &total_mentions) const noexcept; + PresenceStatus GetUserStatus(Snowflake id) const; std::map<Snowflake, RelationshipType> GetRelationships() const; @@ -244,6 +256,8 @@ private: void HandleGatewayThreadMemberUpdate(const GatewayMessage &msg); void HandleGatewayThreadUpdate(const GatewayMessage &msg); void HandleGatewayThreadMemberListUpdate(const GatewayMessage &msg); + void HandleGatewayMessageAck(const GatewayMessage &msg); + void HandleGatewayUserGuildSettingsUpdate(const GatewayMessage &msg); void HandleGatewayReadySupplemental(const GatewayMessage &msg); void HandleGatewayReconnect(const GatewayMessage &msg); void HandleGatewayInvalidSession(const GatewayMessage &msg); @@ -259,6 +273,9 @@ private: void StoreMessageData(Message &msg); + void HandleReadyReadState(const ReadyEventData &data); + void HandleReadyGuildSettings(const ReadyEventData &data); + std::string m_token; void AddUserToGuild(Snowflake user_id, Snowflake guild_id); @@ -269,6 +286,10 @@ private: std::map<Snowflake, RelationshipType> m_user_relationships; std::set<Snowflake> m_joined_threads; std::map<Snowflake, std::vector<Snowflake>> m_thread_members; + std::map<Snowflake, Snowflake> m_last_message_id; + std::unordered_set<Snowflake> m_muted_guilds; + std::unordered_set<Snowflake> m_muted_channels; + std::unordered_map<Snowflake, int> m_unread; UserData m_user_data; UserSettings m_user_settings; @@ -343,6 +364,7 @@ public: typedef sigc::signal<void, ThreadMembersUpdateData> type_signal_thread_members_update; typedef sigc::signal<void, ThreadUpdateData> type_signal_thread_update; typedef sigc::signal<void, ThreadMemberListUpdateData> type_signal_thread_member_list_update; + typedef sigc::signal<void, MessageAckData> type_signal_message_ack; // not discord dispatch events typedef sigc::signal<void, Snowflake> type_signal_added_to_thread; @@ -350,6 +372,10 @@ public: typedef sigc::signal<void, Message> type_signal_message_unpinned; typedef sigc::signal<void, Message> type_signal_message_pinned; typedef sigc::signal<void, Message> type_signal_message_sent; + typedef sigc::signal<void, Snowflake> type_signal_channel_muted; + typedef sigc::signal<void, Snowflake> type_signal_channel_unmuted; + typedef sigc::signal<void, Snowflake> type_signal_guild_muted; + typedef sigc::signal<void, Snowflake> type_signal_guild_unmuted; typedef sigc::signal<void, std::string /* nonce */, float /* retry_after */> type_signal_message_send_fail; // retry after param will be 0 if it failed for a reason that isnt slowmode typedef sigc::signal<void, bool, GatewayCloseCode> type_signal_disconnected; // bool true if reconnecting @@ -393,10 +419,15 @@ public: type_signal_thread_members_update signal_thread_members_update(); type_signal_thread_update signal_thread_update(); type_signal_thread_member_list_update signal_thread_member_list_update(); + type_signal_message_ack signal_message_ack(); type_signal_added_to_thread signal_added_to_thread(); type_signal_removed_from_thread signal_removed_from_thread(); type_signal_message_sent signal_message_sent(); + type_signal_channel_muted signal_channel_muted(); + type_signal_channel_unmuted signal_channel_unmuted(); + type_signal_guild_muted signal_guild_muted(); + type_signal_guild_unmuted signal_guild_unmuted(); type_signal_message_send_fail signal_message_send_fail(); type_signal_disconnected signal_disconnected(); type_signal_connected signal_connected(); @@ -440,10 +471,15 @@ protected: type_signal_thread_members_update m_signal_thread_members_update; type_signal_thread_update m_signal_thread_update; type_signal_thread_member_list_update m_signal_thread_member_list_update; + type_signal_message_ack m_signal_message_ack; type_signal_removed_from_thread m_signal_removed_from_thread; type_signal_added_to_thread m_signal_added_to_thread; type_signal_message_sent m_signal_message_sent; + type_signal_channel_muted m_signal_channel_muted; + type_signal_channel_unmuted m_signal_channel_unmuted; + type_signal_guild_muted m_signal_guild_muted; + type_signal_guild_unmuted m_signal_guild_unmuted; type_signal_message_send_fail m_signal_message_send_fail; type_signal_disconnected m_signal_disconnected; type_signal_connected m_signal_connected; diff --git a/src/discord/message.cpp b/src/discord/message.cpp index 70c557d..93d57c2 100644 --- a/src/discord/message.cpp +++ b/src/discord/message.cpp @@ -263,3 +263,9 @@ bool Message::IsDeleted() const { bool Message::IsEdited() const { return m_edited; } + +bool Message::DoesMention(Snowflake id) const noexcept { + return std::any_of(Mentions.begin(), Mentions.end(), [id](const UserData &user) { + return user.ID == id; + }); +} diff --git a/src/discord/message.hpp b/src/discord/message.hpp index 56f4c0f..244b572 100644 --- a/src/discord/message.hpp +++ b/src/discord/message.hpp @@ -212,6 +212,8 @@ struct Message { bool IsDeleted() const; bool IsEdited() const; + bool DoesMention(Snowflake id) const noexcept; + private: bool m_deleted = false; bool m_edited = false; diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index c6de2ce..8ca8c0f 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -119,6 +119,84 @@ void to_json(nlohmann::json &j, const UpdateStatusMessage &m) { } } +void from_json(const nlohmann::json &j, ReadStateEntry &m) { + JS_ON("mention_count", m.MentionCount); + JS_ON("last_message_id", m.LastMessageID); + JS_D("id", m.ID); +} + +void to_json(nlohmann::json &j, const ReadStateEntry &m) { + j["channel_id"] = m.ID; + j["message_id"] = m.LastMessageID; +} + +void from_json(const nlohmann::json &j, ReadStateData &m) { + JS_ON("version", m.Version); + JS_ON("partial", m.IsPartial); + JS_ON("entries", m.Entries); +} + +void from_json(const nlohmann::json &j, UserGuildSettingsChannelOverride &m) { + JS_D("muted", m.Muted); + JS_D("message_notifications", m.MessageNotifications); + JS_D("collapsed", m.Collapsed); + JS_D("channel_id", m.ChannelID); + JS_N("mute_config", m.MuteConfig); +} + +void to_json(nlohmann::json &j, const UserGuildSettingsChannelOverride &m) { + j["channel_id"] = m.ChannelID; + j["collapsed"] = m.Collapsed; + j["message_notifications"] = m.MessageNotifications; + j["mute_config"] = m.MuteConfig; + j["muted"] = m.Muted; +} + +void from_json(const nlohmann::json &j, MuteConfigData &m) { + JS_ON("end_time", m.EndTime); + JS_D("selected_time_window", m.SelectedTimeWindow); +} + +void to_json(nlohmann::json &j, const MuteConfigData &m) { + if (m.EndTime.has_value()) + j["end_time"] = *m.EndTime; + else + j["end_time"] = nullptr; + j["selected_time_window"] = m.SelectedTimeWindow; +} + +void from_json(const nlohmann::json &j, UserGuildSettingsEntry &m) { + JS_D("version", m.Version); + JS_D("suppress_roles", m.SuppressRoles); + JS_D("suppress_everyone", m.SuppressEveryone); + JS_D("muted", m.Muted); + JS_D("mobile_push", m.MobilePush); + JS_D("message_notifications", m.MessageNotifications); + JS_D("hide_muted_channels", m.HideMutedChannels); + JS_N("guild_id", m.GuildID); + JS_D("channel_overrides", m.ChannelOverrides); + JS_N("mute_config", m.MuteConfig); +} + +void to_json(nlohmann::json &j, const UserGuildSettingsEntry &m) { + j["channel_overrides"] = m.ChannelOverrides; + j["guild_id"] = m.GuildID; + j["hide_muted_channels"] = m.HideMutedChannels; + j["message_notifications"] = m.MessageNotifications; + j["mobile_push"] = m.MobilePush; + j["mute_config"] = m.MuteConfig; + j["muted"] = m.Muted; + j["suppress_everyone"] = m.SuppressEveryone; + j["suppress_roles"] = m.SuppressRoles; + j["version"] = m.Version; +} + +void from_json(const nlohmann::json &j, UserGuildSettingsData &m) { + JS_D("version", m.Version); + JS_D("partial", m.IsPartial); + JS_D("entries", m.Entries); +} + void from_json(const nlohmann::json &j, ReadyEventData &m) { JS_D("v", m.GatewayVersion); JS_D("user", m.SelfUser); @@ -132,6 +210,8 @@ void from_json(const nlohmann::json &j, ReadyEventData &m) { JS_ON("merged_members", m.MergedMembers); JS_O("relationships", m.Relationships); JS_O("guild_join_requests", m.GuildJoinRequests); + JS_O("read_state", m.ReadState); + JS_D("user_guild_settings", m.GuildSettings); } void from_json(const nlohmann::json &j, MergedPresence &m) { @@ -532,3 +612,17 @@ void to_json(nlohmann::json &j, const ModifyChannelObject &m) { JS_IF("archived", m.Archived); JS_IF("locked", m.Locked); } + +void from_json(const nlohmann::json &j, MessageAckData &m) { + // JS_D("version", m.Version); + JS_D("message_id", m.MessageID); + JS_D("channel_id", m.ChannelID); +} + +void to_json(nlohmann::json &j, const AckBulkData &m) { + j["read_states"] = m.ReadStates; +} + +void from_json(const nlohmann::json &j, UserGuildSettingsUpdateData &m) { + m.Settings = j; +} diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 7084efb..c72361b 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -78,6 +78,8 @@ enum class GatewayEvent : int { THREAD_MEMBER_UPDATE, THREAD_MEMBERS_UPDATE, THREAD_MEMBER_LIST_UPDATE, + MESSAGE_ACK, + USER_GUILD_SETTINGS_UPDATE, }; enum class GatewayCloseCode : uint16_t { @@ -224,6 +226,67 @@ struct UpdateStatusMessage { friend void to_json(nlohmann::json &j, const UpdateStatusMessage &m); }; +struct ReadStateEntry { + int MentionCount; + Snowflake LastMessageID; + Snowflake ID; + // std::string LastPinTimestamp; iso + + friend void from_json(const nlohmann::json &j, ReadStateEntry &m); + friend void to_json(nlohmann::json &j, const ReadStateEntry &m); +}; + +struct ReadStateData { + int Version; + bool IsPartial; + std::vector<ReadStateEntry> Entries; + + friend void from_json(const nlohmann::json &j, ReadStateData &m); +}; + +struct MuteConfigData { + std::optional<std::string> EndTime; // nullopt is encoded as null + int SelectedTimeWindow; + + friend void from_json(const nlohmann::json &j, MuteConfigData &m); + friend void to_json(nlohmann::json &j, const MuteConfigData &m); +}; + +struct UserGuildSettingsChannelOverride { + bool Muted; + MuteConfigData MuteConfig; + int MessageNotifications; + bool Collapsed; + Snowflake ChannelID; + + friend void from_json(const nlohmann::json &j, UserGuildSettingsChannelOverride &m); + friend void to_json(nlohmann::json &j, const UserGuildSettingsChannelOverride &m); +}; + +struct UserGuildSettingsEntry { + int Version; + bool SuppressRoles; + bool SuppressEveryone; + bool Muted; + MuteConfigData MuteConfig; + bool MobilePush; + int MessageNotifications; + bool HideMutedChannels; + Snowflake GuildID; + std::vector<UserGuildSettingsChannelOverride> ChannelOverrides; + + friend void from_json(const nlohmann::json &j, UserGuildSettingsEntry &m); + friend void to_json(nlohmann::json &j, const UserGuildSettingsEntry &m); +}; + +struct UserGuildSettingsData { + int Version; + bool IsPartial; + std::vector<UserGuildSettingsEntry> Entries; + + friend void from_json(const nlohmann::json &j, UserGuildSettingsData &m); +}; + struct ReadyEventData { int GatewayVersion; UserData SelfUser; @@ -239,6 +302,8 @@ struct ReadyEventData { std::optional<std::vector<std::vector<GuildMember>>> MergedMembers; std::optional<std::vector<RelationshipData>> Relationships; std::optional<std::vector<GuildApplicationData>> GuildJoinRequests; + ReadStateData ReadState; + UserGuildSettingsData GuildSettings; // std::vector<Unknown> ConnectedAccounts; // opt // std::map<std::string, Unknown> Consents; // opt // std::vector<Unknown> Experiments; // opt @@ -745,3 +810,23 @@ struct ModifyChannelObject { friend void to_json(nlohmann::json &j, const ModifyChannelObject &m); }; + +struct MessageAckData { + // int Version; // what is this ?!?!?!!? + Snowflake MessageID; + Snowflake ChannelID; + + friend void from_json(const nlohmann::json &j, MessageAckData &m); +}; + +struct AckBulkData { + std::vector<ReadStateEntry> ReadStates; + + friend void to_json(nlohmann::json &j, const AckBulkData &m); +}; + +struct UserGuildSettingsUpdateData { + UserGuildSettingsEntry Settings; + + friend void from_json(const nlohmann::json &j, UserGuildSettingsUpdateData &m); +}; diff --git a/src/discord/snowflake.cpp b/src/discord/snowflake.cpp index cea9153..8f470a7 100644 --- a/src/discord/snowflake.cpp +++ b/src/discord/snowflake.cpp @@ -1,7 +1,8 @@ #include "snowflake.hpp" +#include "util.hpp" +#include <chrono> #include <ctime> #include <iomanip> -#include <chrono> constexpr static uint64_t DiscordEpochSeconds = 1420070400; @@ -14,13 +15,13 @@ Snowflake::Snowflake(uint64_t n) : m_num(n) {} Snowflake::Snowflake(const std::string &str) { - if (str.size()) + if (!str.empty()) m_num = std::stoull(str); else m_num = Invalid; } Snowflake::Snowflake(const Glib::ustring &str) { - if (str.size()) + if (!str.empty()) m_num = std::strtoull(str.c_str(), nullptr, 10); else m_num = Invalid; @@ -38,6 +39,16 @@ Snowflake Snowflake::FromNow() { return snowflake; } +Snowflake Snowflake::FromISO8601(std::string_view ts) { + int yr, mon, day, hr, min, sec, tzhr, tzmin; + float milli; + if (std::sscanf(ts.data(), "%d-%d-%dT%d:%d:%d%f+%d:%d", + &yr, &mon, &day, &hr, &min, &sec, &milli, &tzhr, &tzmin) != 9) return Snowflake::Invalid; + const auto epoch = util::TimeToEpoch(yr, mon, day, hr, min, sec); + if (epoch < DiscordEpochSeconds) return Snowflake::Invalid; + return SecondsInterval * (epoch - DiscordEpochSeconds) + static_cast<uint64_t>(milli * static_cast<float>(SecondsInterval)); +} + bool Snowflake::IsValid() const { return m_num != Invalid; } diff --git a/src/discord/snowflake.hpp b/src/discord/snowflake.hpp index 0b79723..f2da5d1 100644 --- a/src/discord/snowflake.hpp +++ b/src/discord/snowflake.hpp @@ -10,6 +10,7 @@ struct Snowflake { Snowflake(const Glib::ustring &str); static Snowflake FromNow(); // not thread safe + static Snowflake FromISO8601(std::string_view ts); bool IsValid() const; std::string GetLocalTimestamp() const; @@ -26,7 +27,7 @@ struct Snowflake { return m_num; } - const static Snowflake Invalid; // makes sense to me + const static Snowflake Invalid; // makes sense to me const static uint64_t SecondsInterval = 4194304000ULL; // the "difference" between two snowflakes one second apart friend void from_json(const nlohmann::json &j, Snowflake &s); |