diff options
author | ouwou <26526779+ouwou@users.noreply.github.com> | 2021-06-04 02:39:35 -0400 |
---|---|---|
committer | ouwou <26526779+ouwou@users.noreply.github.com> | 2021-06-04 02:39:35 -0400 |
commit | 43ea62d444b41e9ffff785ede2620b2e64fbb3b7 (patch) | |
tree | 95ccd96da8a15a4ce6586b8a065277770999087a | |
parent | 79c00c68e5845b496f6d3163d6fbc6e1c6de56ae (diff) | |
parent | 28b33d1b220aef5558ef58eb1326510ccccf5361 (diff) | |
download | abaddon-portaudio-43ea62d444b41e9ffff785ede2620b2e64fbb3b7.tar.gz abaddon-portaudio-43ea62d444b41e9ffff785ede2620b2e64fbb3b7.zip |
merge
-rw-r--r-- | .github/workflows/ci.yml | 2 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | abaddon.cpp | 12 | ||||
-rw-r--r-- | abaddon.hpp | 3 | ||||
m--------- | ci/vcpkg | 0 | ||||
-rw-r--r-- | components/chatmessage.cpp | 2 | ||||
-rw-r--r-- | components/memberlist.cpp | 15 | ||||
-rw-r--r-- | components/memberlist.hpp | 2 | ||||
-rw-r--r-- | css/application-low-priority.css | 86 | ||||
-rw-r--r-- | css/main.css | 109 | ||||
-rw-r--r-- | dialogs/token.cpp | 9 | ||||
-rw-r--r-- | discord/discord.cpp | 23 | ||||
-rw-r--r-- | discord/discord.hpp | 7 | ||||
-rw-r--r-- | discord/httpclient.cpp | 7 | ||||
-rw-r--r-- | discord/httpclient.hpp | 4 | ||||
-rw-r--r-- | discord/member.cpp | 2 | ||||
-rw-r--r-- | discord/member.hpp | 3 | ||||
-rw-r--r-- | discord/permissions.hpp | 23 | ||||
-rw-r--r-- | discord/store.cpp | 5 | ||||
-rw-r--r-- | discord/user.cpp | 26 | ||||
-rw-r--r-- | discord/user.hpp | 6 | ||||
-rw-r--r-- | http.cpp | 12 | ||||
-rw-r--r-- | http.hpp | 2 | ||||
-rw-r--r-- | settings.cpp | 8 | ||||
-rw-r--r-- | settings.hpp | 2 | ||||
-rw-r--r-- | windows/guildsettings/rolespane.cpp | 4 | ||||
-rw-r--r-- | windows/profile/userinfopane.cpp | 36 | ||||
-rw-r--r-- | windows/profile/userinfopane.hpp | 13 | ||||
-rw-r--r-- | windows/profilewindow.cpp | 2 |
29 files changed, 296 insertions, 131 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c35fbb..fa6ad0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: windows: name: windows-${{ matrix.buildtype }} - runs-on: windows-latest + runs-on: windows-2016 strategy: matrix: buildtype: [Debug, RelWithDebInfo] @@ -189,6 +189,8 @@ For example, memory_db would be set by adding `memory_db = true` under the line * css (string) - path to the main CSS file * animations (true or false, default true) - use animated images where available (e.g. server icons, emojis, avatars). false means static images will be used * owner_crown (true or false, default true) - show a crown next to the owner +* gateway (string) - override url for Discord gateway. must be json format and use zlib stream compression +* api_base (string) - override base url for Discord API #### misc * linkcolor (string) - color to use for links in messages diff --git a/abaddon.cpp b/abaddon.cpp index 0bb5f84..fa344b2 100644 --- a/abaddon.cpp +++ b/abaddon.cpp @@ -69,7 +69,6 @@ Abaddon &Abaddon::Get() { int Abaddon::StartGTK() { m_gtk_app = Gtk::Application::create("com.github.uowuo.abaddon"); - // tmp css stuff m_css_provider = Gtk::CssProvider::create(); m_css_provider->signal_parsing_error().connect([this](const Glib::RefPtr<const Gtk::CssSection> §ion, const Glib::Error &error) { Gtk::MessageDialog dlg(*m_main_window, "css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); @@ -77,6 +76,13 @@ int Abaddon::StartGTK() { dlg.run(); }); + m_css_low_provider = Gtk::CssProvider::create(); + m_css_low_provider->signal_parsing_error().connect([this](const Glib::RefPtr<const Gtk::CssSection> §ion, const Glib::Error &error) { + Gtk::MessageDialog dlg(*m_main_window, "low-priority css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); + dlg.set_position(Gtk::WIN_POS_CENTER); + dlg.run(); + }); + m_main_window = std::make_unique<MainWindow>(); m_main_window->set_title(APP_TITLE); m_main_window->UpdateComponents(); @@ -631,6 +637,10 @@ void Abaddon::ActionReloadCSS() { Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_provider); m_css_provider->load_from_path(m_settings.GetMainCSS()); Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider); + m_css_low_provider->load_from_path("./css/application-low-priority.css"); + Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1); } catch (Glib::Error &e) { Gtk::MessageDialog dlg(*m_main_window, "css failed to load (" + e.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); diff --git a/abaddon.hpp b/abaddon.hpp index c8c4351..bee8e75 100644 --- a/abaddon.hpp +++ b/abaddon.hpp @@ -128,5 +128,6 @@ private: mutable std::mutex m_mutex; Glib::RefPtr<Gtk::Application> m_gtk_app; Glib::RefPtr<Gtk::CssProvider> m_css_provider; - std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you + Glib::RefPtr<Gtk::CssProvider> m_css_low_provider; // registered with a lower priority to allow better customization + std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you }; diff --git a/ci/vcpkg b/ci/vcpkg -Subproject 50ea8c0ab7aca3bb9245bba7fc877ad2f2a4464 +Subproject a9b27ed5dffbf70b135eddb0c4729f3ca87f106 diff --git a/components/chatmessage.cpp b/components/chatmessage.cpp index 28e44fc..0bda45e 100644 --- a/components/chatmessage.cpp +++ b/components/chatmessage.cpp @@ -1086,7 +1086,7 @@ ChatMessageHeader::ChatMessageHeader(const Message *data) { m_static_avatar = pb->scale_simple(AvatarSize, AvatarSize, Gdk::INTERP_BILINEAR); m_avatar->property_pixbuf() = m_static_avatar; }; - img.LoadFromURL(author->GetAvatarURL(), sigc::track_obj(cb, *this)); + img.LoadFromURL(author->GetAvatarURL(data->GuildID), sigc::track_obj(cb, *this)); if (author->HasAnimatedAvatar()) { auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) { diff --git a/components/memberlist.cpp b/components/memberlist.cpp index 23f2383..ed02505 100644 --- a/components/memberlist.cpp +++ b/components/memberlist.cpp @@ -6,7 +6,7 @@ constexpr static const int MaxMemberListRows = 200; -MemberListUserRow::MemberListUserRow(const GuildData *guild, const UserData &data) { +MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, const UserData &data) { ID = data.ID; m_ev = Gtk::manage(new Gtk::EventBox); m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); @@ -15,7 +15,7 @@ MemberListUserRow::MemberListUserRow(const GuildData *guild, const UserData &dat m_status_indicator = Gtk::manage(new StatusIndicator(ID)); static bool crown = Abaddon::Get().GetSettings().GetShowOwnerCrown(); - if (crown && guild != nullptr && guild->OwnerID == data.ID) { + if (crown && guild.has_value() && guild->OwnerID == data.ID) { try { auto pixbuf = Gdk::Pixbuf::create_from_file("./res/crown.png", 12, 12); m_crown = Gtk::manage(new Gtk::Image(pixbuf)); @@ -26,7 +26,10 @@ MemberListUserRow::MemberListUserRow(const GuildData *guild, const UserData &dat m_status_indicator->set_margin_start(3); - m_avatar->SetURL(data.GetAvatarURL("png")); + if (guild.has_value()) + m_avatar->SetURL(data.GetAvatarURL(guild->ID, "png")); + else + m_avatar->SetURL(data.GetAvatarURL("png")); get_style_context()->add_class("members-row"); get_style_context()->add_class("members-row-member"); @@ -40,7 +43,7 @@ MemberListUserRow::MemberListUserRow(const GuildData *guild, const UserData &dat std::string display = data.Username; if (show_discriminator) display += "#" + data.Discriminator; - if (guild != nullptr) { + if (guild.has_value()) { if (const auto col_id = data.GetHoistedRole(guild->ID, true); col_id.IsValid()) { auto color = Abaddon::Get().GetDiscordClient().GetRole(col_id)->Color; m_label->set_use_markup(true); @@ -114,7 +117,7 @@ void MemberList::UpdateMemberList() { int num_rows = 0; for (const auto &user : chan->GetDMRecipients()) { if (num_rows++ > MaxMemberListRows) break; - auto *row = Gtk::manage(new MemberListUserRow(nullptr, user)); + auto *row = Gtk::manage(new MemberListUserRow(std::nullopt, user)); m_id_to_row[user.ID] = row; AttachUserMenuHandler(row, user.ID); m_listbox->add(*row); @@ -156,7 +159,7 @@ void MemberList::UpdateMemberList() { const auto guild = *discord.GetGuild(m_guild_id); auto add_user = [this, &user_to_color, &num_rows, guild](const UserData &data) -> bool { if (num_rows++ > MaxMemberListRows) return false; - auto *row = Gtk::manage(new MemberListUserRow(&guild, data)); + auto *row = Gtk::manage(new MemberListUserRow(guild, data)); m_id_to_row[data.ID] = row; AttachUserMenuHandler(row, data.ID); m_listbox->add(*row); diff --git a/components/memberlist.hpp b/components/memberlist.hpp index b765f13..180e76d 100644 --- a/components/memberlist.hpp +++ b/components/memberlist.hpp @@ -9,7 +9,7 @@ class LazyImage; class StatusIndicator; class MemberListUserRow : public Gtk::ListBoxRow { public: - MemberListUserRow(const GuildData *guild, const UserData &data); + MemberListUserRow(const std::optional<GuildData> &guild, const UserData &data); Snowflake ID; diff --git a/css/application-low-priority.css b/css/application-low-priority.css new file mode 100644 index 0000000..449127b --- /dev/null +++ b/css/application-low-priority.css @@ -0,0 +1,86 @@ +/* +application wide stuff +has to be separate to allow main.css to override certain things +*/ + +.app-window label:not(:disabled) { + color: @text_color; +} + +.app-window entry { + background: @secondary_color; + color: @text_color; + border: 1px solid #1c2e40; +} + +.app-window button { + background: @secondary_color; + color: @text_color; + text-shadow: none; + box-shadow: none; +} + +.app-window button:checked { + border-top: 0px; + border-left: 0px; + border-right: 0px; + border-bottom: 3px solid #39a2ed; + color: #ffffff; +} + +.app-window button:not(:checked) { + border: 3px #0000ff; +} + +.app-window.background { + background: @background_color; +} + +.app-window treeview { + color: @text_color; + background: @secondary_color; +} + +.app-popup list { + background: @secondary_color; +} + +.app-window paned separator { + background: @background_color; +} + +.app-window scrollbar { + background: @background_color; + border-left: 1px solid transparent; +} + +.app-window menubar, menu { + background: @background_color; + color: #cccccc; +} + +.app-window textview text { + caret-color: #ababab; +} + +.app-window check, +.app-window radio { + background-clip: padding-box; + background: @secondary_color; + border-color: #070707; + box-shadow: 0 1px rgba(0, 0, 0, 0); + color: #dddddd; +} + +.app-window check:checked, +.app-window radio:checked { + background-clip: border-box; + background: #0b4285; + border-color: #092444; + box-shadow: 0 1px rgba(0, 0, 0, 0); + color: #dddddd; +} + +.app-window colorswatch { + box-shadow: 0 1px rgba(0, 0, 0, 0); +} diff --git a/css/main.css b/css/main.css index 49d3e76..ee60cd1 100644 --- a/css/main.css +++ b/css/main.css @@ -57,14 +57,8 @@ padding: 15px; } -.message-container-author { - margin-top: -10px; -} - .message-container-extra { color: #78909c; - margin-left: -5px; - margin-right: -5px; } .message-container-timestamp { @@ -142,6 +136,26 @@ margin: 5px; } +.message-component { + margin: 5px; +} + +.message-component.primary { + background: #5865F2; +} + +.message-component.secondary, .message-component.link { + background: #4F545C; +} + +.message-component.success { + background: #43B581; +} + +.message-component.danger { + background: #F04747; +} + .reaction-box { padding: 2px 5px 2px 5px; margin: 0px 0px 0px 0px; @@ -177,62 +191,6 @@ color: @text_color; } -.app-window label:not(:disabled) { - color: @text_color; -} - -.app-window entry { - background: @secondary_color; - color: @text_color; - border: 1px solid #1c2e40; -} - -.app-window button { - background: @secondary_color; - color: @text_color; - text-shadow: none; - box-shadow: none; -} - -.app-window button:checked { - border-top: 0px; - border-left: 0px; - border-right: 0px; - border-bottom: 3px solid #39a2ed; - color: #ffffff; -} - -.app-window button:not(:checked) { - border: 3px #0000ff; -} - -.app-window.background { - background: @background_color; -} - -.app-window treeview { - color: @text_color; - background: @secondary_color; -} - -.app-popup list { - background: @secondary_color; -} - -.app-window paned separator { - background: @background_color; -} - -.app-window scrollbar { - background: @background_color; - border-left: 1px solid transparent; -} - -.app-window menubar, menu { - background: @background_color; - color: #cccccc; -} - .status-indicator.dnd { color: #982929; } @@ -304,37 +262,10 @@ padding-left: 5px; } -.app-window textview text { - caret-color: #ababab; -} - .guild-members-pane-info { padding: 10px; } - -.app-window check, -.app-window radio { - background-clip: padding-box; - background: @secondary_color; - border-color: #070707; - box-shadow: 0 1px rgba(0, 0, 0, 0); - color: #dddddd; -} - -.app-window check:checked, -.app-window radio:checked { - background-clip: border-box; - background: #0b4285; - border-color: #092444; - box-shadow: 0 1px rgba(0, 0, 0, 0); - color: #dddddd; -} - -.app-window colorswatch { - box-shadow: 0 1px rgba(0, 0, 0, 0); -} - .drag-hover-top { background: linear-gradient(to bottom, rgba(255, 66, 66, 0.65) 0%, rgba(0, 0, 0, 0) 35%); } diff --git a/dialogs/token.cpp b/dialogs/token.cpp index 9f5850a..b19cc6b 100644 --- a/dialogs/token.cpp +++ b/dialogs/token.cpp @@ -1,5 +1,12 @@ #include "token.hpp" +std::string trim(const std::string& str) { + const auto first = str.find_first_not_of(' '); + if (first == std::string::npos) return str; + const auto last = str.find_last_not_of(' '); + return str.substr(first, last - first + 1); +} + TokenDialog::TokenDialog(Gtk::Window &parent) : Gtk::Dialog("Set Token", parent, true) , m_layout(Gtk::ORIENTATION_VERTICAL) @@ -11,7 +18,7 @@ TokenDialog::TokenDialog(Gtk::Window &parent) get_style_context()->add_class("app-popup"); m_ok.signal_clicked().connect([&]() { - m_token = m_entry.get_text(); + m_token = trim(m_entry.get_text()); response(Gtk::RESPONSE_OK); }); diff --git a/discord/discord.cpp b/discord/discord.cpp index 0e06e39..c879501 100644 --- a/discord/discord.cpp +++ b/discord/discord.cpp @@ -1,10 +1,10 @@ #include "discord.hpp" #include <cassert> #include "../util.hpp" +#include "../abaddon.hpp" DiscordClient::DiscordClient(bool mem_store) - : m_http(DiscordAPI) - , m_decompress_buf(InflateChunkSize) + : m_decompress_buf(InflateChunkSize) , m_store(mem_store) { m_msg_dispatch.connect(sigc::mem_fun(*this, &DiscordClient::MessageDispatch)); auto dispatch_cb = [this]() { @@ -24,13 +24,15 @@ DiscordClient::DiscordClient(bool mem_store) } void DiscordClient::Start() { + m_http.SetBase(GetAPIURL()); + std::memset(&m_zstream, 0, sizeof(m_zstream)); inflateInit2(&m_zstream, MAX_WBITS + 32); m_last_sequence = -1; m_heartbeat_acked = true; m_client_connected = true; - m_websocket.StartConnection(DiscordGateway); + m_websocket.StartConnection(GetGatewayURL()); } void DiscordClient::Stop() { @@ -1104,6 +1106,17 @@ void DiscordClient::HandleGatewayHello(const GatewayMessage &msg) { SendIdentify(); } +// perhaps this should be set by the main class +std::string DiscordClient::GetAPIURL() { + static const auto url = Abaddon::Get().GetSettings().GetAPIBaseURL(); + return url; +} + +std::string DiscordClient::GetGatewayURL() { + static const auto url = Abaddon::Get().GetSettings().GetGatewayURL(); + return url; +} + DiscordError DiscordClient::GetCodeFromResponse(const http::response_type &response) { try { // pull me somewhere else? @@ -1576,7 +1589,7 @@ void DiscordClient::HandleGatewayReconnect(const GatewayMessage &msg) { std::memset(&m_zstream, 0, sizeof(m_zstream)); inflateInit2(&m_zstream, MAX_WBITS + 32); - m_websocket.StartConnection(DiscordGateway); + m_websocket.StartConnection(GetGatewayURL()); } void DiscordClient::HandleGatewayInvalidSession(const GatewayMessage &msg) { @@ -1597,7 +1610,7 @@ void DiscordClient::HandleGatewayInvalidSession(const GatewayMessage &msg) { m_websocket.Stop(1000); - m_websocket.StartConnection(DiscordGateway); + m_websocket.StartConnection(GetGatewayURL()); } void DiscordClient::HandleGatewayMessageUpdate(const GatewayMessage &msg) { diff --git a/discord/discord.hpp b/discord/discord.hpp index ff4fe2e..029cc71 100644 --- a/discord/discord.hpp +++ b/discord/discord.hpp @@ -50,10 +50,6 @@ class DiscordClient { friend class Abaddon; public: - static const constexpr char *DiscordGateway = "wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream"; - static const constexpr char *DiscordAPI = "https://discord.com/api/v9"; - -public: DiscordClient(bool mem_store = false); void Start(); void Stop(); @@ -191,6 +187,9 @@ private: std::vector<uint8_t> 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); diff --git a/discord/httpclient.cpp b/discord/httpclient.cpp index 7297643..05474df 100644 --- a/discord/httpclient.cpp +++ b/discord/httpclient.cpp @@ -1,11 +1,14 @@ #include "httpclient.hpp" //#define USE_LOCAL_PROXY -HTTPClient::HTTPClient(std::string api_base) - : m_api_base(api_base) { +HTTPClient::HTTPClient() { m_dispatcher.connect(sigc::mem_fun(*this, &HTTPClient::RunCallbacks)); } +void HTTPClient::SetBase(const std::string &url) { + m_api_base = url; +} + void HTTPClient::SetUserAgent(std::string agent) { m_agent = agent; } diff --git a/discord/httpclient.hpp b/discord/httpclient.hpp index 8038334..81723b8 100644 --- a/discord/httpclient.hpp +++ b/discord/httpclient.hpp @@ -11,7 +11,9 @@ class HTTPClient { public: - HTTPClient(std::string api_base); + HTTPClient(); + + void SetBase(const std::string &url); void SetUserAgent(std::string agent); void SetAuth(std::string auth); diff --git a/discord/member.cpp b/discord/member.cpp index fee007f..c7d2e4a 100644 --- a/discord/member.cpp +++ b/discord/member.cpp @@ -10,6 +10,7 @@ void from_json(const nlohmann::json &j, GuildMember &m) { JS_D("deaf", m.IsDeafened); JS_D("mute", m.IsMuted); JS_O("user_id", m.UserID); + JS_ON("avatar", m.Avatar); } std::vector<RoleData> GuildMember::GetSortedRoles() const { @@ -33,4 +34,5 @@ void GuildMember::update_from_json(const nlohmann::json &j) { JS_RD("nick", Nickname); JS_RD("joined_at", JoinedAt); JS_RD("premium_since", PremiumSince); + JS_RD("avatar", Avatar); } diff --git a/discord/member.hpp b/discord/member.hpp index 5910f18..ae93524 100644 --- a/discord/member.hpp +++ b/discord/member.hpp @@ -16,6 +16,9 @@ struct GuildMember { bool IsMuted; std::optional<Snowflake> UserID; // present in merged_members + // undocuemtned moment !!!1 + std::optional<std::string> Avatar; + std::vector<RoleData> GetSortedRoles() const; void update_from_json(const nlohmann::json &j); diff --git a/discord/permissions.hpp b/discord/permissions.hpp index d9cff11..5609135 100644 --- a/discord/permissions.hpp +++ b/discord/permissions.hpp @@ -40,10 +40,11 @@ enum class Permission : uint64_t { MANAGE_EMOJIS = (1ULL << 30), // Allows management and editing of emojis USE_SLASH_COMMANDS = (1ULL << 31), // Allows members to use slash commands in text channels REQUEST_TO_SPEAK = (1ULL << 32), // Allows for requesting to speak in stage channels - USE_THREADS = (1ULL << 33), // Allows for creating and participating in threads - USE_PRIVATE_THREADS = (1ULL << 34), // Allows for creating and participating in private threads + MANAGE_THREADS = (1ULL << 34), // Allows for deleting and archiving threads, and viewing all private threads + USE_PUBLIC_THREADS = (1ULL << 35), // Allows for creating and participating in threads + USE_PRIVATE_THREADS = (1ULL << 36), // Allows for creating and participating in private threads - ALL = 0x7FFFFFFFFULL, + ALL = 0x1FFFFFFFFFULL, }; template<> struct Bitwise<Permission> { @@ -107,7 +108,7 @@ constexpr const char *GetPermissionString(Permission perm) { case Permission::USE_EXTERNAL_EMOJIS: return "Use External Emojis"; case Permission::VIEW_GUILD_INSIGHTS: - return "View Guild Insights"; + return "View Server Insights"; case Permission::CONNECT: return "Connect to Voice"; case Permission::SPEAK: @@ -132,6 +133,12 @@ constexpr const char *GetPermissionString(Permission perm) { return "Manage Emojis"; case Permission::USE_SLASH_COMMANDS: return "Use Slash Commands"; + case Permission::MANAGE_THREADS: + return "Manage Threads"; + case Permission::USE_PUBLIC_THREADS: + return "Use Public Threads"; + case Permission::USE_PRIVATE_THREADS: + return "Use Private Threads"; default: return "Unknown Permission"; } @@ -180,7 +187,7 @@ constexpr const char *GetPermissionDescription(Permission perm) { case Permission::USE_EXTERNAL_EMOJIS: return "Allows members to use emoji from other servers, if they're a Discord Nitro member"; case Permission::VIEW_GUILD_INSIGHTS: - return ""; + return "Allows members to view Server Insights, which shows data on community growth, engagement, and more."; case Permission::CONNECT: return "Allows members to join voice channels and hear others."; case Permission::SPEAK: @@ -205,6 +212,12 @@ constexpr const char *GetPermissionDescription(Permission perm) { return "Allows members to add or remove custom emojis in this server."; case Permission::USE_SLASH_COMMANDS: return "Allows members to use slash commands in text channels."; + case Permission::MANAGE_THREADS: + return "Allows members to rename, delete, archive/unarchive, and turn on slow mode for threads."; + case Permission::USE_PUBLIC_THREADS: + return "Allows members to talk in threads. The \"Send Messages\" permission must be enabled for members to start new threads; if it's disabled, they can only respond to existing threads."; + case Permission::USE_PRIVATE_THREADS: + return "Allows members to create and chat in private threads. The \"Send Messages\" permission must be enabled for members to start new private threads; if it's disabled, they can only respond to private threads they're added to."; default: return ""; } diff --git a/discord/store.cpp b/discord/store.cpp index bd9ad32..e7699ba 100644 --- a/discord/store.cpp +++ b/discord/store.cpp @@ -210,6 +210,7 @@ void Store::SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMem Bind(m_set_member_stmt, 6, data.PremiumSince); Bind(m_set_member_stmt, 7, data.IsDeafened); Bind(m_set_member_stmt, 8, data.IsMuted); + Bind(m_set_member_stmt, 9, data.Avatar); if (!RunInsert(m_set_member_stmt)) fprintf(stderr, "member insert failed: %s\n", sqlite3_errstr(m_db_err)); @@ -520,6 +521,7 @@ std::optional<GuildMember> Store::GetGuildMember(Snowflake guild_id, Snowflake u Get(m_get_member_stmt, 5, ret.PremiumSince); Get(m_get_member_stmt, 6, ret.IsDeafened); Get(m_get_member_stmt, 7, ret.IsMuted); + Get(m_get_member_stmt, 8, ret.Avatar); Reset(m_get_member_stmt); @@ -823,6 +825,7 @@ bool Store::CreateTables() { premium_since TEXT, deaf BOOL NOT NULL, mute BOOL NOT NULL, + avatar TEXT, PRIMARY KEY(user_id, guild_id) ) )"; @@ -1039,7 +1042,7 @@ bool Store::CreateStatements() { constexpr const char *set_member = R"( REPLACE INTO members VALUES ( - ?, ?, ?, ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, ?, ?, ?, ? ) )"; diff --git a/discord/user.cpp b/discord/user.cpp index a0bce0b..a68346e 100644 --- a/discord/user.cpp +++ b/discord/user.cpp @@ -13,11 +13,33 @@ bool UserData::HasAnimatedAvatar() const { return Avatar.size() > 0 && Avatar[0] == 'a' && Avatar[1] == '_'; } +std::string UserData::GetAvatarURL(Snowflake guild_id, std::string ext, std::string size) const { + const auto member = Abaddon::Get().GetDiscordClient().GetMember(ID, guild_id); + if (member.has_value() && member->Avatar.has_value()) + return "https://cdn.discordapp.com/guilds/" + + std::to_string(guild_id) + "/users/" + std::to_string(ID) + + "/avatars/" + *member->Avatar + "." + + ext + "?" + "size=" + size; + else + return GetAvatarURL(ext, size); +} + +std::string UserData::GetAvatarURL(const std::optional<Snowflake> &guild_id, std::string ext, std::string size) const { + if (guild_id.has_value()) + return GetAvatarURL(*guild_id, ext, size); + else + return GetAvatarURL(ext, size); +} + std::string UserData::GetAvatarURL(std::string ext, std::string size) const { if (HasAvatar()) return "https://cdn.discordapp.com/avatars/" + std::to_string(ID) + "/" + Avatar + "." + ext + "?size=" + size; else - return "https://cdn.discordapp.com/embed/avatars/" + std::to_string(std::stoul(Discriminator) % 5) + ".png"; // size isn't respected by the cdn + return GetDefaultAvatarURL(); +} + +std::string UserData::GetDefaultAvatarURL() const { + return "https://cdn.discordapp.com/embed/avatars/" + std::to_string(std::stoul(Discriminator) % 5) + ".png"; // size isn't respected by the cdn } Snowflake UserData::GetHoistedRole(Snowflake guild_id, bool with_color) const { @@ -58,6 +80,8 @@ void from_json(const nlohmann::json &j, UserData &m) { JS_O("mobile", m.IsMobile); JS_ON("nsfw_allowed", m.IsNSFWAllowed); JS_ON("phone", m.Phone); + JS_ON("bio", m.Bio); + JS_ON("banner", m.BannerHash); } void to_json(nlohmann::json &j, const UserData &m) { diff --git a/discord/user.hpp b/discord/user.hpp index dc3223d..d4711fa 100644 --- a/discord/user.hpp +++ b/discord/user.hpp @@ -52,6 +52,9 @@ struct UserData { std::optional<bool> IsMobile; std::optional<bool> IsNSFWAllowed; // null std::optional<std::string> Phone; // null? + // for now (unserialized) + std::optional<std::string> BannerHash; // null + std::optional<std::string> Bio; // null friend void from_json(const nlohmann::json &j, UserData &m); friend void to_json(nlohmann::json &j, const UserData &m); @@ -60,7 +63,10 @@ struct UserData { bool IsDeleted() const; bool HasAvatar() const; bool HasAnimatedAvatar() const; + std::string GetAvatarURL(Snowflake guild_id, std::string ext = "png", std::string size = "32") const; + std::string GetAvatarURL(const std::optional<Snowflake> &guild_id, std::string ext = "png", std::string size = "32") const; std::string GetAvatarURL(std::string ext = "png", std::string size = "32") const; + std::string GetDefaultAvatarURL() const; Snowflake GetHoistedRole(Snowflake guild_id, bool with_color = false) const; std::string GetMention() const; std::string GetEscapedName() const; @@ -64,12 +64,14 @@ response request::execute() { detail::check_init(); std::string str; + curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, m_method); curl_easy_setopt(m_curl, CURLOPT_URL, m_url.c_str()); curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, detail::curl_write_data_callback); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &str); curl_easy_setopt(m_curl, CURLOPT_ERRORBUFFER, m_error_buf); + m_error_buf[0] = '\0'; if (m_header_list != nullptr) curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_header_list); @@ -81,7 +83,7 @@ response request::execute() { return response; } - int response_code = 0; + long response_code = 0; curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &response_code); auto response = detail::make_response(m_url, response_code); @@ -95,10 +97,10 @@ void request::prepare() { } namespace detail { - size_t curl_write_data_callback(void *ptr, size_t size, size_t nmemb, std::string *outstr) { - auto new_length = size * nmemb; - outstr->append(reinterpret_cast<char *>(ptr), new_length); - return new_length; + size_t curl_write_data_callback(void *ptr, size_t size, size_t nmemb, void *userdata) { + const size_t n = size * nmemb; + static_cast<std::string*>(userdata)->append(static_cast<char*>(ptr), n); + return n; } response make_response(const std::string &url, int code) { @@ -121,7 +121,7 @@ private: using response_type = response; namespace detail { - size_t curl_write_data_callback(void *ptr, size_t size, size_t nmemb, std::string *outstr); + size_t curl_write_data_callback(void *ptr, size_t size, size_t nmemb, void *userdata); response make_response(const std::string &url, int code); void check_init(); diff --git a/settings.cpp b/settings.cpp index fc6255d..6083d96 100644 --- a/settings.cpp +++ b/settings.cpp @@ -85,3 +85,11 @@ bool SettingsManager::GetShowAnimations() const { bool SettingsManager::GetShowOwnerCrown() const { return GetSettingBool("gui", "owner_crown", true); } + +std::string SettingsManager::GetGatewayURL() const { + return GetSettingString("discord", "gateway", "wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream"); +} + +std::string SettingsManager::GetAPIBaseURL() const { + return GetSettingString("discord", "api_base", "https://discord.com/api/v9"); +} diff --git a/settings.hpp b/settings.hpp index e2374b5..d77b418 100644 --- a/settings.hpp +++ b/settings.hpp @@ -21,6 +21,8 @@ public: std::string GetMainCSS() const; bool GetShowAnimations() const; bool GetShowOwnerCrown() const; + std::string GetGatewayURL() const; + std::string GetAPIBaseURL() const; bool IsValid() const; diff --git a/windows/guildsettings/rolespane.cpp b/windows/guildsettings/rolespane.cpp index 635c2e2..e60a540 100644 --- a/windows/guildsettings/rolespane.cpp +++ b/windows/guildsettings/rolespane.cpp @@ -263,6 +263,7 @@ GuildSettingsRolesPaneInfo::GuildSettingsRolesPaneInfo(Snowflake guild_id) Permission::MANAGE_ROLES, Permission::MANAGE_EMOJIS, Permission::VIEW_AUDIT_LOG, + Permission::VIEW_GUILD_INSIGHTS, Permission::MANAGE_WEBHOOKS, Permission::MANAGE_GUILD }); @@ -275,12 +276,15 @@ GuildSettingsRolesPaneInfo::GuildSettingsRolesPaneInfo(Snowflake guild_id) add_perms("Text Channels", RIGHT, { Permission::SEND_MESSAGES, + Permission::USE_PUBLIC_THREADS, + Permission::USE_PRIVATE_THREADS, Permission::EMBED_LINKS, Permission::ATTACH_FILES, Permission::ADD_REACTIONS, Permission::USE_EXTERNAL_EMOJIS, Permission::MENTION_EVERYONE, Permission::MANAGE_MESSAGES, + Permission::MANAGE_THREADS, Permission::READ_MESSAGE_HISTORY, Permission::SEND_TTS_MESSAGES, Permission::USE_SLASH_COMMANDS }); diff --git a/windows/profile/userinfopane.cpp b/windows/profile/userinfopane.cpp index 384f62c..e9309d6 100644 --- a/windows/profile/userinfopane.cpp +++ b/windows/profile/userinfopane.cpp @@ -158,6 +158,25 @@ NotesContainer::type_signal_update_note NotesContainer::signal_update_note() { return m_signal_update_note; } +BioContainer::BioContainer() + : Gtk::Box(Gtk::ORIENTATION_VERTICAL) { + m_label.set_markup("<b>ABOUT ME</b>"); + m_label.set_halign(Gtk::ALIGN_START); + m_bio.set_halign(Gtk::ALIGN_START); + m_bio.set_line_wrap(true); + m_bio.set_line_wrap_mode(Pango::WRAP_WORD_CHAR); + + m_label.show(); + m_bio.show(); + + add(m_label); + add(m_bio); +} + +void BioContainer::SetBio(const std::string &bio) { + m_bio.set_text(bio); +} + ProfileUserInfoPane::ProfileUserInfoPane(Snowflake ID) : Gtk::Box(Gtk::ORIENTATION_VERTICAL) , UserID(ID) { @@ -194,12 +213,23 @@ ProfileUserInfoPane::ProfileUserInfoPane(Snowflake ID) m_conns.set_halign(Gtk::ALIGN_START); m_conns.set_hexpand(true); + m_created.show(); + m_note.show(); + m_conns.show(); + add(m_created); + add(m_bio); add(m_note); add(m_conns); - show_all_children(); } -void ProfileUserInfoPane::SetConnections(const std::vector<ConnectionData> &connections) { - m_conns.SetConnections(connections); +void ProfileUserInfoPane::SetProfile(const UserProfileData &data) { + if (data.User.Bio.has_value() && *data.User.Bio != "") { + m_bio.SetBio(*data.User.Bio); + m_bio.show(); + } else { + m_bio.hide(); + } + + m_conns.SetConnections(data.ConnectedAccounts); } diff --git a/windows/profile/userinfopane.hpp b/windows/profile/userinfopane.hpp index 169ac25..b29cb76 100644 --- a/windows/profile/userinfopane.hpp +++ b/windows/profile/userinfopane.hpp @@ -39,16 +39,27 @@ public: type_signal_update_note signal_update_note(); }; +class BioContainer : public Gtk::Box { +public: + BioContainer(); + void SetBio(const std::string &bio); + +private: + Gtk::Label m_label; + Gtk::Label m_bio; +}; + class ProfileUserInfoPane : public Gtk::Box { public: ProfileUserInfoPane(Snowflake ID); - void SetConnections(const std::vector<ConnectionData> &connections); + void SetProfile(const UserProfileData &data); Snowflake UserID; private: Gtk::Label m_created; + BioContainer m_bio; NotesContainer m_note; ConnectionsContainer m_conns; }; diff --git a/windows/profilewindow.cpp b/windows/profilewindow.cpp index d0fefc7..90e98f1 100644 --- a/windows/profilewindow.cpp +++ b/windows/profilewindow.cpp @@ -96,7 +96,7 @@ ProfileWindow::ProfileWindow(Snowflake user_id) } void ProfileWindow::OnFetchProfile(const UserProfileData &data) { - m_pane_info.SetConnections(data.ConnectedAccounts); + m_pane_info.SetProfile(data); m_pane_guilds.SetMutualGuilds(data.MutualGuilds); for (auto child : m_badges.get_children()) |