diff options
-rw-r--r-- | components/channels.cpp | 86 | ||||
-rw-r--r-- | components/channels.hpp | 12 | ||||
-rw-r--r-- | discord/channel.cpp | 8 | ||||
-rw-r--r-- | discord/channel.hpp | 17 | ||||
-rw-r--r-- | discord/discord.cpp | 10 | ||||
-rw-r--r-- | discord/guild.cpp | 1 | ||||
-rw-r--r-- | discord/guild.hpp | 1 | ||||
-rw-r--r-- | discord/store.cpp | 15 |
8 files changed, 136 insertions, 14 deletions
diff --git a/components/channels.cpp b/components/channels.cpp index 3e7862b..956fee1 100644 --- a/components/channels.cpp +++ b/components/channels.cpp @@ -22,15 +22,20 @@ ChannelList::ChannelList() const auto cb = [this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column) { auto row = *m_model->get_iter(path); - if (row[m_columns.m_expanded]) { - m_view.collapse_row(path); - row[m_columns.m_expanded] = false; - } else { - m_view.expand_row(path, false); - row[m_columns.m_expanded] = true; + const auto type = row[m_columns.m_type]; + // text channels should not be allowed to be collapsed + // maybe they should be but it seems a little difficult to handle expansion to permit this + if (type != RenderType::TextChannel) { + if (row[m_columns.m_expanded]) { + m_view.collapse_row(path); + row[m_columns.m_expanded] = false; + } else { + m_view.expand_row(path, false); + row[m_columns.m_expanded] = true; + } } - if (row[m_columns.m_type] == RenderType::TextChannel || row[m_columns.m_type] == RenderType::DM) { + if (type == RenderType::TextChannel || type == RenderType::DM || type == RenderType::Thread) { m_signal_action_channel_item_select.emit(static_cast<Snowflake>(row[m_columns.m_id])); } }; @@ -313,6 +318,28 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { } } + std::map<Snowflake, std::vector<ChannelData>> threads; + for (const auto &tmp : *guild.Threads) { + const auto thread = discord.GetChannel(tmp.ID); + if (thread.has_value()) + threads[*thread->ParentID].push_back(*thread); + } + const auto add_threads = [&](const ChannelData &channel, Gtk::TreeRow row) { + row[m_columns.m_expanded] = true; + + const auto it = threads.find(channel.ID); + if (it == threads.end()) return; + + for (const auto &thread : it->second) { + auto thread_row = *m_model->append(row.children()); + thread_row[m_columns.m_type] = RenderType::Thread; + thread_row[m_columns.m_id] = thread.ID; + thread_row[m_columns.m_name] = "- " + Glib::Markup::escape_text(*thread.Name); + thread_row[m_columns.m_sort] = thread.ID; + thread_row[m_columns.m_nsfw] = false; + } + }; + for (const auto &channel : orphan_channels) { auto channel_row = *m_model->append(guild_row.children()); channel_row[m_columns.m_type] = RenderType::TextChannel; @@ -320,6 +347,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name); channel_row[m_columns.m_sort] = *channel.Position + OrphanChannelSortOffset; channel_row[m_columns.m_nsfw] = channel.NSFW(); + add_threads(channel, channel_row); } for (const auto &[category_id, channels] : categories) { @@ -340,6 +368,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name); channel_row[m_columns.m_sort] = *channel.Position; channel_row[m_columns.m_nsfw] = channel.NSFW(); + add_threads(channel, channel_row); } } @@ -423,7 +452,7 @@ bool ChannelList::SelectionFunc(const Glib::RefPtr<Gtk::TreeModel> &model, const m_last_selected = m_model->get_path(row); auto type = (*m_model->get_iter(path))[m_columns.m_type]; - return type == RenderType::TextChannel || type == RenderType::DM; + return type == RenderType::TextChannel || type == RenderType::DM || type == RenderType::Thread; } void ChannelList::AddPrivateChannels() { @@ -614,6 +643,8 @@ void CellRendererChannels::get_preferred_width_vfunc(Gtk::Widget &widget, int &m return get_preferred_width_vfunc_category(widget, minimum_width, natural_width); case RenderType::TextChannel: return get_preferred_width_vfunc_channel(widget, minimum_width, natural_width); + case RenderType::Thread: + return get_preferred_width_vfunc_thread(widget, minimum_width, natural_width); case RenderType::DMHeader: return get_preferred_width_vfunc_dmheader(widget, minimum_width, natural_width); case RenderType::DM: @@ -629,6 +660,8 @@ void CellRendererChannels::get_preferred_width_for_height_vfunc(Gtk::Widget &wid return get_preferred_width_for_height_vfunc_category(widget, height, minimum_width, natural_width); case RenderType::TextChannel: return get_preferred_width_for_height_vfunc_channel(widget, height, minimum_width, natural_width); + case RenderType::Thread: + return get_preferred_width_for_height_vfunc_thread(widget, height, minimum_width, natural_width); case RenderType::DMHeader: return get_preferred_width_for_height_vfunc_dmheader(widget, height, minimum_width, natural_width); case RenderType::DM: @@ -644,6 +677,8 @@ void CellRendererChannels::get_preferred_height_vfunc(Gtk::Widget &widget, int & return get_preferred_height_vfunc_category(widget, minimum_height, natural_height); case RenderType::TextChannel: return get_preferred_height_vfunc_channel(widget, minimum_height, natural_height); + case RenderType::Thread: + return get_preferred_height_vfunc_thread(widget, minimum_height, natural_height); case RenderType::DMHeader: return get_preferred_height_vfunc_dmheader(widget, minimum_height, natural_height); case RenderType::DM: @@ -659,6 +694,8 @@ void CellRendererChannels::get_preferred_height_for_width_vfunc(Gtk::Widget &wid return get_preferred_height_for_width_vfunc_category(widget, width, minimum_height, natural_height); case RenderType::TextChannel: return get_preferred_height_for_width_vfunc_channel(widget, width, minimum_height, natural_height); + case RenderType::Thread: + return get_preferred_height_for_width_vfunc_thread(widget, width, minimum_height, natural_height); case RenderType::DMHeader: return get_preferred_height_for_width_vfunc_dmheader(widget, width, minimum_height, natural_height); case RenderType::DM: @@ -674,6 +711,8 @@ void CellRendererChannels::render_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, return render_vfunc_category(cr, widget, background_area, cell_area, flags); case RenderType::TextChannel: return render_vfunc_channel(cr, widget, background_area, cell_area, flags); + case RenderType::Thread: + return render_vfunc_thread(cr, widget, background_area, cell_area, flags); case RenderType::DMHeader: return render_vfunc_dmheader(cr, widget, background_area, cell_area, flags); case RenderType::DM: @@ -883,6 +922,37 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtr<Cairo::Conte m_renderer_text.property_foreground_set() = false; } +// thread + +void CellRendererChannels::get_preferred_width_vfunc_thread(Gtk::Widget &widget, int &minimum_width, int &natural_width) const { + m_renderer_text.get_preferred_width(widget, minimum_width, natural_width); +} + +void CellRendererChannels::get_preferred_width_for_height_vfunc_thread(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const { + get_preferred_width_vfunc_thread(widget, minimum_width, natural_width); +} + +void CellRendererChannels::get_preferred_height_vfunc_thread(Gtk::Widget &widget, int &minimum_height, int &natural_height) const { + m_renderer_text.get_preferred_height(widget, minimum_height, natural_height); +} + +void CellRendererChannels::get_preferred_height_for_width_vfunc_thread(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const { + get_preferred_height_vfunc_thread(widget, minimum_height, natural_height); +} + +void CellRendererChannels::render_vfunc_thread(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) { + Gtk::Requisition minimum_size, natural_size; + m_renderer_text.get_preferred_size(widget, minimum_size, natural_size); + + const int text_x = background_area.get_x() + 26; + const int text_y = background_area.get_y() + background_area.get_height() / 2 - natural_size.height / 2; + const int text_w = natural_size.width; + const int text_h = natural_size.height; + + Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h); + m_renderer_text.render(cr, widget, background_area, text_cell_area, flags); +} + // dm header void CellRendererChannels::get_preferred_width_vfunc_dmheader(Gtk::Widget &widget, int &minimum_width, int &natural_width) const { diff --git a/components/channels.hpp b/components/channels.hpp index 69f1f77..3b81cab 100644 --- a/components/channels.hpp +++ b/components/channels.hpp @@ -16,6 +16,7 @@ enum class RenderType : uint8_t { Guild, Category, TextChannel, + Thread, DMHeader, DM, @@ -77,6 +78,17 @@ protected: const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags); + // thread + void get_preferred_width_vfunc_thread(Gtk::Widget &widget, int &minimum_width, int &natural_width) const; + void get_preferred_width_for_height_vfunc_thread(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const; + void get_preferred_height_vfunc_thread(Gtk::Widget &widget, int &minimum_height, int &natural_height) const; + void get_preferred_height_for_width_vfunc_thread(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const; + void render_vfunc_thread(const Cairo::RefPtr<Cairo::Context> &cr, + Gtk::Widget &widget, + const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, + Gtk::CellRendererState flags); + // dm header void get_preferred_width_vfunc_dmheader(Gtk::Widget &widget, int &minimum_width, int &natural_width) const; void get_preferred_width_for_height_vfunc_dmheader(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const; diff --git a/discord/channel.cpp b/discord/channel.cpp index 9d90eb5..e5dfb06 100644 --- a/discord/channel.cpp +++ b/discord/channel.cpp @@ -1,6 +1,13 @@ #include "../abaddon.hpp" #include "channel.hpp" +void from_json(const nlohmann::json &j, ThreadMetadata &m) { + JS_D("archived", m.IsArchived); + JS_D("auto_archive_duration", m.AutoArchiveDuration); + JS_D("archive_timestamp", m.ArchiveTimestamp); + JS_O("locked", m.IsLocked); +} + void from_json(const nlohmann::json &j, ChannelData &m) { JS_D("id", m.ID); JS_D("type", m.Type); @@ -21,6 +28,7 @@ void from_json(const nlohmann::json &j, ChannelData &m) { JS_O("application_id", m.ApplicationID); JS_ON("parent_id", m.ParentID); JS_ON("last_pin_timestamp", m.LastPinTimestamp); + JS_O("thread_metadata", m.ThreadMetadata); } void ChannelData::update_from_json(const nlohmann::json &j) { diff --git a/discord/channel.hpp b/discord/channel.hpp index 68597e6..d262ddf 100644 --- a/discord/channel.hpp +++ b/discord/channel.hpp @@ -15,9 +15,10 @@ enum class ChannelType : int { GUILD_NEWS = 5, GUILD_STORE = 6, /* 7 and 8 were used for LFG */ - /* 9 and 10 were used for threads */ - PUBLIC_THREAD = 11, - PRIVATE_THREAD = 12, + /* 9 was used for threads */ + GUILD_NEWS_THREAD = 10, + GUILD_PUBLIC_THREAD = 11, + GUILD_PRIVATE_THREAD = 12, GUILD_STAGE_VOICE = 13, }; @@ -37,6 +38,15 @@ constexpr const char *GetStagePrivacyDisplayString(StagePrivacy e) { } } +struct ThreadMetadata { + bool IsArchived; + int AutoArchiveDuration; + std::string ArchiveTimestamp; + std::optional<bool> IsLocked; + + friend void from_json(const nlohmann::json &j, ThreadMetadata &m); +}; + struct ChannelData { Snowflake ID; ChannelType Type; @@ -57,6 +67,7 @@ struct ChannelData { std::optional<Snowflake> ApplicationID; std::optional<Snowflake> ParentID; // null std::optional<std::string> LastPinTimestamp; // null + std::optional<ThreadMetadata> ThreadMetadata; friend void from_json(const nlohmann::json &j, ChannelData &m); void update_from_json(const nlohmann::json &j); diff --git a/discord/discord.cpp b/discord/discord.cpp index 8dc1ef8..2c0c7d7 100644 --- a/discord/discord.cpp +++ b/discord/discord.cpp @@ -1209,7 +1209,7 @@ void DiscordClient::ProcessNewGuild(GuildData &guild) { m_store.BeginTransaction(); m_store.SetGuild(guild.ID, guild); - if (guild.Channels.has_value()) + if (guild.Channels.has_value()) { for (auto &c : *guild.Channels) { c.GuildID = guild.ID; m_store.SetChannel(c.ID, c); @@ -1218,6 +1218,14 @@ void DiscordClient::ProcessNewGuild(GuildData &guild) { m_store.SetPermissionOverwrite(c.ID, p.ID, p); } } + } + + if (guild.Threads.has_value()) { + for (auto& c : *guild.Threads) { + c.GuildID = guild.ID; + m_store.SetChannel(c.ID, c); + } + } for (auto &r : *guild.Roles) m_store.SetRole(r.ID, r); diff --git a/discord/guild.cpp b/discord/guild.cpp index d387729..1c79a38 100644 --- a/discord/guild.cpp +++ b/discord/guild.cpp @@ -43,6 +43,7 @@ void from_json(const nlohmann::json &j, GuildData &m) { // JS_O("voice_states", m.VoiceStates); // JS_O("members", m.Members); JS_O("channels", m.Channels); + JS_O("threads", m.Threads); // JS_O("presences", m.Presences); JS_ON("max_presences", m.MaxPresences); JS_O("max_members", m.MaxMembers); diff --git a/discord/guild.hpp b/discord/guild.hpp index a57bc15..3c3828d 100644 --- a/discord/guild.hpp +++ b/discord/guild.hpp @@ -80,6 +80,7 @@ struct GuildData { std::optional<int> MaxVideoChannelUsers; std::optional<int> ApproximateMemberCount; std::optional<int> ApproximatePresenceCount; + std::optional<std::vector<ChannelData>> Threads; // only with permissions to view, id only // undocumented // std::map<std::string, Unknown> GuildHashes; diff --git a/discord/store.cpp b/discord/store.cpp index 2439da5..3cac46e 100644 --- a/discord/store.cpp +++ b/discord/store.cpp @@ -194,6 +194,12 @@ void Store::SetGuild(Snowflake id, const GuildData &guild) { Bind(m_set_guild_stmt, 37, guild.ApproximateMemberCount); Bind(m_set_guild_stmt, 38, guild.ApproximatePresenceCount); Bind(m_set_guild_stmt, 39, guild.IsLazy); + if (guild.Threads.has_value()) { + snowflakes.clear(); + for (const auto &x : *guild.Threads) snowflakes.push_back(x.ID); + Bind(m_set_guild_stmt, 40, nlohmann::json(snowflakes).dump()); + } else + Bind(m_set_guild_stmt, 40, "[]"s); if (!RunInsert(m_set_guild_stmt)) fprintf(stderr, "guild insert failed: %s\n", sqlite3_errstr(m_db_err)); @@ -626,6 +632,10 @@ std::optional<GuildData> Store::GetGuild(Snowflake id) const { Get(m_get_guild_stmt, 36, ret.ApproximateMemberCount); Get(m_get_guild_stmt, 37, ret.ApproximatePresenceCount); Get(m_get_guild_stmt, 38, ret.IsLazy); + Get(m_get_guild_stmt, 39, tmp); + ret.Threads.emplace(); + for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>()) + ret.Threads->emplace_back().ID = id; Reset(m_get_guild_stmt); @@ -934,7 +944,8 @@ bool Store::CreateTables() { max_video_users INTEGER, approx_members INTEGER, approx_presences INTEGER, - lazy BOOL + lazy BOOL, + threads TEXT NOT NULL /* json */ ) )"; @@ -1116,7 +1127,7 @@ bool Store::CreateStatements() { const char *set_guild = R"( REPLACE INTO guilds VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) )"; |