11 Commits

Author SHA1 Message Date
ouwou
4bd5c89266 fix and refactor multiple embeds in one message 2022-02-20 01:20:19 -05:00
ouwou
a0599ab812 parse role mentions 2022-02-20 01:19:18 -05:00
ouwou
6c54296ba3 change windows environment to windows-2019 2022-02-18 01:15:45 -05:00
ouwou
7ed415040a delete database instead of trying to clear it 2022-02-17 02:14:19 -05:00
ouwou
011cb159cf Merge branch 'master' of https://github.com/uowuo/abaddon 2022-02-14 02:53:31 -05:00
ouwou
25fd2c3840 fix per-guild avatars 2022-02-14 02:53:21 -05:00
social reject
7e3976785f Update README.md (#57)
include command to clone necessary submodules
2022-02-14 04:58:51 +00:00
ouwou
75213fcede handle multiple embeds in one message 2022-02-02 22:46:55 -05:00
ouwou
179ff980e9 fix ready parsing (#54) 2022-02-02 22:34:54 -05:00
ouwou
f784550964 support channel icons for dms 2022-02-02 22:27:19 -05:00
ouwou
ce238d08e9 add style option for unread indicator color 2022-01-28 14:46:33 -05:00
16 changed files with 154 additions and 48 deletions

View File

@@ -5,7 +5,7 @@ on: [push, pull_request]
jobs:
windows:
name: windows-${{ matrix.buildtype }}
runs-on: windows-latest
runs-on: windows-2019
strategy:
matrix:
buildtype: [Debug, RelWithDebInfo, MinSizeRel]

View File

@@ -43,9 +43,10 @@ Or, do steps 1 and 2, and open CMakeLists.txt in Visual Studio if `vcpkg integra
#### Mac:
1. `git clone https://github.com/uowuo/abaddon && cd abaddon`
2. `brew install gtkmm3 nlohmann-json`
3. `mkdir build && cd build`
4. `cmake ..`
5. `make`
3. `git submodule update --init subprojects`
4. `mkdir build && cd build`
5. `cmake ..`
6. `make`
#### Linux:
1. Install dependencies: `libgtkmm-3.0-dev`, `libcurl4-gnutls-dev`, and [nlohmann-json](https://github.com/nlohmann/json)
@@ -213,6 +214,7 @@ For example, memory_db would be set by adding `memory_db = true` under the line
* channelcolor (string) - color to use for SFW channels in the channel list
* mentionbadgecolor (string) - background color for mention badges
* mentionbadgetextcolor (string) - color to use for number displayed on mention badges
* unreadcolor (string) - color to use for the unread indicator
### Environment variables

View File

@@ -752,7 +752,13 @@ void ChannelList::AddPrivateChannels() {
else if (dm->Type == ChannelType::GROUP_DM)
row[m_columns.m_name] = std::to_string(recipients.size()) + " members";
if (top_recipient.has_value()) {
if (dm->HasIcon()) {
const auto cb = [this, iter](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
if (iter)
(*iter)[m_columns.m_icon] = pb->scale_simple(DMIconSize, DMIconSize, Gdk::INTERP_BILINEAR);
};
img.LoadFromURL(dm->GetIconURL(), sigc::track_obj(cb, *this));
} else if (top_recipient.has_value()) {
const auto cb = [this, iter](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
if (iter)
(*iter)[m_columns.m_icon] = pb->scale_simple(DMIconSize, DMIconSize, Gdk::INTERP_BILINEAR);

View File

@@ -258,7 +258,8 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtr<Cairo::Context
const auto has_unread = discord.GetUnreadStateForGuild(id, total_mentions);
if (has_unread && !discord.IsGuildMuted(id)) {
cr->set_source_rgb(1.0, 1.0, 1.0);
static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().UnreadIndicatorColor);
cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue());
const auto x = background_area.get_x();
const auto y = background_area.get_y();
const auto w = background_area.get_width();
@@ -403,7 +404,8 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtr<Cairo::Conte
if (unread_state < 0) return;
if (!is_muted) {
cr->set_source_rgb(1.0, 1.0, 1.0);
static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().UnreadIndicatorColor);
cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue());
const auto x = background_area.get_x();
const auto y = background_area.get_y();
const auto w = background_area.get_width();
@@ -474,7 +476,8 @@ void CellRendererChannels::render_vfunc_thread(const Cairo::RefPtr<Cairo::Contex
if (unread_state < 0) return;
if (!is_muted) {
cr->set_source_rgb(1.0, 1.0, 1.0);
static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().UnreadIndicatorColor);
cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue());
const auto x = background_area.get_x();
const auto y = background_area.get_y();
const auto w = background_area.get_width();
@@ -614,7 +617,8 @@ void CellRendererChannels::render_vfunc_dm(const Cairo::RefPtr<Cairo::Context> &
if (unread_state < 0) return;
if (!is_muted) {
cr->set_source_rgb(1.0, 1.0, 1.0);
static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().UnreadIndicatorColor);
cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue());
const auto x = background_area.get_x();
const auto y = background_area.get_y();
const auto w = background_area.get_width();

View File

@@ -1,7 +1,7 @@
#include "chatmessage.hpp"
#include "abaddon.hpp"
#include "util.hpp"
#include "chatmessage.hpp"
#include "lazyimage.hpp"
#include "util.hpp"
#include <unordered_map>
constexpr static int EmojiSize = 24; // settings eventually
@@ -44,18 +44,9 @@ ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(const Message &d
}
}
// there should only ever be 1 embed (i think?)
if (data.Embeds.size() == 1) {
const auto &embed = data.Embeds[0];
if (IsEmbedImageOnly(embed)) {
auto *widget = container->CreateImageComponent(*embed.Thumbnail->ProxyURL, *embed.Thumbnail->URL, *embed.Thumbnail->Width, *embed.Thumbnail->Height);
container->AttachEventHandlers(*widget);
container->m_main.add(*widget);
} else {
container->m_embed_component = container->CreateEmbedComponent(embed);
container->AttachEventHandlers(*container->m_embed_component);
container->m_main.add(*container->m_embed_component);
}
if (!data.Embeds.empty()) {
container->m_embed_component = container->CreateEmbedsComponent(data.Embeds);
container->m_main.add(*container->m_embed_component);
}
// i dont think attachments can be edited
@@ -108,10 +99,11 @@ void ChatMessageItemContainer::UpdateContent() {
m_embed_component = nullptr;
}
if (data->Embeds.size() == 1) {
m_embed_component = CreateEmbedComponent(data->Embeds[0]);
if (!data->Embeds.empty()) {
m_embed_component = CreateEmbedsComponent(data->Embeds);
AttachEventHandlers(*m_embed_component);
m_main.add(*m_embed_component);
m_embed_component->show_all();
}
}
@@ -199,6 +191,7 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) {
case MessageType::DEFAULT:
case MessageType::INLINE_REPLY:
b->insert(s, data->Content);
HandleRoleMentions(b);
HandleUserMentions(b);
HandleLinks(*tv);
HandleChannelMentions(tv);
@@ -298,6 +291,24 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) {
}
}
Gtk::Widget *ChatMessageItemContainer::CreateEmbedsComponent(const std::vector<EmbedData> &embeds) {
auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
for (const auto &embed : embeds) {
if (IsEmbedImageOnly(embed)) {
auto *widget = CreateImageComponent(*embed.Thumbnail->ProxyURL, *embed.Thumbnail->URL, *embed.Thumbnail->Width, *embed.Thumbnail->Height);
widget->show();
AttachEventHandlers(*widget);
box->add(*widget);
} else {
auto *widget = CreateEmbedComponent(embed);
widget->show();
AttachEventHandlers(*widget);
box->add(*widget);
}
}
return box;
}
Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const EmbedData &embed) {
Gtk::EventBox *ev = Gtk::manage(new Gtk::EventBox);
ev->set_can_focus(true);
@@ -732,7 +743,47 @@ bool ChatMessageItemContainer::IsEmbedImageOnly(const EmbedData &data) {
return data.Thumbnail->ProxyURL.has_value() && data.Thumbnail->URL.has_value() && data.Thumbnail->Width.has_value() && data.Thumbnail->Height.has_value();
}
void ChatMessageItemContainer::HandleUserMentions(Glib::RefPtr<Gtk::TextBuffer> buf) {
void ChatMessageItemContainer::HandleRoleMentions(const Glib::RefPtr<Gtk::TextBuffer> &buf) {
constexpr static const auto mentions_regex = R"(<@&(\d+)>)";
static auto rgx = Glib::Regex::create(mentions_regex);
Glib::ustring text = GetText(buf);
const auto &discord = Abaddon::Get().GetDiscordClient();
int startpos = 0;
Glib::MatchInfo match;
while (rgx->match(text, startpos, match)) {
int mstart, mend;
if (!match.fetch_pos(0, mstart, mend)) break;
const Glib::ustring role_id = match.fetch(1);
const auto role = discord.GetRole(role_id);
if (!role.has_value()) {
startpos = mend;
continue;
}
Glib::ustring replacement;
if (role->HasColor()) {
replacement = "<b><span color=\"#" + IntToCSSColor(role->Color) + "\">@" + role->GetEscapedName() + "</span></b>";
} else {
replacement = "<b>@" + role->GetEscapedName() + "</b>";
}
const auto chars_start = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mstart);
const auto chars_end = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mend);
const auto start_it = buf->get_iter_at_offset(chars_start);
const auto end_it = buf->get_iter_at_offset(chars_end);
auto it = buf->erase(start_it, end_it);
buf->insert_markup(it, replacement);
text = GetText(buf);
startpos = 0;
}
}
void ChatMessageItemContainer::HandleUserMentions(const Glib::RefPtr<Gtk::TextBuffer> &buf) {
constexpr static const auto mentions_regex = R"(<@!?(\d+)>)";
static auto rgx = Glib::Regex::create(mentions_regex);
@@ -1064,11 +1115,11 @@ ChatMessageHeader::ChatMessageHeader(const Message &data)
};
img.LoadFromURL(author->GetAvatarURL(data.GuildID), sigc::track_obj(cb, *this));
if (author->HasAnimatedAvatar()) {
if (author->HasAnimatedAvatar(data.GuildID)) {
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
m_anim_avatar = pb;
};
img.LoadAnimationFromURL(author->GetAvatarURL("gif"), AvatarSize, AvatarSize, sigc::track_obj(cb, *this));
img.LoadAnimationFromURL(author->GetAvatarURL(data.GuildID, "gif"), AvatarSize, AvatarSize, sigc::track_obj(cb, *this));
}
get_style_context()->add_class("message-container");

View File

@@ -22,6 +22,7 @@ protected:
void AddClickHandler(Gtk::Widget *widget, std::string);
Gtk::TextView *CreateTextComponent(const Message &data); // Message.Content
void UpdateTextComponent(Gtk::TextView *tv);
Gtk::Widget *CreateEmbedsComponent(const std::vector<EmbedData> &embeds);
Gtk::Widget *CreateEmbedComponent(const EmbedData &data); // Message.Embeds[0]
Gtk::Widget *CreateImageComponent(const std::string &proxy_url, const std::string &url, int inw, int inh);
Gtk::Widget *CreateAttachmentComponent(const AttachmentData &data); // non-image attachments
@@ -34,7 +35,8 @@ protected:
static bool IsEmbedImageOnly(const EmbedData &data);
void HandleUserMentions(Glib::RefPtr<Gtk::TextBuffer> buf);
void HandleRoleMentions(const Glib::RefPtr<Gtk::TextBuffer> &buf);
void HandleUserMentions(const Glib::RefPtr<Gtk::TextBuffer> &buf);
void HandleStockEmojis(Gtk::TextView &tv);
void HandleCustomEmojis(Gtk::TextView &tv);
void HandleEmojis(Gtk::TextView &tv);

View File

@@ -84,6 +84,14 @@ bool ChannelData::IsCategory() const noexcept {
return Type == ChannelType::GUILD_CATEGORY;
}
bool ChannelData::HasIcon() const noexcept {
return Icon.has_value();
}
std::string ChannelData::GetIconURL() const {
return "https://cdn.discordapp.com/channel-icons/" + std::to_string(ID) + "/" + *Icon + ".png";
}
std::vector<Snowflake> ChannelData::GetChildIDs() const {
return Abaddon::Get().GetDiscordClient().GetChildChannelIDs(ID);
}

View File

@@ -99,6 +99,8 @@ struct ChannelData {
bool IsThread() const noexcept;
bool IsJoinedThread() const;
bool IsCategory() const noexcept;
bool HasIcon() const noexcept;
std::string GetIconURL() const;
std::vector<Snowflake> GetChildIDs() const;
std::optional<PermissionOverwrite> GetOverwrite(Snowflake id) const;
std::vector<UserData> GetDMRecipients() const;

View File

@@ -154,7 +154,7 @@ void to_json(nlohmann::json &j, const UserGuildSettingsChannelOverride &m) {
void from_json(const nlohmann::json &j, MuteConfigData &m) {
JS_ON("end_time", m.EndTime);
JS_D("selected_time_window", m.SelectedTimeWindow);
JS_ON("selected_time_window", m.SelectedTimeWindow);
}
void to_json(nlohmann::json &j, const MuteConfigData &m) {

View File

@@ -12,3 +12,11 @@ void from_json(const nlohmann::json &j, RoleData &m) {
JS_D("managed", m.IsManaged);
JS_D("mentionable", m.IsMentionable);
}
bool RoleData::HasColor() const noexcept {
return Color != 0;
}
Glib::ustring RoleData::GetEscapedName() const {
return Glib::Markup::escape_text(Name);
}

View File

@@ -16,5 +16,8 @@ struct RoleData {
bool IsManaged;
bool IsMentionable;
bool HasColor() const noexcept;
Glib::ustring GetEscapedName() const;
friend void from_json(const nlohmann::json &j, RoleData &m);
};

View File

@@ -13,18 +13,6 @@ Store::Store(bool mem_store)
return;
}
m_db.Execute(R"(
PRAGMA writable_schema = 1;
DELETE FROM sqlite_master WHERE TYPE IN ("view", "table", "index", "trigger");
PRAGMA writable_schema = 0;
VACUUM;
PRAGMA integrity_check;
)");
if (!m_db.OK()) {
fprintf(stderr, "failed to clear database: %s\n", m_db.ErrStr());
return;
}
if (m_db.Execute("PRAGMA journal_mode = WAL") != SQLITE_OK) {
fprintf(stderr, "enabling write-ahead-log failed: %s\n", m_db.ErrStr());
return;
@@ -646,6 +634,7 @@ std::optional<ChannelData> Store::GetChannel(Snowflake id) const {
s->Get(6, r.IsNSFW);
s->Get(7, r.LastMessageID);
s->Get(10, r.RateLimitPerUser);
s->Get(11, r.Icon);
s->Get(12, r.OwnerID);
s->Get(14, r.ParentID);
if (!s->IsNull(16)) {
@@ -2149,6 +2138,13 @@ bool Store::CreateStatements() {
}
Store::Database::Database(const char *path) {
if (path != ":memory:"s) {
std::error_code ec;
if (std::filesystem::exists(path, ec) && !std::filesystem::remove(path, ec)) {
fprintf(stderr, "the database could not be removed. the database may be corrupted as a result\n");
}
}
m_err = sqlite3_open(path, &m_db);
}

View File

@@ -6,22 +6,41 @@ bool UserData::IsDeleted() const {
}
bool UserData::HasAvatar() const {
return Avatar.size() > 0;
return !Avatar.empty();
}
bool UserData::HasAnimatedAvatar() const {
return Avatar.size() > 0 && Avatar[0] == 'a' && Avatar[1] == '_';
bool UserData::HasAnimatedAvatar() const noexcept {
return !Avatar.empty() && Avatar[0] == 'a' && Avatar[1] == '_';
}
bool UserData::HasAnimatedAvatar(Snowflake guild_id) const {
const auto member = Abaddon::Get().GetDiscordClient().GetMember(ID, guild_id);
if (member.has_value() && member->Avatar.has_value() && member->Avatar.value()[0] == 'a' && member->Avatar.value()[1] == '_')
return true;
else if (!member->Avatar.has_value())
return HasAnimatedAvatar();
return false;
}
bool UserData::HasAnimatedAvatar(const std::optional<Snowflake> &guild_id) const {
if (guild_id.has_value())
return HasAnimatedAvatar(*guild_id);
else
return HasAnimatedAvatar();
}
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())
if (member.has_value() && member->Avatar.has_value()) {
if (ext == "gif" && !(member->Avatar.value()[0] == 'a' && member->Avatar.value()[1] == '_'))
return GetAvatarURL(ext, size);
return "https://cdn.discordapp.com/guilds/" +
std::to_string(guild_id) + "/users/" + std::to_string(ID) +
"/avatars/" + *member->Avatar + "." +
ext + "?" + "size=" + size;
else
} else {
return GetAvatarURL(ext, size);
}
}
std::string UserData::GetAvatarURL(const std::optional<Snowflake> &guild_id, std::string ext, std::string size) const {

View File

@@ -62,7 +62,9 @@ struct UserData {
bool IsDeleted() const;
bool HasAvatar() const;
bool HasAnimatedAvatar() const;
bool HasAnimatedAvatar() const noexcept;
bool HasAnimatedAvatar(Snowflake guild_id) const;
bool HasAnimatedAvatar(const std::optional<Snowflake> &guild_id) 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;

View File

@@ -56,6 +56,7 @@ void SettingsManager::ReadSettings() {
SMSTR("style", "channelcolor", ChannelColor);
SMSTR("style", "mentionbadgecolor", MentionBadgeColor);
SMSTR("style", "mentionbadgetextcolor", MentionBadgeTextColor);
SMSTR("style", "unreadcolor", UnreadIndicatorColor);
#undef SMBOOL
#undef SMSTR
@@ -108,6 +109,7 @@ void SettingsManager::Close() {
SMSTR("style", "channelcolor", ChannelColor);
SMSTR("style", "mentionbadgecolor", MentionBadgeColor);
SMSTR("style", "mentionbadgetextcolor", MentionBadgeTextColor);
SMSTR("style", "unreadcolor", UnreadIndicatorColor);
#undef SMSTR
#undef SMBOOL

View File

@@ -40,6 +40,7 @@ public:
std::string ChannelColor { "#fbfbfb" };
std::string MentionBadgeColor { "#b82525" };
std::string MentionBadgeTextColor { "#fbfbfb" };
std::string UnreadIndicatorColor { "#ffffff" };
};
SettingsManager(const std::string &filename);