summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--components/channels.cpp126
-rw-r--r--components/channels.hpp16
-rw-r--r--components/chatmessage.cpp12
-rw-r--r--discord/channel.cpp16
-rw-r--r--discord/channel.hpp29
-rw-r--r--discord/discord.cpp28
-rw-r--r--discord/discord.hpp6
-rw-r--r--discord/guild.cpp1
-rw-r--r--discord/guild.hpp1
-rw-r--r--discord/message.hpp2
-rw-r--r--discord/objects.cpp4
-rw-r--r--discord/objects.hpp12
-rw-r--r--discord/store.cpp15
13 files changed, 234 insertions, 34 deletions
diff --git a/components/channels.cpp b/components/channels.cpp
index 3e7862b..a80cfd8 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]));
}
};
@@ -123,6 +128,7 @@ ChannelList::ChannelList()
discord.signal_channel_delete().connect(sigc::mem_fun(*this, &ChannelList::UpdateRemoveChannel));
discord.signal_channel_update().connect(sigc::mem_fun(*this, &ChannelList::UpdateChannel));
discord.signal_channel_create().connect(sigc::mem_fun(*this, &ChannelList::UpdateCreateChannel));
+ discord.signal_thread_create().connect(sigc::mem_fun(*this, &ChannelList::UpdateCreateThread));
discord.signal_guild_update().connect(sigc::mem_fun(*this, &ChannelList::UpdateGuild));
}
@@ -206,32 +212,31 @@ void ChannelList::UpdateChannel(Snowflake id) {
channel_row[m_columns.m_sort] = *channel->Position;
}
-void ChannelList::UpdateCreateChannel(Snowflake id) {
- const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(id);
- if (!channel.has_value()) return;
- if (channel->Type == ChannelType::GUILD_CATEGORY) return (void)UpdateCreateChannelCategory(*channel);
- if (channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM) return UpdateCreateDMChannel(*channel);
- if (channel->Type != ChannelType::GUILD_TEXT && channel->Type != ChannelType::GUILD_NEWS) return;
+void ChannelList::UpdateCreateChannel(const ChannelData &channel) {
+ ;
+ if (channel.Type == ChannelType::GUILD_CATEGORY) return (void)UpdateCreateChannelCategory(channel);
+ if (channel.Type == ChannelType::DM || channel.Type == ChannelType::GROUP_DM) return UpdateCreateDMChannel(channel);
+ if (channel.Type != ChannelType::GUILD_TEXT && channel.Type != ChannelType::GUILD_NEWS) return;
Gtk::TreeRow channel_row;
bool orphan;
- if (channel->ParentID.has_value()) {
+ if (channel.ParentID.has_value()) {
orphan = false;
- auto iter = GetIteratorForChannelFromID(*channel->ParentID);
+ auto iter = GetIteratorForChannelFromID(*channel.ParentID);
channel_row = *m_model->append(iter->children());
} else {
orphan = true;
- auto iter = GetIteratorForGuildFromID(*channel->GuildID);
+ auto iter = GetIteratorForGuildFromID(*channel.GuildID);
channel_row = *m_model->append(iter->children());
}
channel_row[m_columns.m_type] = RenderType::TextChannel;
- channel_row[m_columns.m_id] = channel->ID;
- channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel->Name);
- channel_row[m_columns.m_nsfw] = channel->NSFW();
+ channel_row[m_columns.m_id] = channel.ID;
+ channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name);
+ channel_row[m_columns.m_nsfw] = channel.NSFW();
if (orphan)
- channel_row[m_columns.m_sort] = *channel->Position + OrphanChannelSortOffset;
+ channel_row[m_columns.m_sort] = *channel.Position + OrphanChannelSortOffset;
else
- channel_row[m_columns.m_sort] = *channel->Position;
+ channel_row[m_columns.m_sort] = *channel.Position;
}
void ChannelList::UpdateGuild(Snowflake id) {
@@ -260,6 +265,12 @@ void ChannelList::UpdateGuild(Snowflake id) {
}
}
+void ChannelList::UpdateCreateThread(const ChannelData &channel) {
+ auto parent_row = GetIteratorForChannelFromID(*channel.ParentID);
+ if (parent_row)
+ CreateThreadRow(parent_row->children(), channel);
+}
+
void ChannelList::SetActiveChannel(Snowflake id) {
const auto channel_iter = GetIteratorForChannelFromID(id);
if (channel_iter) {
@@ -313,6 +324,22 @@ 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)
+ CreateThreadRow(row.children(), thread);
+ };
+
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);
}
}
@@ -360,6 +389,18 @@ Gtk::TreeModel::iterator ChannelList::UpdateCreateChannelCategory(const ChannelD
return cat_row;
}
+Gtk::TreeModel::iterator ChannelList::CreateThreadRow(const Gtk::TreeNodeChildren &children, const ChannelData &channel) {
+ auto thread_iter = m_model->append(children);
+ auto thread_row = *thread_iter;
+ thread_row[m_columns.m_type] = RenderType::Thread;
+ thread_row[m_columns.m_id] = channel.ID;
+ thread_row[m_columns.m_name] = "- " + Glib::Markup::escape_text(*channel.Name);
+ thread_row[m_columns.m_sort] = channel.ID;
+ thread_row[m_columns.m_nsfw] = false;
+
+ return thread_iter;
+}
+
void ChannelList::UpdateChannelCategory(const ChannelData &channel) {
auto iter = GetIteratorForChannelFromID(channel.ID);
if (!iter) return;
@@ -423,7 +464,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 +655,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 +672,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 +689,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 +706,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 +723,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 +934,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..bdc3e84 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;
@@ -127,7 +139,8 @@ protected:
void UpdateRemoveGuild(Snowflake id);
void UpdateRemoveChannel(Snowflake id);
void UpdateChannel(Snowflake id);
- void UpdateCreateChannel(Snowflake id);
+ void UpdateCreateChannel(const ChannelData &channel);
+ void UpdateCreateThread(const ChannelData &channel);
void UpdateGuild(Snowflake id);
Gtk::TreeView m_view;
@@ -156,6 +169,7 @@ protected:
Gtk::TreeModel::iterator AddGuild(const GuildData &guild);
Gtk::TreeModel::iterator UpdateCreateChannelCategory(const ChannelData &channel);
+ Gtk::TreeModel::iterator CreateThreadRow(const Gtk::TreeNodeChildren &children, const ChannelData &channel);
void UpdateChannelCategory(const ChannelData &channel);
diff --git a/components/chatmessage.cpp b/components/chatmessage.cpp
index 63db7e9..b7cdf17 100644
--- a/components/chatmessage.cpp
+++ b/components/chatmessage.cpp
@@ -38,8 +38,10 @@ ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(const Message &d
if ((data.MessageReference.has_value() || data.Interaction.has_value()) && data.Type != MessageType::CHANNEL_FOLLOW_ADD) {
auto *widget = container->CreateReplyComponent(data);
- container->m_main.add(*widget);
- container->m_main.child_property_position(*widget) = 0; // eek
+ if (widget != nullptr) {
+ container->m_main.add(*widget);
+ container->m_main.child_property_position(*widget) = 0; // eek
+ }
}
// there should only ever be 1 embed (i think?)
@@ -274,6 +276,10 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) {
case MessageType::GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING: {
b->insert_markup(s, "<i><span color='#999999'>This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery.</span></i>");
} break;
+ case MessageType::THREAD_CREATED: {
+ const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID);
+ b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " started a thread: </span><b>" + Glib::Markup::escape_text(data->Content) + "</b></i>");
+ } break;
default: break;
}
}
@@ -607,6 +613,8 @@ Gtk::Widget *ChatMessageItemContainer::CreateReactionsComponent(const Message &d
}
Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) {
+ if (data.Type == MessageType::THREAD_CREATED) return nullptr;
+
auto *box = Gtk::manage(new Gtk::Box);
auto *lbl = Gtk::manage(new Gtk::Label);
lbl->set_single_line_mode(true);
diff --git a/discord/channel.cpp b/discord/channel.cpp
index 9d90eb5..685cc72 100644
--- a/discord/channel.cpp
+++ b/discord/channel.cpp
@@ -1,6 +1,20 @@
#include "../abaddon.hpp"
#include "channel.hpp"
+void from_json(const nlohmann::json &j, ThreadMetadataData &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, ThreadMemberObject &m) {
+ JS_O("id", m.ThreadID);
+ JS_O("user_id", m.ThreadID);
+ JS_D("join_timestamp", m.JoinTimestamp);
+ JS_D("flags", m.Flags);
+}
+
void from_json(const nlohmann::json &j, ChannelData &m) {
JS_D("id", m.ID);
JS_D("type", m.Type);
@@ -21,6 +35,8 @@ 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);
+ JS_O("member", m.ThreadMember);
}
void ChannelData::update_from_json(const nlohmann::json &j) {
diff --git a/discord/channel.hpp b/discord/channel.hpp
index 68597e6..606686d 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,26 @@ constexpr const char *GetStagePrivacyDisplayString(StagePrivacy e) {
}
}
+// should be moved somewhere?
+
+struct ThreadMetadataData {
+ bool IsArchived;
+ int AutoArchiveDuration;
+ std::string ArchiveTimestamp;
+ std::optional<bool> IsLocked;
+
+ friend void from_json(const nlohmann::json &j, ThreadMetadataData &m);
+};
+
+struct ThreadMemberObject {
+ std::optional<Snowflake> ThreadID;
+ std::optional<Snowflake> UserID;
+ std::string JoinTimestamp;
+ int Flags;
+
+ friend void from_json(const nlohmann::json &j, ThreadMemberObject &m);
+};
+
struct ChannelData {
Snowflake ID;
ChannelType Type;
@@ -57,6 +78,8 @@ struct ChannelData {
std::optional<Snowflake> ApplicationID;
std::optional<Snowflake> ParentID; // null
std::optional<std::string> LastPinTimestamp; // null
+ std::optional<ThreadMetadataData> ThreadMetadata;
+ std::optional<ThreadMemberObject> ThreadMember;
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 a95e4fa..61c224a 100644
--- a/discord/discord.cpp
+++ b/discord/discord.cpp
@@ -1155,6 +1155,9 @@ void DiscordClient::HandleGatewayMessage(std::string str) {
case GatewayEvent::RELATIONSHIP_ADD: {
HandleGatewayRelationshipAdd(m);
} break;
+ case GatewayEvent::THREAD_CREATE: {
+ HandleGatewayThreadCreate(m);
+ } break;
}
} break;
default:
@@ -1209,7 +1212,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 +1221,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);
@@ -1373,7 +1384,7 @@ void DiscordClient::HandleGatewayChannelCreate(const GatewayMessage &msg) {
for (const auto &p : *data.PermissionOverwrites)
m_store.SetPermissionOverwrite(data.ID, p.ID, p);
m_store.EndTransaction();
- m_signal_channel_create.emit(data.ID);
+ m_signal_channel_create.emit(data);
}
void DiscordClient::HandleGatewayGuildUpdate(const GatewayMessage &msg) {
@@ -1627,6 +1638,14 @@ void DiscordClient::HandleGatewayRelationshipAdd(const GatewayMessage &msg) {
m_signal_relationship_add.emit(std::move(data));
}
+void DiscordClient::HandleGatewayThreadCreate(const GatewayMessage &msg) {
+ ThreadCreateData data = msg.Data;
+
+ m_store.SetChannel(data.Channel.ID, data.Channel);
+
+ m_signal_thread_create.emit(data.Channel);
+}
+
void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) {
ReadySupplementalData data = msg.Data;
for (const auto &p : data.MergedPresences.Friends) {
@@ -1989,6 +2008,7 @@ void DiscordClient::LoadEventMap() {
m_event_map["GUILD_JOIN_REQUEST_DELETE"] = GatewayEvent::GUILD_JOIN_REQUEST_DELETE;
m_event_map["RELATIONSHIP_REMOVE"] = GatewayEvent::RELATIONSHIP_REMOVE;
m_event_map["RELATIONSHIP_ADD"] = GatewayEvent::RELATIONSHIP_ADD;
+ m_event_map["THREAD_CREATE"] = GatewayEvent::THREAD_CREATE;
}
DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() {
@@ -2127,6 +2147,10 @@ DiscordClient::type_signal_message_pinned DiscordClient::signal_message_pinned()
return m_signal_message_pinned;
}
+DiscordClient::type_signal_thread_create DiscordClient::signal_thread_create() {
+ return m_signal_thread_create;
+}
+
DiscordClient::type_signal_message_sent DiscordClient::signal_message_sent() {
return m_signal_message_sent;
}
diff --git a/discord/discord.hpp b/discord/discord.hpp
index 918b1cb..93825af 100644
--- a/discord/discord.hpp
+++ b/discord/discord.hpp
@@ -236,6 +236,7 @@ private:
void HandleGatewayGuildJoinRequestDelete(const GatewayMessage &msg);
void HandleGatewayRelationshipRemove(const GatewayMessage &msg);
void HandleGatewayRelationshipAdd(const GatewayMessage &msg);
+ void HandleGatewayThreadCreate(const GatewayMessage &msg);
void HandleGatewayReadySupplemental(const GatewayMessage &msg);
void HandleGatewayReconnect(const GatewayMessage &msg);
void HandleGatewayInvalidSession(const GatewayMessage &msg);
@@ -308,7 +309,7 @@ public:
typedef sigc::signal<void, Snowflake> type_signal_guild_delete;
typedef sigc::signal<void, Snowflake> type_signal_channel_delete;
typedef sigc::signal<void, Snowflake> type_signal_channel_update;
- typedef sigc::signal<void, Snowflake> type_signal_channel_create;
+ typedef sigc::signal<void, ChannelData> type_signal_channel_create;
typedef sigc::signal<void, Snowflake> type_signal_guild_update;
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_role_update; // guild id, role id
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_role_create; // guild id, role id
@@ -329,6 +330,7 @@ public:
typedef sigc::signal<void, GuildJoinRequestDeleteData> type_signal_guild_join_request_delete;
typedef sigc::signal<void, Snowflake, RelationshipType> type_signal_relationship_remove;
typedef sigc::signal<void, RelationshipAddData> type_signal_relationship_add;
+ typedef sigc::signal<void, ChannelData> type_signal_thread_create;
typedef sigc::signal<void, Message> type_signal_message_unpinned; // not a real event
typedef sigc::signal<void, Message> type_signal_message_pinned; // not a real event either
typedef sigc::signal<void, Message> type_signal_message_sent;
@@ -368,6 +370,7 @@ public:
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_message_sent signal_message_sent();
type_signal_message_send_fail signal_message_send_fail();
type_signal_disconnected signal_disconnected();
@@ -406,6 +409,7 @@ protected:
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_message_sent m_signal_message_sent;
type_signal_message_send_fail m_signal_message_send_fail;
type_signal_disconnected m_signal_disconnected;
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/message.hpp b/discord/message.hpp
index a6ea039..56f4c0f 100644
--- a/discord/message.hpp
+++ b/discord/message.hpp
@@ -27,7 +27,7 @@ enum class MessageType {
GUILD_DISCOVERY_REQUALIFIED = 15, // yep
GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16, // yep
GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17, // yep
- THREAD_CREATED = 18, // nope
+ THREAD_CREATED = 18, // yep
INLINE_REPLY = 19, // yep
APPLICATION_COMMAND = 20, // yep
THREAD_STARTER_MESSAGE = 21, // nope
diff --git a/discord/objects.cpp b/discord/objects.cpp
index 8588b30..c4b1517 100644
--- a/discord/objects.cpp
+++ b/discord/objects.cpp
@@ -472,3 +472,7 @@ void to_json(nlohmann::json &j, const FriendRequestObject &m) {
void to_json(nlohmann::json &j, const PutRelationshipObject &m) {
JS_IF("type", m.Type);
}
+
+void from_json(const nlohmann::json &j, ThreadCreateData &m) {
+ j.get_to(m.Channel);
+}
diff --git a/discord/objects.hpp b/discord/objects.hpp
index 11c8df4..7c249cd 100644
--- a/discord/objects.hpp
+++ b/discord/objects.hpp
@@ -71,6 +71,12 @@ enum class GatewayEvent : int {
GUILD_JOIN_REQUEST_DELETE,
RELATIONSHIP_REMOVE,
RELATIONSHIP_ADD,
+ THREAD_CREATE,
+ THREAD_UPDATE,
+ THREAD_DELETE,
+ THREAD_LIST_SYNC,
+ THREAD_MEMBER_UPDATE,
+ THREAD_MEMBERS_UPDATE,
};
enum class GatewayCloseCode : uint16_t {
@@ -659,3 +665,9 @@ struct PutRelationshipObject {
friend void to_json(nlohmann::json &j, const PutRelationshipObject &m);
};
+
+struct ThreadCreateData {
+ ChannelData Channel;
+
+ friend void from_json(const nlohmann::json &j, ThreadCreateData &m);
+};
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 (
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
)";