From a51a54bc5979a2491f152abc47ad54e6b63f27c8 Mon Sep 17 00:00:00 2001 From: Dylam De La Torre Date: Tue, 23 Nov 2021 05:21:56 +0100 Subject: Restructure source and resource files (#46) importantly, res is now res/res and css is now res/css --- src/discord/discord.hpp | 449 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 src/discord/discord.hpp (limited to 'src/discord/discord.hpp') diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp new file mode 100644 index 0000000..4b9bc82 --- /dev/null +++ b/src/discord/discord.hpp @@ -0,0 +1,449 @@ +#pragma once +#include "websocket.hpp" +#include "httpclient.hpp" +#include "objects.hpp" +#include "store.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef GetMessage + #undef GetMessage +#endif + +class HeartbeatWaiter { +public: + template + bool wait_for(std::chrono::duration const &time) const { + std::unique_lock lock(m); + return !cv.wait_for(lock, time, [&] { return terminate; }); + } + + void kill() { + std::unique_lock lock(m); + terminate = true; + cv.notify_all(); + } + + void revive() { + std::unique_lock lock(m); + terminate = false; + } + +private: + mutable std::condition_variable cv; + mutable std::mutex m; + bool terminate = false; +}; + +class Abaddon; +class DiscordClient { + friend class Abaddon; + +public: + DiscordClient(bool mem_store = false); + void Start(); + void Stop(); + bool IsStarted() const; + bool IsStoreValid() const; + + std::unordered_set GetGuilds() const; + const UserData &GetUserData() const; + const UserSettings &GetUserSettings() const; + std::vector GetUserSortedGuilds() const; + std::vector GetMessagesForChannel(Snowflake id, size_t limit = 50) const; + std::vector GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit = 50) const; + std::set GetPrivateChannels() const; + + EPremiumType GetSelfPremiumType() const; + + void FetchMessagesInChannel(Snowflake id, sigc::slot &)> cb); + void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, sigc::slot &)> cb); + std::optional GetMessage(Snowflake id) const; + std::optional GetChannel(Snowflake id) const; + std::optional GetEmoji(Snowflake id) const; + std::optional GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const; + std::optional GetUser(Snowflake id) const; + std::optional GetRole(Snowflake id) const; + std::optional GetGuild(Snowflake id) const; + std::optional GetMember(Snowflake user_id, Snowflake guild_id) const; + std::optional GetBan(Snowflake guild_id, Snowflake user_id) const; + Snowflake GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color = false) const; + std::optional GetMemberHighestRole(Snowflake guild_id, Snowflake user_id) const; + std::set GetUsersInGuild(Snowflake id) const; + std::set GetChannelsInGuild(Snowflake id) const; + std::vector GetUsersInThread(Snowflake id) const; + std::vector GetActiveThreads(Snowflake channel_id) const; + void GetArchivedPublicThreads(Snowflake channel_id, sigc::slot callback); + + bool IsThreadJoined(Snowflake thread_id) const; + bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const; + + bool HasAnyChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const; + bool HasChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const; + Permission ComputePermissions(Snowflake member_id, Snowflake guild_id) const; + Permission ComputeOverwrites(Permission base, Snowflake member_id, Snowflake channel_id) const; + bool CanManageMember(Snowflake guild_id, Snowflake actor, Snowflake target) const; // kick, ban, edit nickname (cant think of a better name) + + void ChatMessageCallback(std::string nonce, const http::response_type &response); + + void SendChatMessage(const std::string &content, Snowflake channel); + void SendChatMessage(const std::string &content, Snowflake channel, Snowflake referenced_message); + void DeleteMessage(Snowflake channel_id, Snowflake id); + void EditMessage(Snowflake channel_id, Snowflake id, std::string content); + void SendLazyLoad(Snowflake id); + void SendThreadLazyLoad(Snowflake id); + void JoinGuild(std::string code); + void LeaveGuild(Snowflake id); + void KickUser(Snowflake user_id, Snowflake guild_id); + void BanUser(Snowflake user_id, Snowflake guild_id); // todo: reason, delete messages + void UpdateStatus(PresenceStatus status, bool is_afk); + void UpdateStatus(PresenceStatus status, bool is_afk, const ActivityData &obj); + void CreateDM(Snowflake user_id); + void CreateDM(Snowflake user_id, sigc::slot callback); + void CloseDM(Snowflake channel_id); + std::optional FindDM(Snowflake user_id); // wont find group dms + void AddReaction(Snowflake id, Glib::ustring param); + void RemoveReaction(Snowflake id, Glib::ustring param); + void SetGuildName(Snowflake id, const Glib::ustring &name); + void SetGuildName(Snowflake id, const Glib::ustring &name, sigc::slot callback); + void SetGuildIcon(Snowflake id, const std::string &data); + void SetGuildIcon(Snowflake id, const std::string &data, sigc::slot callback); + void UnbanUser(Snowflake guild_id, Snowflake user_id); + void UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot callback); + void DeleteInvite(const std::string &code); + void DeleteInvite(const std::string &code, sigc::slot callback); + void AddGroupDMRecipient(Snowflake channel_id, Snowflake user_id); + void RemoveGroupDMRecipient(Snowflake channel_id, Snowflake user_id); + void ModifyRolePermissions(Snowflake guild_id, Snowflake role_id, Permission permissions, sigc::slot callback); + void ModifyRoleName(Snowflake guild_id, Snowflake role_id, const Glib::ustring &name, sigc::slot callback); + void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, uint32_t color, sigc::slot callback); + void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, Gdk::RGBA color, sigc::slot callback); + void ModifyRolePosition(Snowflake guild_id, Snowflake role_id, int position, sigc::slot callback); + void ModifyEmojiName(Snowflake guild_id, Snowflake emoji_id, const Glib::ustring &name, sigc::slot callback); + void DeleteEmoji(Snowflake guild_id, Snowflake emoji_id, sigc::slot callback); + std::optional GetGuildApplication(Snowflake guild_id) const; + void RemoveRelationship(Snowflake id, sigc::slot callback); + void SendFriendRequest(const Glib::ustring &username, int discriminator, sigc::slot callback); + void PutRelationship(Snowflake id, sigc::slot callback); // send fr by id, accept incoming + void Pin(Snowflake channel_id, Snowflake message_id, sigc::slot callback); + void Unpin(Snowflake channel_id, Snowflake message_id, sigc::slot callback); + void LeaveThread(Snowflake channel_id, const std::string &location, sigc::slot callback); + void ArchiveThread(Snowflake channel_id, sigc::slot callback); + void UnArchiveThread(Snowflake channel_id, sigc::slot callback); + + bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const; + bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const; + + // real client doesn't seem to use the single role endpoints so neither do we + template + auto SetMemberRoles(Snowflake guild_id, Snowflake user_id, Iter begin, Iter end, sigc::slot callback) { + ModifyGuildMemberObject obj; + obj.Roles = { begin, end }; + m_http.MakePATCH("/guilds/" + std::to_string(guild_id) + "/members/" + std::to_string(user_id), nlohmann::json(obj).dump(), [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); + } + + // FetchGuildBans fetches all bans+reasons via api, this func fetches stored bans (so usually just GUILD_BAN_ADD data) + std::vector GetBansInGuild(Snowflake guild_id); + void FetchGuildBan(Snowflake guild_id, Snowflake user_id, sigc::slot callback); + void FetchGuildBans(Snowflake guild_id, sigc::slot)> callback); + + void FetchInvite(std::string code, sigc::slot)> callback); + void FetchGuildInvites(Snowflake guild_id, sigc::slot)> callback); + + void FetchAuditLog(Snowflake guild_id, sigc::slot callback); + + void FetchGuildEmojis(Snowflake guild_id, sigc::slot)> callback); + + void FetchUserProfile(Snowflake user_id, sigc::slot callback); + void FetchUserNote(Snowflake user_id, sigc::slot callback); + void SetUserNote(Snowflake user_id, std::string note); + void SetUserNote(Snowflake user_id, std::string note, sigc::slot callback); + void FetchUserRelationships(Snowflake user_id, sigc::slot)> callback); + + void FetchPinned(Snowflake id, sigc::slot, DiscordError code)> callback); + + bool IsVerificationRequired(Snowflake guild_id); + void GetVerificationGateInfo(Snowflake guild_id, sigc::slot)> callback); + void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, sigc::slot callback); + + void UpdateToken(std::string token); + void SetUserAgent(std::string agent); + + PresenceStatus GetUserStatus(Snowflake id) const; + + std::map GetRelationships() const; + std::set GetRelationships(RelationshipType type) const; + std::optional GetRelationship(Snowflake id) const; + +private: + static const constexpr int InflateChunkSize = 0x10000; + std::vector m_compressed_buf; + std::vector m_decompress_buf; + z_stream m_zstream; + + std::string GetAPIURL(); + std::string GetGatewayURL(); + + static DiscordError GetCodeFromResponse(const http::response_type &response); + + void ProcessNewGuild(GuildData &guild); + + void HandleGatewayMessageRaw(std::string str); + void HandleGatewayMessage(std::string str); + void HandleGatewayHello(const GatewayMessage &msg); + void HandleGatewayReady(const GatewayMessage &msg); + void HandleGatewayMessageCreate(const GatewayMessage &msg); + void HandleGatewayMessageDelete(const GatewayMessage &msg); + void HandleGatewayMessageUpdate(const GatewayMessage &msg); + void HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg); + void HandleGatewayGuildCreate(const GatewayMessage &msg); + void HandleGatewayGuildDelete(const GatewayMessage &msg); + void HandleGatewayMessageDeleteBulk(const GatewayMessage &msg); + void HandleGatewayGuildMemberUpdate(const GatewayMessage &msg); + void HandleGatewayPresenceUpdate(const GatewayMessage &msg); + void HandleGatewayChannelDelete(const GatewayMessage &msg); + void HandleGatewayChannelUpdate(const GatewayMessage &msg); + void HandleGatewayChannelCreate(const GatewayMessage &msg); + void HandleGatewayGuildUpdate(const GatewayMessage &msg); + void HandleGatewayGuildRoleUpdate(const GatewayMessage &msg); + void HandleGatewayGuildRoleCreate(const GatewayMessage &msg); + void HandleGatewayGuildRoleDelete(const GatewayMessage &msg); + void HandleGatewayMessageReactionAdd(const GatewayMessage &msg); + void HandleGatewayMessageReactionRemove(const GatewayMessage &msg); + void HandleGatewayChannelRecipientAdd(const GatewayMessage &msg); + void HandleGatewayChannelRecipientRemove(const GatewayMessage &msg); + void HandleGatewayTypingStart(const GatewayMessage &msg); + void HandleGatewayGuildBanRemove(const GatewayMessage &msg); + void HandleGatewayGuildBanAdd(const GatewayMessage &msg); + void HandleGatewayInviteCreate(const GatewayMessage &msg); + void HandleGatewayInviteDelete(const GatewayMessage &msg); + void HandleGatewayUserNoteUpdate(const GatewayMessage &msg); + void HandleGatewayGuildEmojisUpdate(const GatewayMessage &msg); + void HandleGatewayGuildJoinRequestCreate(const GatewayMessage &msg); + void HandleGatewayGuildJoinRequestUpdate(const GatewayMessage &msg); + void HandleGatewayGuildJoinRequestDelete(const GatewayMessage &msg); + void HandleGatewayRelationshipRemove(const GatewayMessage &msg); + void HandleGatewayRelationshipAdd(const GatewayMessage &msg); + void HandleGatewayThreadCreate(const GatewayMessage &msg); + void HandleGatewayThreadDelete(const GatewayMessage &msg); + void HandleGatewayThreadListSync(const GatewayMessage &msg); + void HandleGatewayThreadMembersUpdate(const GatewayMessage &msg); + void HandleGatewayThreadMemberUpdate(const GatewayMessage &msg); + void HandleGatewayThreadUpdate(const GatewayMessage &msg); + void HandleGatewayThreadMemberListUpdate(const GatewayMessage &msg); + void HandleGatewayReadySupplemental(const GatewayMessage &msg); + void HandleGatewayReconnect(const GatewayMessage &msg); + void HandleGatewayInvalidSession(const GatewayMessage &msg); + void HeartbeatThread(); + void SendIdentify(); + void SendResume(); + + void HandleSocketOpen(); + void HandleSocketClose(uint16_t code); + + bool CheckCode(const http::response_type &r); + bool CheckCode(const http::response_type &r, int expected); + + void StoreMessageData(Message &msg); + + std::string m_token; + + void AddUserToGuild(Snowflake user_id, Snowflake guild_id); + std::map> m_guild_to_users; + std::map> m_guild_to_channels; + std::map m_guild_join_requests; + std::map m_user_to_status; + std::map m_user_relationships; + std::set m_joined_threads; + std::map> m_thread_members; + + UserData m_user_data; + UserSettings m_user_settings; + + Store m_store; + HTTPClient m_http; + Websocket m_websocket; + std::atomic m_client_connected = false; + std::atomic m_ready_received = false; + bool m_client_started = false; + + std::map m_event_map; + void LoadEventMap(); + + std::thread m_heartbeat_thread; + std::atomic m_last_sequence = -1; + std::atomic m_heartbeat_msec = 0; + HeartbeatWaiter m_heartbeat_waiter; + std::atomic m_heartbeat_acked = true; + + bool m_reconnecting = false; // reconnecting either to resume or reidentify + bool m_wants_resume = false; // reconnecting specifically to resume + std::string m_session_id; + + mutable std::mutex m_msg_mutex; + Glib::Dispatcher m_msg_dispatch; + std::queue m_msg_queue; + void MessageDispatch(); + + mutable std::mutex m_generic_mutex; + Glib::Dispatcher m_generic_dispatch; + std::queue> m_generic_queue; + + std::set m_channels_pinned_requested; + std::set m_channels_lazy_loaded; + + // signals +public: + typedef sigc::signal type_signal_gateway_ready; + typedef sigc::signal type_signal_message_create; + typedef sigc::signal type_signal_message_delete; + typedef sigc::signal type_signal_message_update; + typedef sigc::signal type_signal_guild_member_list_update; + typedef sigc::signal type_signal_guild_create; + typedef sigc::signal type_signal_guild_delete; + typedef sigc::signal type_signal_channel_delete; + typedef sigc::signal type_signal_channel_update; + typedef sigc::signal type_signal_channel_create; + typedef sigc::signal type_signal_guild_update; + typedef sigc::signal type_signal_role_update; // guild id, role id + typedef sigc::signal type_signal_role_create; // guild id, role id + typedef sigc::signal type_signal_role_delete; // guild id, role id + typedef sigc::signal type_signal_reaction_add; + typedef sigc::signal type_signal_reaction_remove; + typedef sigc::signal type_signal_typing_start; // user id, channel id + typedef sigc::signal type_signal_guild_member_update; // guild id, user id + typedef sigc::signal type_signal_guild_ban_remove; // guild id, user id + typedef sigc::signal type_signal_guild_ban_add; // guild id, user id + typedef sigc::signal type_signal_invite_create; + typedef sigc::signal type_signal_invite_delete; + typedef sigc::signal type_signal_presence_update; + typedef sigc::signal type_signal_note_update; + typedef sigc::signal> type_signal_guild_emojis_update; // guild id + typedef sigc::signal type_signal_guild_join_request_create; + typedef sigc::signal type_signal_guild_join_request_update; + typedef sigc::signal type_signal_guild_join_request_delete; + typedef sigc::signal type_signal_relationship_remove; + typedef sigc::signal type_signal_relationship_add; + typedef sigc::signal type_signal_thread_create; + typedef sigc::signal type_signal_thread_delete; + typedef sigc::signal type_signal_thread_list_sync; + typedef sigc::signal type_signal_thread_members_update; + typedef sigc::signal type_signal_thread_update; + typedef sigc::signal type_signal_thread_member_list_update; + + // not discord dispatch events + typedef sigc::signal type_signal_added_to_thread; + typedef sigc::signal type_signal_removed_from_thread; + typedef sigc::signal type_signal_message_unpinned; + typedef sigc::signal type_signal_message_pinned; + typedef sigc::signal type_signal_message_sent; + + typedef sigc::signal type_signal_message_send_fail; // retry after param will be 0 if it failed for a reason that isnt slowmode + typedef sigc::signal type_signal_disconnected; // bool true if reconnecting + typedef sigc::signal type_signal_connected; + + type_signal_gateway_ready signal_gateway_ready(); + type_signal_message_create signal_message_create(); + type_signal_message_delete signal_message_delete(); + type_signal_message_update signal_message_update(); + type_signal_guild_member_list_update signal_guild_member_list_update(); + type_signal_guild_create signal_guild_create(); // structs are complete in this signal + type_signal_guild_delete signal_guild_delete(); + type_signal_channel_delete signal_channel_delete(); + type_signal_channel_update signal_channel_update(); + type_signal_channel_create signal_channel_create(); + type_signal_guild_update signal_guild_update(); + type_signal_role_update signal_role_update(); + type_signal_role_create signal_role_create(); + type_signal_role_delete signal_role_delete(); + type_signal_reaction_add signal_reaction_add(); + type_signal_reaction_remove signal_reaction_remove(); + type_signal_typing_start signal_typing_start(); + type_signal_guild_member_update signal_guild_member_update(); + type_signal_guild_ban_remove signal_guild_ban_remove(); + type_signal_guild_ban_add signal_guild_ban_add(); + type_signal_invite_create signal_invite_create(); + type_signal_invite_delete signal_invite_delete(); // safe to assume guild id is set + type_signal_presence_update signal_presence_update(); + type_signal_note_update signal_note_update(); + type_signal_guild_emojis_update signal_guild_emojis_update(); + type_signal_guild_join_request_create signal_guild_join_request_create(); + type_signal_guild_join_request_update signal_guild_join_request_update(); + type_signal_guild_join_request_delete signal_guild_join_request_delete(); + type_signal_relationship_remove signal_relationship_remove(); + type_signal_relationship_add signal_relationship_add(); + type_signal_message_unpinned signal_message_unpinned(); + type_signal_message_pinned signal_message_pinned(); + type_signal_thread_create signal_thread_create(); + type_signal_thread_delete signal_thread_delete(); + type_signal_thread_list_sync signal_thread_list_sync(); + 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_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_message_send_fail signal_message_send_fail(); + type_signal_disconnected signal_disconnected(); + type_signal_connected signal_connected(); + +protected: + type_signal_gateway_ready m_signal_gateway_ready; + type_signal_message_create m_signal_message_create; + type_signal_message_delete m_signal_message_delete; + type_signal_message_update m_signal_message_update; + type_signal_guild_member_list_update m_signal_guild_member_list_update; + type_signal_guild_create m_signal_guild_create; + type_signal_guild_delete m_signal_guild_delete; + type_signal_channel_delete m_signal_channel_delete; + type_signal_channel_update m_signal_channel_update; + type_signal_channel_create m_signal_channel_create; + type_signal_guild_update m_signal_guild_update; + type_signal_role_update m_signal_role_update; + type_signal_role_create m_signal_role_create; + type_signal_role_delete m_signal_role_delete; + type_signal_reaction_add m_signal_reaction_add; + type_signal_reaction_remove m_signal_reaction_remove; + type_signal_typing_start m_signal_typing_start; + type_signal_guild_member_update m_signal_guild_member_update; + type_signal_guild_ban_remove m_signal_guild_ban_remove; + type_signal_guild_ban_add m_signal_guild_ban_add; + type_signal_invite_create m_signal_invite_create; + type_signal_invite_delete m_signal_invite_delete; + type_signal_presence_update m_signal_presence_update; + type_signal_note_update m_signal_note_update; + type_signal_guild_emojis_update m_signal_guild_emojis_update; + type_signal_guild_join_request_create m_signal_guild_join_request_create; + type_signal_guild_join_request_update m_signal_guild_join_request_update; + type_signal_guild_join_request_delete m_signal_guild_join_request_delete; + type_signal_relationship_remove m_signal_relationship_remove; + type_signal_relationship_add m_signal_relationship_add; + type_signal_message_unpinned m_signal_message_unpinned; + type_signal_message_pinned m_signal_message_pinned; + type_signal_thread_create m_signal_thread_create; + type_signal_thread_delete m_signal_thread_delete; + type_signal_thread_list_sync m_signal_thread_list_sync; + 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_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_message_send_fail m_signal_message_send_fail; + type_signal_disconnected m_signal_disconnected; + type_signal_connected m_signal_connected; +}; -- cgit v1.2.3