better member list, role color, some fixes

This commit is contained in:
ouwou 2020-09-05 23:04:11 -04:00
parent de482d6cb7
commit af82a8df8e
10 changed files with 264 additions and 21 deletions

View File

@ -6,6 +6,7 @@
ChatMessageContainer::ChatMessageContainer(const MessageData *data) {
UserID = data->Author.ID;
ChannelID = data->ChannelID;
m_main_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
m_content_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
@ -49,6 +50,27 @@ ChatMessageContainer::ChatMessageContainer(const MessageData *data) {
show();
}
void ChatMessageContainer::SetAbaddon(Abaddon *ptr) {
m_abaddon = ptr;
}
void ChatMessageContainer::Update() {
if (m_abaddon == nullptr) return;
auto &discord = m_abaddon->GetDiscordClient();
auto guild_id = discord.GetChannel(ChannelID)->GuildID;
auto role_id = discord.GetMemberHoistedRole(guild_id, UserID, true);
auto *user = discord.GetUser(UserID);
std::string md;
if (role_id.IsValid()) {
auto *role = discord.GetRole(role_id);
if (role != nullptr)
md = "<span weight='bold' color='#" + IntToCSSColor(role->Color) + "'>" + Glib::Markup::escape_text(user->Username) + "</span>";
} else {
md = "<span weight='bold'>" + Glib::Markup::escape_text(user->Username) + "</span>";
}
m_author->set_markup(md);
}
void ChatMessageContainer::AddNewContent(Gtk::Widget *widget, bool prepend) {
if (prepend)
m_content_box->pack_end(*widget);

View File

@ -1,5 +1,6 @@
#pragma once
#include <gtkmm.h>
#include <string>
#include "../discord/discord.hpp"
enum class ChatDisplayType {
@ -14,11 +15,16 @@ class Abaddon;
class ChatMessageContainer : public Gtk::ListBoxRow {
public:
Snowflake UserID;
Snowflake ChannelID;
ChatMessageContainer(const MessageData *data);
void SetAbaddon(Abaddon *ptr);
void AddNewContent(Gtk::Widget *widget, bool prepend = false);
void Update();
protected:
Abaddon *m_abaddon = nullptr;
Gtk::Box *m_main_box;
Gtk::Box *m_content_box;
Gtk::Box *m_meta_box;

View File

@ -112,6 +112,8 @@ void ChatWindow::ProcessMessage(const MessageData *data, bool prepend) {
container = last_row;
} else {
container = Gtk::manage(new ChatMessageContainer(data)); // only accesses timestamp and user
container->SetAbaddon(m_abaddon);
container->Update();
m_num_rows++;
}

View File

@ -1,5 +1,6 @@
#include "memberlist.hpp"
#include "../abaddon.hpp"
#include "../util.hpp"
MemberList::MemberList() {
m_update_member_list_dispatcher.connect(sigc::mem_fun(*this, &MemberList::UpdateMemberListInternal));
@ -7,6 +8,8 @@ MemberList::MemberList() {
m_main = Gtk::manage(new Gtk::ScrolledWindow);
m_listbox = Gtk::manage(new Gtk::ListBox);
m_listbox->set_selection_mode(Gtk::SELECTION_NONE);
m_main->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
m_main->add(*m_listbox);
m_main->show_all();
@ -24,6 +27,7 @@ void MemberList::SetActiveChannel(Snowflake id) {
void MemberList::UpdateMemberList() {
std::scoped_lock<std::mutex> guard(m_mutex);
printf("update member list\n");
m_update_member_list_dispatcher.emit();
}
@ -47,20 +51,95 @@ void MemberList::UpdateMemberListInternal() {
ids = discord.GetUsersInGuild(m_guild_id);
}
// process all the shit first so its in proper order
std::map<int, const RoleData *> pos_to_role;
std::map<int, std::vector<const UserData *>> pos_to_users;
std::unordered_map<Snowflake, int> user_to_color;
std::vector<Snowflake> roleless_users;
for (const auto &id : ids) {
auto *user = discord.GetUser(id);
auto *row = Gtk::manage(new Gtk::ListBoxRow);
auto *label = Gtk::manage(new Gtk::Label);
label->set_single_line_mode(true);
label->set_ellipsize(Pango::ELLIPSIZE_END);
if (user == nullptr)
label->set_text("[unknown user]");
else
label->set_text(user->Username + "#" + user->Discriminator);
label->set_halign(Gtk::ALIGN_START);
row->add(*label);
row->show_all();
m_listbox->add(*row);
if (user == nullptr) {
roleless_users.push_back(id);
continue;
}
auto pos_role_id = discord.GetMemberHoistedRole(m_guild_id, id); // role for positioning
auto col_role_id = discord.GetMemberHoistedRole(m_guild_id, id, true); // role for color
auto pos_role = discord.GetRole(pos_role_id);
auto col_role = discord.GetRole(col_role_id);
if (pos_role == nullptr) {
roleless_users.push_back(id);
continue;
};
pos_to_role[pos_role->Position] = pos_role;
pos_to_users[pos_role->Position].push_back(user);
if (col_role != nullptr) {
if (ColorDistance(col_role->Color, 0xFFFFFF) < 15)
user_to_color[id] = 0x000000;
else
user_to_color[id] = col_role->Color;
}
}
auto add_user = [this, &user_to_color](const UserData *data) {
auto *user_row = Gtk::manage(new Gtk::ListBoxRow);
auto *user_lbl = Gtk::manage(new Gtk::Label);
user_lbl->set_single_line_mode(true);
user_lbl->set_ellipsize(Pango::ELLIPSIZE_END);
if (data != nullptr) {
std::string display = data->Username + "#" + data->Discriminator;
if (user_to_color.find(data->ID) != user_to_color.end()) {
auto color = user_to_color.at(data->ID);
user_lbl->set_use_markup(true);
user_lbl->set_markup("<span color='#" + IntToCSSColor(color) + "'>" + Glib::Markup::escape_text(display) + "</span>");
} else {
user_lbl->set_text(display);
}
} else {
user_lbl->set_use_markup(true);
user_lbl->set_markup("<i>[unknown user]</i>");
}
user_lbl->set_halign(Gtk::ALIGN_START);
user_row->add(*user_lbl);
user_row->show_all();
m_listbox->add(*user_row);
};
auto add_role = [this](std::string name) {
auto *role_row = Gtk::manage(new Gtk::ListBoxRow);
auto *role_lbl = Gtk::manage(new Gtk::Label);
role_lbl->set_single_line_mode(true);
role_lbl->set_ellipsize(Pango::ELLIPSIZE_END);
role_lbl->set_use_markup(true);
role_lbl->set_markup("<b>" + Glib::Markup::escape_text(name) + "</b>");
role_lbl->set_halign(Gtk::ALIGN_START);
role_row->add(*role_lbl);
role_row->show_all();
m_listbox->add(*role_row);
};
for (auto it = pos_to_role.crbegin(); it != pos_to_role.crend(); it++) {
auto pos = it->first;
auto role = it->second;
add_role(role->Name);
if (pos_to_users.find(pos) == pos_to_users.end()) continue;
auto &users = pos_to_users.at(pos);
AlphabeticalSort(users.begin(), users.end(), [](auto e) { return e->Username; });
for (const auto data : users)
add_user(data);
}
add_role("@everyone");
for (const auto &id : roleless_users) {
add_user(discord.GetUser(id));
}
}

View File

@ -175,6 +175,35 @@ const UserData *DiscordClient::GetUser(Snowflake id) const {
return nullptr;
}
const RoleData *DiscordClient::GetRole(Snowflake id) const {
if (m_roles.find(id) != m_roles.end())
return &m_roles.at(id);
return nullptr;
}
Snowflake DiscordClient::GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color) const {
auto *data = GetGuildMemberData(user_id, guild_id);
if (data == nullptr) return Snowflake::Invalid;
std::vector<const RoleData *> roles;
for (const auto &id : data->Roles) {
auto *role = GetRole(id);
if (role != nullptr) {
if ((!with_color && role->IsHoisted) || role->Color != 0)
roles.push_back(role);
}
}
if (roles.size() == 0) return Snowflake::Invalid;
std::sort(roles.begin(), roles.end(), [this](const RoleData *a, const RoleData *b) -> bool {
return a->Position > b->Position;
});
return roles[0]->ID;
}
std::unordered_set<Snowflake> DiscordClient::GetUsersInGuild(Snowflake id) const {
auto it = m_guild_to_users.find(id);
if (it != m_guild_to_users.end())
@ -327,6 +356,9 @@ void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) {
c.GuildID = g.ID;
StoreChannel(c.ID, c);
}
for (auto &r : g.Roles)
StoreRole(r);
}
}
@ -378,9 +410,10 @@ void DiscordClient::HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg
auto known = GetUser(member->User.ID);
if (known == nullptr) {
StoreUser(member->User);
AddUserToGuild(member->User.ID, data.GuildID);
known = GetUser(member->User.ID);
}
AddUserToGuild(member->User.ID, data.GuildID);
AddGuildMemberData(data.GuildID, member->User.ID, member->GetAsMemberData());
}
}
}
@ -407,10 +440,21 @@ void DiscordClient::StoreChannel(Snowflake id, const ChannelData &c) {
m_channels[id] = c;
}
void DiscordClient::AddUserToGuild(Snowflake user_id, Snowflake guild_id) {
if (m_guild_to_users.find(guild_id) == m_guild_to_users.end())
m_guild_to_users[guild_id] = std::unordered_set<Snowflake>();
void DiscordClient::AddGuildMemberData(Snowflake guild_id, Snowflake user_id, const GuildMemberData &data) {
m_members[guild_id][user_id] = data;
}
const GuildMemberData *DiscordClient::GetGuildMemberData(Snowflake user_id, Snowflake guild_id) const {
if (m_members.find(guild_id) == m_members.end())
return nullptr;
if (m_members.at(guild_id).find(user_id) == m_members.at(guild_id).end())
return nullptr;
return &m_members.at(guild_id).at(user_id);
}
void DiscordClient::AddUserToGuild(Snowflake user_id, Snowflake guild_id) {
m_guild_to_users[guild_id].insert(user_id);
}
@ -418,6 +462,10 @@ void DiscordClient::StoreUser(const UserData &u) {
m_users[u.ID] = u;
}
void DiscordClient::StoreRole(const RoleData &r) {
m_roles[r.ID] = r;
}
std::set<Snowflake> DiscordClient::GetPrivateChannels() const {
auto ret = std::set<Snowflake>();
@ -458,7 +506,7 @@ void DiscordClient::SendIdentify() {
}
bool DiscordClient::CheckCode(const cpr::Response &r) {
if (r.status_code >= 300) {
if (r.status_code >= 300 || r.error) {
fprintf(stderr, "api request to %s failed with status code %d\n", r.url.c_str(), r.status_code);
return false;
}

View File

@ -56,6 +56,7 @@ public:
using Guilds_t = std::unordered_map<Snowflake, GuildData>;
using Messages_t = std::unordered_map<Snowflake, MessageData>;
using Users_t = std::unordered_map<Snowflake, UserData>;
using Roles_t = std::unordered_map<Snowflake, RoleData>;
const Guilds_t &GetGuilds() const;
const UserData &GetUserData() const;
@ -70,6 +71,8 @@ public:
const MessageData *GetMessage(Snowflake id) const;
const ChannelData *GetChannel(Snowflake id) const;
const UserData *GetUser(Snowflake id) const;
const RoleData *GetRole(Snowflake id) const;
Snowflake GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color = false) const;
std::unordered_set<Snowflake> GetUsersInGuild(Snowflake id) const;
void SendChatMessage(std::string content, Snowflake channel);
@ -113,10 +116,16 @@ private:
void StoreChannel(Snowflake id, const ChannelData &c);
Channels_t m_channels;
void AddGuildMemberData(Snowflake guild_id, Snowflake user_id, const GuildMemberData &data);
const GuildMemberData *GetGuildMemberData(Snowflake user_id, Snowflake guild_id) const;
void AddUserToGuild(Snowflake user_id, Snowflake guild_id);
void StoreUser(const UserData &u);
Users_t m_users;
std::unordered_map<Snowflake, std::unordered_set<Snowflake>> m_guild_to_users;
std::unordered_map<Snowflake, std::unordered_map<Snowflake, GuildMemberData>> m_members;
void StoreRole(const RoleData &r);
Roles_t m_roles;
UserData m_user_data;
UserSettingsData m_user_settings;

View File

@ -32,6 +32,20 @@ void from_json(const nlohmann::json &j, HelloMessageData &m) {
JS_D("heartbeat_interval", m.HeartbeatInterval);
}
void from_json(const nlohmann::json &j, RoleData &m) {
JS_D("id", m.ID);
JS_D("name", m.Name);
JS_D("color", m.Color);
JS_D("hoist", m.IsHoisted);
JS_D("position", m.Position);
JS_D("permissions", m.PermissionsLegacy);
std::string tmp;
JS_D("permissions_new", tmp);
m.Permissions = std::stoull(tmp);
JS_D("managed", m.IsManaged);
JS_D("mentionable", m.IsMentionable);
}
void from_json(const nlohmann::json &j, UserData &m) {
JS_D("id", m.ID);
JS_D("username", m.Username);
@ -52,6 +66,16 @@ void from_json(const nlohmann::json &j, UserData &m) {
JS_ON("phone", m.Phone);
}
void from_json(const nlohmann::json &j, GuildMemberData &m) {
JS_O("user", m.User);
JS_ON("nick", m.Nickname);
JS_D("roles", m.Roles);
JS_D("joined_at", m.JoinedAt);
JS_ON("premium_since", m.PremiumSince);
JS_D("deaf", m.IsDeafened);
JS_D("mute", m.IsMuted);
}
void from_json(const nlohmann::json &j, GuildData &m) {
JS_D("id", m.ID);
if (j.contains("unavailable")) {
@ -75,7 +99,7 @@ void from_json(const nlohmann::json &j, GuildData &m) {
JS_D("verification_level", m.VerificationLevel);
JS_D("default_message_notifications", m.DefaultMessageNotifications);
JS_D("explicit_content_filter", m.ExplicitContentFilter);
// JS_D("roles", m.Roles);
JS_D("roles", m.Roles);
// JS_D("emojis", m.Emojis);
JS_D("features", m.Features);
JS_D("mfa_level", m.MFALevel);
@ -186,6 +210,10 @@ void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage::GroupItem
JS_D("count", m.Count);
}
GuildMemberData GuildMemberListUpdateMessage::MemberItem::GetAsMemberData() const {
return m_member_data;
}
void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage::MemberItem &m) {
m.Type = "member";
JS_D("user", m.User);
@ -196,6 +224,7 @@ void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage::MemberItem
JS_N("hoisted_role", m.HoistedRole);
JS_ON("premium_since", m.PremiumSince);
JS_ON("nick", m.Nickname);
m.m_member_data = j;
}
void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage::OpObject &m) {

View File

@ -92,6 +92,20 @@ enum class ChannelType : int {
GUILD_STORE = 6,
};
struct RoleData {
Snowflake ID;
std::string Name;
int Color;
bool IsHoisted;
int Position;
int PermissionsLegacy;
uint64_t Permissions;
bool IsManaged;
bool IsMentionable;
friend void from_json(const nlohmann::json &j, RoleData &m);
};
struct UserData {
Snowflake ID; //
std::string Username; //
@ -116,6 +130,18 @@ struct UserData {
friend void from_json(const nlohmann::json &j, UserData &m);
};
struct GuildMemberData {
UserData User; // opt
std::string Nickname; // null
std::vector<Snowflake> Roles; //
std::string JoinedAt; //
std::string PremiumSince; // opt, null
bool IsDeafened; //
bool IsMuted; //
friend void from_json(const nlohmann::json &j, GuildMemberData &m);
};
struct ChannelData {
Snowflake ID; //
ChannelType Type; //
@ -159,7 +185,7 @@ struct GuildData {
int VerificationLevel; //
int DefaultMessageNotifications; //
int ExplicitContentFilter; //
// std::vector<RoleData> Roles; //
std::vector<RoleData> Roles; //
// std::vector<EmojiData> Emojis; //
std::vector<std::string> Features; //
int MFALevel; //
@ -396,7 +422,12 @@ struct GuildMemberListUpdateMessage {
std::string HoistedRole; // null
bool IsDefeaned; //
GuildMemberData GetAsMemberData() const;
friend void from_json(const nlohmann::json &j, MemberItem &m);
private:
GuildMemberData m_member_data;
};
struct OpObject {

View File

@ -43,3 +43,19 @@ inline std::string IntToCSSColor(int color) {
<< std::hex << std::setw(2) << std::setfill('0') << b;
return ss.str();
}
// https://www.compuphase.com/cmetric.htm
inline double ColorDistance(int c1, int c2) {
int r1 = (c1 & 0xFF0000) >> 16;
int g1 = (c1 & 0x00FF00) >> 8;
int b1 = (c1 & 0x0000FF) >> 0;
int r2 = (c2 & 0xFF0000) >> 16;
int g2 = (c2 & 0x00FF00) >> 8;
int b2 = (c2 & 0x0000FF) >> 0;
int rmean = (r1 - r2) / 2;
int r = r1 - r2;
int g = g1 - g2;
int b = b1 - b2;
return sqrt((((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8));
}

View File

@ -125,9 +125,10 @@ Snowflake MainWindow::GetChatActiveChannel() const {
}
void MainWindow::UpdateChatNewMessage(Snowflake id) {
if (m_abaddon->GetDiscordClient().GetMessage(id)->ChannelID == GetChatActiveChannel())
if (m_abaddon->GetDiscordClient().GetMessage(id)->ChannelID == GetChatActiveChannel()) {
m_chat.AddNewMessage(id);
m_members.UpdateMemberList();
m_members.UpdateMemberList();
}
}
void MainWindow::UpdateChatMessageDeleted(Snowflake id, Snowflake channel_id) {