This commit is contained in:
ouwou 2021-06-04 02:39:35 -04:00
commit 43ea62d444
29 changed files with 297 additions and 132 deletions

View File

@ -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]

View File

@ -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

View File

@ -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> &section, 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> &section, 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);

View File

@ -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
};

@ -1 +1 @@
Subproject commit 50ea8c0ab7aca3bb9245bba7fc877ad2f2a4464c
Subproject commit a9b27ed5dffbf70b135eddb0c4729f3ca87f106c

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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);
}

View File

@ -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%);
}

View File

@ -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);
});

View File

@ -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) {

View File

@ -49,10 +49,6 @@ class Abaddon;
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();
@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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 "";
}

View File

@ -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 (
?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?
)
)";

View File

@ -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) {

View File

@ -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;

View File

@ -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) {

View File

@ -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();

View File

@ -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");
}

View File

@ -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;

View File

@ -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 });

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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())