view user profile (notes+connections+badges)
change some stuff with callbacks
@@ -44,6 +44,8 @@ file(GLOB ABADDON_SOURCES
|
|||||||
"windows/*.cpp"
|
"windows/*.cpp"
|
||||||
"windows/guildsettings/*.hpp"
|
"windows/guildsettings/*.hpp"
|
||||||
"windows/guildsettings/*.cpp"
|
"windows/guildsettings/*.cpp"
|
||||||
|
"windows/profile/*.hpp"
|
||||||
|
"windows/profile/*.cpp"
|
||||||
"dialogs/*.hpp"
|
"dialogs/*.hpp"
|
||||||
"dialogs/*.cpp"
|
"dialogs/*.cpp"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "dialogs/setstatus.hpp"
|
#include "dialogs/setstatus.hpp"
|
||||||
#include "abaddon.hpp"
|
#include "abaddon.hpp"
|
||||||
#include "windows/guildsettingswindow.hpp"
|
#include "windows/guildsettingswindow.hpp"
|
||||||
|
#include "windows/profilewindow.hpp"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#pragma comment(lib, "crypt32.lib")
|
#pragma comment(lib, "crypt32.lib")
|
||||||
@@ -84,6 +85,7 @@ int Abaddon::StartGTK() {
|
|||||||
m_user_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID"));
|
m_user_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID"));
|
||||||
m_user_menu_open_dm = Gtk::manage(new Gtk::MenuItem("Open DM"));
|
m_user_menu_open_dm = Gtk::manage(new Gtk::MenuItem("Open DM"));
|
||||||
m_user_menu_roles = Gtk::manage(new Gtk::MenuItem("Roles"));
|
m_user_menu_roles = Gtk::manage(new Gtk::MenuItem("Roles"));
|
||||||
|
m_user_menu_info = Gtk::manage(new Gtk::MenuItem("View Profile"));
|
||||||
m_user_menu_roles_submenu = Gtk::manage(new Gtk::Menu);
|
m_user_menu_roles_submenu = Gtk::manage(new Gtk::Menu);
|
||||||
m_user_menu_roles->set_submenu(*m_user_menu_roles_submenu);
|
m_user_menu_roles->set_submenu(*m_user_menu_roles_submenu);
|
||||||
m_user_menu_insert_mention->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_insert_mention));
|
m_user_menu_insert_mention->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_insert_mention));
|
||||||
@@ -91,6 +93,11 @@ int Abaddon::StartGTK() {
|
|||||||
m_user_menu_kick->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_kick));
|
m_user_menu_kick->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_kick));
|
||||||
m_user_menu_copy_id->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_copy_id));
|
m_user_menu_copy_id->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_copy_id));
|
||||||
m_user_menu_open_dm->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_open_dm));
|
m_user_menu_open_dm->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_open_dm));
|
||||||
|
m_user_menu_info->signal_activate().connect([this]() {
|
||||||
|
auto *window = new ProfileWindow(m_shown_user_menu_id);
|
||||||
|
window->show();
|
||||||
|
});
|
||||||
|
m_user_menu->append(*m_user_menu_info);
|
||||||
m_user_menu->append(*m_user_menu_insert_mention);
|
m_user_menu->append(*m_user_menu_insert_mention);
|
||||||
m_user_menu->append(*m_user_menu_ban);
|
m_user_menu->append(*m_user_menu_ban);
|
||||||
m_user_menu->append(*m_user_menu_kick);
|
m_user_menu->append(*m_user_menu_kick);
|
||||||
@@ -503,7 +510,7 @@ void Abaddon::ActionReactionRemove(Snowflake id, const Glib::ustring ¶m) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Abaddon::ActionGuildSettings(Snowflake id) {
|
void Abaddon::ActionGuildSettings(Snowflake id) {
|
||||||
auto *window = new GuildSettingsWindow(id);
|
auto window = new GuildSettingsWindow(id);
|
||||||
window->show();
|
window->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ protected:
|
|||||||
void ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_id);
|
void ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_id);
|
||||||
|
|
||||||
Gtk::Menu *m_user_menu;
|
Gtk::Menu *m_user_menu;
|
||||||
|
Gtk::MenuItem *m_user_menu_info;
|
||||||
Gtk::MenuItem *m_user_menu_insert_mention;
|
Gtk::MenuItem *m_user_menu_insert_mention;
|
||||||
Gtk::MenuItem *m_user_menu_ban;
|
Gtk::MenuItem *m_user_menu_ban;
|
||||||
Gtk::MenuItem *m_user_menu_kick;
|
Gtk::MenuItem *m_user_menu_kick;
|
||||||
|
|||||||
54
css/main.css
@@ -233,3 +233,57 @@
|
|||||||
.status-indicator.idle {
|
.status-indicator.idle {
|
||||||
color: #FAA61A;
|
color: #FAA61A;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-main-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-username {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-badge {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-switcher {
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-connections {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-connection {
|
||||||
|
background: @secondary_color;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-connection box {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stack {
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-notes-text, .profile-notes-text text {
|
||||||
|
background: @secondary_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-notes-text text {
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #36515e;
|
||||||
|
color: @text_color;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-badges {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-window textview text {
|
||||||
|
caret-color: #ababab;
|
||||||
|
}
|
||||||
|
|||||||
@@ -112,8 +112,6 @@ std::set<Snowflake> DiscordClient::GetMessagesForChannel(Snowflake id) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DiscordClient::FetchInvite(std::string code, sigc::slot<void(std::optional<InviteData>)> callback) {
|
void DiscordClient::FetchInvite(std::string code, sigc::slot<void(std::optional<InviteData>)> callback) {
|
||||||
sigc::signal<void, std::optional<InviteData>> signal;
|
|
||||||
signal.connect(callback);
|
|
||||||
m_http.MakeGET("/invites/" + code + "?with_counts=true", [this, callback](http::response_type r) {
|
m_http.MakeGET("/invites/" + code + "?with_counts=true", [this, callback](http::response_type r) {
|
||||||
if (!CheckCode(r)) {
|
if (!CheckCode(r)) {
|
||||||
if (r.status_code == 404)
|
if (r.status_code == 404)
|
||||||
@@ -371,18 +369,16 @@ void DiscordClient::EditMessage(Snowflake channel_id, Snowflake id, std::string
|
|||||||
|
|
||||||
void DiscordClient::SendLazyLoad(Snowflake id) {
|
void DiscordClient::SendLazyLoad(Snowflake id) {
|
||||||
LazyLoadRequestMessage msg;
|
LazyLoadRequestMessage msg;
|
||||||
std::unordered_map<Snowflake, std::vector<std::pair<int, int>>> c;
|
msg.Channels.emplace();
|
||||||
c[id] = {
|
msg.Channels.value()[id] = {
|
||||||
std::make_pair(0, 99),
|
std::make_pair(0, 99),
|
||||||
std::make_pair(100, 199)
|
std::make_pair(100, 199)
|
||||||
};
|
};
|
||||||
msg.Channels = c;
|
|
||||||
msg.GuildID = *GetChannel(id)->GuildID;
|
msg.GuildID = *GetChannel(id)->GuildID;
|
||||||
msg.ShouldGetActivities = true;
|
msg.ShouldGetActivities = true;
|
||||||
msg.ShouldGetTyping = true;
|
msg.ShouldGetTyping = true;
|
||||||
|
|
||||||
nlohmann::json j = msg;
|
m_websocket.Send(msg);
|
||||||
m_websocket.Send(j);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiscordClient::JoinGuild(std::string code) {
|
void DiscordClient::JoinGuild(std::string code) {
|
||||||
@@ -477,11 +473,9 @@ void DiscordClient::SetGuildName(Snowflake id, const Glib::ustring &name) {
|
|||||||
void DiscordClient::SetGuildName(Snowflake id, const Glib::ustring &name, sigc::slot<void(bool success)> callback) {
|
void DiscordClient::SetGuildName(Snowflake id, const Glib::ustring &name, sigc::slot<void(bool success)> callback) {
|
||||||
ModifyGuildObject obj;
|
ModifyGuildObject obj;
|
||||||
obj.Name = name;
|
obj.Name = name;
|
||||||
sigc::signal<void, bool> signal;
|
m_http.MakePATCH("/guilds/" + std::to_string(id), nlohmann::json(obj).dump(), [this, callback](const http::response_type &r) {
|
||||||
signal.connect(callback);
|
|
||||||
m_http.MakePATCH("/guilds/" + std::to_string(id), nlohmann::json(obj).dump(), [this, signal](const http::response_type &r) {
|
|
||||||
const auto success = r.status_code == 200;
|
const auto success = r.status_code == 200;
|
||||||
signal.emit(success);
|
callback(success);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,11 +486,9 @@ void DiscordClient::SetGuildIcon(Snowflake id, const std::string &data) {
|
|||||||
void DiscordClient::SetGuildIcon(Snowflake id, const std::string &data, sigc::slot<void(bool success)> callback) {
|
void DiscordClient::SetGuildIcon(Snowflake id, const std::string &data, sigc::slot<void(bool success)> callback) {
|
||||||
ModifyGuildObject obj;
|
ModifyGuildObject obj;
|
||||||
obj.IconData = data;
|
obj.IconData = data;
|
||||||
sigc::signal<void, bool> signal;
|
m_http.MakePATCH("/guilds/" + std::to_string(id), nlohmann::json(obj).dump(), [this, callback](const http::response_type &r) {
|
||||||
signal.connect(callback);
|
|
||||||
m_http.MakePATCH("/guilds/" + std::to_string(id), nlohmann::json(obj).dump(), [this, signal](const http::response_type &r) {
|
|
||||||
const auto success = r.status_code == 200;
|
const auto success = r.status_code == 200;
|
||||||
signal.emit(success);
|
callback(success);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,8 +497,6 @@ void DiscordClient::UnbanUser(Snowflake guild_id, Snowflake user_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DiscordClient::UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot<void(bool success)> callback) {
|
void DiscordClient::UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot<void(bool success)> callback) {
|
||||||
sigc::signal<void, bool> signal;
|
|
||||||
signal.connect(callback);
|
|
||||||
m_http.MakeDELETE("/guilds/" + std::to_string(guild_id) + "/bans/" + std::to_string(user_id), [this, callback](const http::response_type &response) {
|
m_http.MakeDELETE("/guilds/" + std::to_string(guild_id) + "/bans/" + std::to_string(user_id), [this, callback](const http::response_type &response) {
|
||||||
callback(response.status_code == 204);
|
callback(response.status_code == 204);
|
||||||
});
|
});
|
||||||
@@ -517,8 +507,6 @@ void DiscordClient::DeleteInvite(const std::string &code) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DiscordClient::DeleteInvite(const std::string &code, sigc::slot<void(bool success)> callback) {
|
void DiscordClient::DeleteInvite(const std::string &code, sigc::slot<void(bool success)> callback) {
|
||||||
sigc::signal<void, bool> signal;
|
|
||||||
signal.connect(callback);
|
|
||||||
m_http.MakeDELETE("/invites/" + code, [this, callback](const http::response_type &response) {
|
m_http.MakeDELETE("/invites/" + code, [this, callback](const http::response_type &response) {
|
||||||
callback(CheckCode(response));
|
callback(CheckCode(response));
|
||||||
});
|
});
|
||||||
@@ -529,8 +517,6 @@ std::vector<BanData> DiscordClient::GetBansInGuild(Snowflake guild_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DiscordClient::FetchGuildBan(Snowflake guild_id, Snowflake user_id, sigc::slot<void(BanData)> callback) {
|
void DiscordClient::FetchGuildBan(Snowflake guild_id, Snowflake user_id, sigc::slot<void(BanData)> callback) {
|
||||||
sigc::signal<void, BanData> signal;
|
|
||||||
signal.connect(callback);
|
|
||||||
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/bans/" + std::to_string(user_id), [this, callback, guild_id](const http::response_type &response) {
|
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/bans/" + std::to_string(user_id), [this, callback, guild_id](const http::response_type &response) {
|
||||||
if (!CheckCode(response)) return;
|
if (!CheckCode(response)) return;
|
||||||
auto ban = nlohmann::json::parse(response.text).get<BanData>();
|
auto ban = nlohmann::json::parse(response.text).get<BanData>();
|
||||||
@@ -541,8 +527,6 @@ void DiscordClient::FetchGuildBan(Snowflake guild_id, Snowflake user_id, sigc::s
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DiscordClient::FetchGuildBans(Snowflake guild_id, sigc::slot<void(std::vector<BanData>)> callback) {
|
void DiscordClient::FetchGuildBans(Snowflake guild_id, sigc::slot<void(std::vector<BanData>)> callback) {
|
||||||
sigc::signal<void, std::vector<BanData>> signal;
|
|
||||||
signal.connect(callback);
|
|
||||||
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/bans", [this, callback, guild_id](const http::response_type &response) {
|
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/bans", [this, callback, guild_id](const http::response_type &response) {
|
||||||
if (!CheckCode(response)) return;
|
if (!CheckCode(response)) return;
|
||||||
auto bans = nlohmann::json::parse(response.text).get<std::vector<BanData>>();
|
auto bans = nlohmann::json::parse(response.text).get<std::vector<BanData>>();
|
||||||
@@ -557,8 +541,6 @@ void DiscordClient::FetchGuildBans(Snowflake guild_id, sigc::slot<void(std::vect
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DiscordClient::FetchGuildInvites(Snowflake guild_id, sigc::slot<void(std::vector<InviteData>)> callback) {
|
void DiscordClient::FetchGuildInvites(Snowflake guild_id, sigc::slot<void(std::vector<InviteData>)> callback) {
|
||||||
sigc::signal<void, std::vector<InviteData>> signal;
|
|
||||||
signal.connect(callback);
|
|
||||||
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/invites", [this, callback, guild_id](const http::response_type &response) {
|
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/invites", [this, callback, guild_id](const http::response_type &response) {
|
||||||
// store?
|
// store?
|
||||||
if (!CheckCode(response)) return;
|
if (!CheckCode(response)) return;
|
||||||
@@ -575,8 +557,6 @@ void DiscordClient::FetchGuildInvites(Snowflake guild_id, sigc::slot<void(std::v
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DiscordClient::FetchAuditLog(Snowflake guild_id, sigc::slot<void(AuditLogData)> callback) {
|
void DiscordClient::FetchAuditLog(Snowflake guild_id, sigc::slot<void(AuditLogData)> callback) {
|
||||||
sigc::signal<void, AuditLogData> signal;
|
|
||||||
signal.connect(callback);
|
|
||||||
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/audit-logs", [this, callback](const http::response &response) {
|
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/audit-logs", [this, callback](const http::response &response) {
|
||||||
if (!CheckCode(response)) return;
|
if (!CheckCode(response)) return;
|
||||||
auto data = nlohmann::json::parse(response.text).get<AuditLogData>();
|
auto data = nlohmann::json::parse(response.text).get<AuditLogData>();
|
||||||
@@ -590,6 +570,35 @@ void DiscordClient::FetchAuditLog(Snowflake guild_id, sigc::slot<void(AuditLogDa
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DiscordClient::FetchUserProfile(Snowflake user_id, sigc::slot<void(UserProfileData)> callback) {
|
||||||
|
m_http.MakeGET("/users/" + std::to_string(user_id) + "/profile", [this, callback](const http::response_type &response) {
|
||||||
|
if (!CheckCode(response)) return;
|
||||||
|
callback(nlohmann::json::parse(response.text).get<UserProfileData>());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiscordClient::FetchUserNote(Snowflake user_id, sigc::slot<void(std::string note)> callback) {
|
||||||
|
m_http.MakeGET("/users/@me/notes/" + std::to_string(user_id), [this, callback](const http::response_type &response) {
|
||||||
|
if (response.status_code == 404) return;
|
||||||
|
if (!CheckCode(response)) return;
|
||||||
|
const auto note = nlohmann::json::parse(response.text).get<UserNoteObject>().Note;
|
||||||
|
if (note.has_value())
|
||||||
|
callback(*note);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiscordClient::SetUserNote(Snowflake user_id, std::string note) {
|
||||||
|
SetUserNote(user_id, note, [](auto) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiscordClient::SetUserNote(Snowflake user_id, std::string note, sigc::slot<void(bool success)> callback) {
|
||||||
|
UserSetNoteObject obj;
|
||||||
|
obj.Note = note;
|
||||||
|
m_http.MakePUT("/users/@me/notes/" + std::to_string(user_id), nlohmann::json(obj).dump(), [this, callback](const http::response_type &response) {
|
||||||
|
callback(response.status_code == 204);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void DiscordClient::UpdateToken(std::string token) {
|
void DiscordClient::UpdateToken(std::string token) {
|
||||||
if (!IsStarted()) {
|
if (!IsStarted()) {
|
||||||
m_token = token;
|
m_token = token;
|
||||||
@@ -769,6 +778,9 @@ void DiscordClient::HandleGatewayMessage(std::string str) {
|
|||||||
case GatewayEvent::INVITE_DELETE: {
|
case GatewayEvent::INVITE_DELETE: {
|
||||||
HandleGatewayInviteDelete(m);
|
HandleGatewayInviteDelete(m);
|
||||||
} break;
|
} break;
|
||||||
|
case GatewayEvent::USER_NOTE_UPDATE: {
|
||||||
|
HandleGatewayUserNoteUpdate(m);
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
default:
|
default:
|
||||||
@@ -1147,6 +1159,11 @@ void DiscordClient::HandleGatewayInviteDelete(const GatewayMessage &msg) {
|
|||||||
m_signal_invite_delete.emit(data);
|
m_signal_invite_delete.emit(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DiscordClient::HandleGatewayUserNoteUpdate(const GatewayMessage &msg) {
|
||||||
|
UserNoteUpdateMessage data = msg.Data;
|
||||||
|
m_signal_note_update.emit(data.ID, data.Note);
|
||||||
|
}
|
||||||
|
|
||||||
void DiscordClient::HandleGatewayReconnect(const GatewayMessage &msg) {
|
void DiscordClient::HandleGatewayReconnect(const GatewayMessage &msg) {
|
||||||
printf("received reconnect\n");
|
printf("received reconnect\n");
|
||||||
m_signal_disconnected.emit(true, GatewayCloseCode::Reconnecting);
|
m_signal_disconnected.emit(true, GatewayCloseCode::Reconnecting);
|
||||||
@@ -1427,6 +1444,7 @@ void DiscordClient::LoadEventMap() {
|
|||||||
m_event_map["GUILD_BAN_ADD"] = GatewayEvent::GUILD_BAN_ADD;
|
m_event_map["GUILD_BAN_ADD"] = GatewayEvent::GUILD_BAN_ADD;
|
||||||
m_event_map["INVITE_CREATE"] = GatewayEvent::INVITE_CREATE;
|
m_event_map["INVITE_CREATE"] = GatewayEvent::INVITE_CREATE;
|
||||||
m_event_map["INVITE_DELETE"] = GatewayEvent::INVITE_DELETE;
|
m_event_map["INVITE_DELETE"] = GatewayEvent::INVITE_DELETE;
|
||||||
|
m_event_map["USER_NOTE_UPDATE"] = GatewayEvent::USER_NOTE_UPDATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() {
|
DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() {
|
||||||
@@ -1528,3 +1546,7 @@ DiscordClient::type_signal_invite_delete DiscordClient::signal_invite_delete() {
|
|||||||
DiscordClient::type_signal_presence_update DiscordClient::signal_presence_update() {
|
DiscordClient::type_signal_presence_update DiscordClient::signal_presence_update() {
|
||||||
return m_signal_presence_update;
|
return m_signal_presence_update;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DiscordClient::type_signal_note_update DiscordClient::signal_note_update() {
|
||||||
|
return m_signal_note_update;
|
||||||
|
}
|
||||||
|
|||||||
@@ -130,6 +130,11 @@ public:
|
|||||||
|
|
||||||
void FetchAuditLog(Snowflake guild_id, sigc::slot<void(AuditLogData)> callback);
|
void FetchAuditLog(Snowflake guild_id, sigc::slot<void(AuditLogData)> callback);
|
||||||
|
|
||||||
|
void FetchUserProfile(Snowflake user_id, sigc::slot<void(UserProfileData)> callback);
|
||||||
|
void FetchUserNote(Snowflake user_id, sigc::slot<void(std::string note)> callback);
|
||||||
|
void SetUserNote(Snowflake user_id, std::string note);
|
||||||
|
void SetUserNote(Snowflake user_id, std::string note, sigc::slot<void(bool success)> callback);
|
||||||
|
|
||||||
void UpdateToken(std::string token);
|
void UpdateToken(std::string token);
|
||||||
void SetUserAgent(std::string agent);
|
void SetUserAgent(std::string agent);
|
||||||
|
|
||||||
@@ -172,6 +177,7 @@ private:
|
|||||||
void HandleGatewayGuildBanAdd(const GatewayMessage &msg);
|
void HandleGatewayGuildBanAdd(const GatewayMessage &msg);
|
||||||
void HandleGatewayInviteCreate(const GatewayMessage &msg);
|
void HandleGatewayInviteCreate(const GatewayMessage &msg);
|
||||||
void HandleGatewayInviteDelete(const GatewayMessage &msg);
|
void HandleGatewayInviteDelete(const GatewayMessage &msg);
|
||||||
|
void HandleGatewayUserNoteUpdate(const GatewayMessage &msg);
|
||||||
void HandleGatewayReconnect(const GatewayMessage &msg);
|
void HandleGatewayReconnect(const GatewayMessage &msg);
|
||||||
void HandleGatewayInvalidSession(const GatewayMessage &msg);
|
void HandleGatewayInvalidSession(const GatewayMessage &msg);
|
||||||
void HeartbeatThread();
|
void HeartbeatThread();
|
||||||
@@ -252,6 +258,7 @@ public:
|
|||||||
typedef sigc::signal<void, InviteData> type_signal_invite_create;
|
typedef sigc::signal<void, InviteData> type_signal_invite_create;
|
||||||
typedef sigc::signal<void, InviteDeleteObject> type_signal_invite_delete;
|
typedef sigc::signal<void, InviteDeleteObject> type_signal_invite_delete;
|
||||||
typedef sigc::signal<void, Snowflake, PresenceStatus> type_signal_presence_update;
|
typedef sigc::signal<void, Snowflake, PresenceStatus> type_signal_presence_update;
|
||||||
|
typedef sigc::signal<void, Snowflake, std::string> type_signal_note_update;
|
||||||
typedef sigc::signal<void, bool, GatewayCloseCode> type_signal_disconnected; // bool true if reconnecting
|
typedef sigc::signal<void, bool, GatewayCloseCode> type_signal_disconnected; // bool true if reconnecting
|
||||||
typedef sigc::signal<void> type_signal_connected;
|
typedef sigc::signal<void> type_signal_connected;
|
||||||
|
|
||||||
@@ -278,6 +285,7 @@ public:
|
|||||||
type_signal_invite_create signal_invite_create();
|
type_signal_invite_create signal_invite_create();
|
||||||
type_signal_invite_delete signal_invite_delete(); // safe to assume guild id is set
|
type_signal_invite_delete signal_invite_delete(); // safe to assume guild id is set
|
||||||
type_signal_presence_update signal_presence_update();
|
type_signal_presence_update signal_presence_update();
|
||||||
|
type_signal_note_update signal_note_update();
|
||||||
type_signal_disconnected signal_disconnected();
|
type_signal_disconnected signal_disconnected();
|
||||||
type_signal_connected signal_connected();
|
type_signal_connected signal_connected();
|
||||||
|
|
||||||
@@ -305,6 +313,7 @@ protected:
|
|||||||
type_signal_invite_create m_signal_invite_create;
|
type_signal_invite_create m_signal_invite_create;
|
||||||
type_signal_invite_delete m_signal_invite_delete;
|
type_signal_invite_delete m_signal_invite_delete;
|
||||||
type_signal_presence_update m_signal_presence_update;
|
type_signal_presence_update m_signal_presence_update;
|
||||||
|
type_signal_note_update m_signal_note_update;
|
||||||
type_signal_disconnected m_signal_disconnected;
|
type_signal_disconnected m_signal_disconnected;
|
||||||
type_signal_connected m_signal_connected;
|
type_signal_connected m_signal_connected;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -74,14 +74,15 @@ void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m) {
|
|||||||
j["op"] = GatewayOp::LazyLoadRequest;
|
j["op"] = GatewayOp::LazyLoadRequest;
|
||||||
j["d"] = nlohmann::json::object();
|
j["d"] = nlohmann::json::object();
|
||||||
j["d"]["guild_id"] = m.GuildID;
|
j["d"]["guild_id"] = m.GuildID;
|
||||||
j["d"]["channels"] = nlohmann::json::object();
|
if (m.Channels.has_value()) {
|
||||||
for (const auto &[key, chans] : m.Channels) { // apparently a map gets written as a list
|
j["d"]["channels"] = nlohmann::json::object();
|
||||||
j["d"]["channels"][std::to_string(key)] = chans;
|
for (const auto &[key, chans] : *m.Channels)
|
||||||
|
j["d"]["channels"][std::to_string(key)] = chans;
|
||||||
}
|
}
|
||||||
j["d"]["typing"] = m.ShouldGetTyping;
|
j["d"]["typing"] = m.ShouldGetTyping;
|
||||||
j["d"]["activities"] = m.ShouldGetActivities;
|
j["d"]["activities"] = m.ShouldGetActivities;
|
||||||
if (m.Members.size() > 0)
|
if (m.Members.has_value())
|
||||||
j["d"]["members"] = m.Members;
|
j["d"]["members"] = *m.Members;
|
||||||
}
|
}
|
||||||
|
|
||||||
void to_json(nlohmann::json &j, const UpdateStatusMessage &m) {
|
void to_json(nlohmann::json &j, const UpdateStatusMessage &m) {
|
||||||
@@ -301,3 +302,38 @@ void from_json(const nlohmann::json &j, InviteDeleteObject &m) {
|
|||||||
JS_O("guild_id", m.GuildID);
|
JS_O("guild_id", m.GuildID);
|
||||||
JS_D("code", m.Code);
|
JS_D("code", m.Code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json &j, ConnectionData &m) {
|
||||||
|
JS_D("id", m.ID);
|
||||||
|
JS_D("type", m.Type);
|
||||||
|
JS_D("name", m.Name);
|
||||||
|
JS_D("verified", m.IsVerified);
|
||||||
|
}
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json &j, MutualGuildData &m) {
|
||||||
|
JS_D("id", m.ID);
|
||||||
|
JS_ON("nick", m.Nick);
|
||||||
|
}
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json &j, UserProfileData &m) {
|
||||||
|
JS_D("connected_accounts", m.ConnectedAccounts);
|
||||||
|
JS_D("mutual_guilds", m.MutualGuilds);
|
||||||
|
JS_ON("premium_guild_since", m.PremiumGuildSince);
|
||||||
|
JS_ON("premium_since", m.PremiumSince);
|
||||||
|
JS_D("user", m.User);
|
||||||
|
}
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json &j, UserNoteObject &m) {
|
||||||
|
JS_ON("note", m.Note);
|
||||||
|
JS_ON("note_user_id", m.NoteUserID);
|
||||||
|
JS_ON("user_id", m.UserID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void to_json(nlohmann::json &j, UserSetNoteObject &m) {
|
||||||
|
j["note"] = m.Note;
|
||||||
|
}
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json &j, UserNoteUpdateMessage &m) {
|
||||||
|
JS_D("note", m.Note);
|
||||||
|
JS_D("id", m.ID);
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ enum class GatewayEvent : int {
|
|||||||
GUILD_BAN_ADD,
|
GUILD_BAN_ADD,
|
||||||
INVITE_CREATE,
|
INVITE_CREATE,
|
||||||
INVITE_DELETE,
|
INVITE_DELETE,
|
||||||
|
USER_NOTE_UPDATE,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class GatewayCloseCode : uint16_t {
|
enum class GatewayCloseCode : uint16_t {
|
||||||
@@ -170,8 +171,8 @@ struct LazyLoadRequestMessage {
|
|||||||
Snowflake GuildID;
|
Snowflake GuildID;
|
||||||
bool ShouldGetTyping = false;
|
bool ShouldGetTyping = false;
|
||||||
bool ShouldGetActivities = false;
|
bool ShouldGetActivities = false;
|
||||||
std::vector<std::string> Members; // snowflake?
|
std::optional<std::vector<std::string>> Members; // snowflake?
|
||||||
std::unordered_map<Snowflake, std::vector<std::pair<int, int>>> Channels; // channel ID -> range of sidebar
|
std::optional<std::unordered_map<Snowflake, std::vector<std::pair<int, int>>>> Channels; // channel ID -> range of sidebar
|
||||||
|
|
||||||
friend void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m);
|
friend void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m);
|
||||||
};
|
};
|
||||||
@@ -424,3 +425,51 @@ struct InviteDeleteObject {
|
|||||||
|
|
||||||
friend void from_json(const nlohmann::json &j, InviteDeleteObject &m);
|
friend void from_json(const nlohmann::json &j, InviteDeleteObject &m);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ConnectionData {
|
||||||
|
std::string ID;
|
||||||
|
std::string Type;
|
||||||
|
std::string Name;
|
||||||
|
bool IsVerified;
|
||||||
|
|
||||||
|
friend void from_json(const nlohmann::json &j, ConnectionData &m);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MutualGuildData {
|
||||||
|
Snowflake ID;
|
||||||
|
std::optional<std::string> Nick; // null
|
||||||
|
|
||||||
|
friend void from_json(const nlohmann::json &j, MutualGuildData &m);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UserProfileData {
|
||||||
|
std::vector<ConnectionData> ConnectedAccounts;
|
||||||
|
std::vector<MutualGuildData> MutualGuilds;
|
||||||
|
std::optional<std::string> PremiumGuildSince; // null
|
||||||
|
std::optional<std::string> PremiumSince; // null
|
||||||
|
UserData User;
|
||||||
|
|
||||||
|
friend void from_json(const nlohmann::json &j, UserProfileData &m);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UserNoteObject {
|
||||||
|
// idk if these can be null or missing but i play it safe
|
||||||
|
std::optional<std::string> Note;
|
||||||
|
std::optional<Snowflake> NoteUserID;
|
||||||
|
std::optional<Snowflake> UserID;
|
||||||
|
|
||||||
|
friend void from_json(const nlohmann::json &j, UserNoteObject &m);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UserSetNoteObject {
|
||||||
|
std::string Note;
|
||||||
|
|
||||||
|
friend void to_json(nlohmann::json &j, UserSetNoteObject &m);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UserNoteUpdateMessage {
|
||||||
|
std::string Note;
|
||||||
|
Snowflake ID;
|
||||||
|
|
||||||
|
friend void from_json(const nlohmann::json &j, UserNoteUpdateMessage &m);
|
||||||
|
};
|
||||||
|
|||||||
@@ -86,3 +86,69 @@ void UserData::update_from_json(const nlohmann::json &j) {
|
|||||||
JS_RD("nsfw_allowed", IsNSFWAllowed);
|
JS_RD("nsfw_allowed", IsNSFWAllowed);
|
||||||
JS_RD("phone", Phone);
|
JS_RD("phone", Phone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *UserData::GetFlagName(uint64_t flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case DiscordEmployee:
|
||||||
|
return "discordstaff";
|
||||||
|
case PartneredServerOwner:
|
||||||
|
return "partneredowner";
|
||||||
|
case HypeSquadEvents:
|
||||||
|
return "hypesquadevents";
|
||||||
|
case BugHunterLevel1:
|
||||||
|
return "discordbughunter";
|
||||||
|
case HouseBravery:
|
||||||
|
return "hypesquadbravery";
|
||||||
|
case HouseBrilliance:
|
||||||
|
return "hypesquadbrilliance";
|
||||||
|
case HouseBalance:
|
||||||
|
return "hypesquadbalance";
|
||||||
|
case EarlySupporter:
|
||||||
|
return "earlysupporter";
|
||||||
|
case TeamUser:
|
||||||
|
return "teamuser";
|
||||||
|
case System:
|
||||||
|
return "system";
|
||||||
|
case BugHunterLevel2:
|
||||||
|
return "discordbughunter2";
|
||||||
|
case VerifiedBot:
|
||||||
|
return "verifiedbot";
|
||||||
|
case EarlyVerifiedBotDeveloper:
|
||||||
|
return "earlyverifiedbotdeveloper";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *UserData::GetFlagReadableName(uint64_t flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case DiscordEmployee:
|
||||||
|
return "Discord Staff";
|
||||||
|
case PartneredServerOwner:
|
||||||
|
return "Partnered Server Owner";
|
||||||
|
case HypeSquadEvents:
|
||||||
|
return "HypeSquad Events";
|
||||||
|
case BugHunterLevel1:
|
||||||
|
return "Discord Bug Hunter";
|
||||||
|
case HouseBravery:
|
||||||
|
return "HypeSquad Bravery";
|
||||||
|
case HouseBrilliance:
|
||||||
|
return "HypeSquad Brilliance";
|
||||||
|
case HouseBalance:
|
||||||
|
return "HypeSquad Balance";
|
||||||
|
case EarlySupporter:
|
||||||
|
return "Early Supporter";
|
||||||
|
case TeamUser:
|
||||||
|
return "Team User"; // ???
|
||||||
|
case System:
|
||||||
|
return "System";
|
||||||
|
case BugHunterLevel2:
|
||||||
|
return "Discord Bug Hunter Level 2";
|
||||||
|
case VerifiedBot:
|
||||||
|
return "Verified Bot";
|
||||||
|
case EarlyVerifiedBotDeveloper:
|
||||||
|
return "Early Verified Bot Developer";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,27 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
struct UserData {
|
struct UserData {
|
||||||
|
enum {
|
||||||
|
DiscordEmployee = 1 << 0,
|
||||||
|
PartneredServerOwner = 1 << 1,
|
||||||
|
HypeSquadEvents = 1 << 2,
|
||||||
|
BugHunterLevel1 = 1 << 3,
|
||||||
|
HouseBravery = 1 << 6,
|
||||||
|
HouseBrilliance = 1 << 7,
|
||||||
|
HouseBalance = 1 << 8,
|
||||||
|
EarlySupporter = 1 << 9,
|
||||||
|
TeamUser = 1 << 10, // no idea what this is
|
||||||
|
System = 1 << 12,
|
||||||
|
BugHunterLevel2 = 1 << 14,
|
||||||
|
VerifiedBot = 1 << 16,
|
||||||
|
EarlyVerifiedBotDeveloper = 1 << 17,
|
||||||
|
|
||||||
|
MaxFlag = EarlyVerifiedBotDeveloper,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *GetFlagName(uint64_t flag);
|
||||||
|
static const char *GetFlagReadableName(uint64_t flag);
|
||||||
|
|
||||||
Snowflake ID;
|
Snowflake ID;
|
||||||
std::string Username;
|
std::string Username;
|
||||||
std::string Discriminator;
|
std::string Discriminator;
|
||||||
@@ -14,9 +35,9 @@ struct UserData {
|
|||||||
std::optional<std::string> Locale;
|
std::optional<std::string> Locale;
|
||||||
std::optional<bool> IsVerified;
|
std::optional<bool> IsVerified;
|
||||||
std::optional<std::string> Email; // null
|
std::optional<std::string> Email; // null
|
||||||
std::optional<int> Flags;
|
std::optional<uint64_t> Flags;
|
||||||
std::optional<int> PremiumType; // null
|
std::optional<int> PremiumType; // null
|
||||||
std::optional<int> PublicFlags;
|
std::optional<uint64_t> PublicFlags;
|
||||||
|
|
||||||
// undocumented (opt)
|
// undocumented (opt)
|
||||||
std::optional<bool> IsDesktop;
|
std::optional<bool> IsDesktop;
|
||||||
|
|||||||
BIN
res/battlenet.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
res/discordbughunter.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
res/discordbughunter2.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
res/discordstaff.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
res/earlysupporter.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
res/earlyverifiedbotdeveloper.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
res/facebook.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
res/github.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
res/guildsubscriber.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
res/hypesquadbalance.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
res/hypesquadbravery.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
res/hypesquadbrilliance.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
res/hypesquadevents.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
res/leagueoflegends.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
res/partneredowner.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
res/premium.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
res/reddit.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
res/skype.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
res/spotify.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
res/steam.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
res/twitch.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
res/twitter.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
res/xbox.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
res/youtube.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
9
util.cpp
@@ -182,3 +182,12 @@ bool IsURLViewableImage(const std::string &url) {
|
|||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddPointerCursor(Gtk::Widget &widget) {
|
||||||
|
widget.signal_realize().connect([&widget]() {
|
||||||
|
auto window = widget.get_window();
|
||||||
|
auto display = window->get_display();
|
||||||
|
auto cursor = Gdk::Cursor::create(display, "pointer");
|
||||||
|
window->set_cursor(cursor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
1
util.hpp
@@ -46,6 +46,7 @@ bool IsURLViewableImage(const std::string &url);
|
|||||||
std::vector<uint8_t> ReadWholeFile(std::string path);
|
std::vector<uint8_t> ReadWholeFile(std::string path);
|
||||||
std::string HumanReadableBytes(uint64_t bytes);
|
std::string HumanReadableBytes(uint64_t bytes);
|
||||||
std::string FormatISO8601(const std::string &in, int extra_offset = 0, const std::string &fmt = "%x %X");
|
std::string FormatISO8601(const std::string &in, int extra_offset = 0, const std::string &fmt = "%x %X");
|
||||||
|
void AddPointerCursor(Gtk::Widget &widget);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
struct Bitwise {
|
struct Bitwise {
|
||||||
|
|||||||
179
windows/profile/userinfopane.cpp
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
#include "userinfopane.hpp"
|
||||||
|
#include <unordered_set>
|
||||||
|
#include "../../abaddon.hpp"
|
||||||
|
|
||||||
|
ConnectionsContainer::ConnectionsContainer() {
|
||||||
|
get_style_context()->add_class("profile-connections");
|
||||||
|
set_column_homogeneous(true);
|
||||||
|
set_row_spacing(10);
|
||||||
|
set_column_spacing(10);
|
||||||
|
show_all_children();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectionsContainer::SetConnections(const std::vector<ConnectionData> &connections) {
|
||||||
|
for (auto child : get_children())
|
||||||
|
delete child;
|
||||||
|
|
||||||
|
static const std::unordered_set<std::string> supported_services = {
|
||||||
|
"battlenet",
|
||||||
|
"github",
|
||||||
|
"leagueoflegends",
|
||||||
|
"reddit",
|
||||||
|
"skype",
|
||||||
|
"spotify",
|
||||||
|
"steam",
|
||||||
|
"twitch",
|
||||||
|
"twitter",
|
||||||
|
"xbox",
|
||||||
|
"youtube",
|
||||||
|
"facebook"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < connections.size(); i++) {
|
||||||
|
const auto &conn = connections[i];
|
||||||
|
if (supported_services.find(conn.Type) == supported_services.end()) continue;
|
||||||
|
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||||
|
try {
|
||||||
|
pixbuf = Gdk::Pixbuf::create_from_file("./res/" + conn.Type + ".png", 32, 32);
|
||||||
|
} catch (const Glib::Exception &e) {}
|
||||||
|
std::string url;
|
||||||
|
if (conn.Type == "github")
|
||||||
|
url = "https://github.com/" + conn.Name;
|
||||||
|
else if (conn.Type == "steam")
|
||||||
|
url = "https://steamcommunity.com/profiles/" + conn.ID;
|
||||||
|
else if (conn.Type == "twitch")
|
||||||
|
url = "https://twitch.tv/" + conn.Name;
|
||||||
|
else if (conn.Type == "twitter")
|
||||||
|
url = "https://twitter.com/i/user/" + conn.ID;
|
||||||
|
else if (conn.Type == "spotify")
|
||||||
|
url = "https://open.spotify.com/user/" + conn.ID;
|
||||||
|
else if (conn.Type == "reddit")
|
||||||
|
url = "https://reddit.com/u/" + conn.Name;
|
||||||
|
else if (conn.Type == "youtube")
|
||||||
|
url = "https://www.youtube.com/channel/" + conn.ID;
|
||||||
|
else if (conn.Type == "facebook")
|
||||||
|
url = "https://www.facebook.com/" + conn.ID;
|
||||||
|
auto *ev = Gtk::manage(new Gtk::EventBox);
|
||||||
|
auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||||
|
if (pixbuf) {
|
||||||
|
auto *img = Gtk::manage(new Gtk::Image(pixbuf));
|
||||||
|
img->get_style_context()->add_class("profile-connection-image");
|
||||||
|
box->add(*img);
|
||||||
|
}
|
||||||
|
auto *lbl = Gtk::manage(new Gtk::Label(conn.Name));
|
||||||
|
box->set_halign(Gtk::ALIGN_START);
|
||||||
|
box->set_size_request(200, -1);
|
||||||
|
box->get_style_context()->add_class("profile-connection");
|
||||||
|
lbl->get_style_context()->add_class("profile-connection-label");
|
||||||
|
lbl->set_valign(Gtk::ALIGN_CENTER);
|
||||||
|
lbl->set_single_line_mode(true);
|
||||||
|
lbl->set_ellipsize(Pango::ELLIPSIZE_END);
|
||||||
|
box->add(*lbl);
|
||||||
|
if (url != "") {
|
||||||
|
auto cb = [this, url](GdkEventButton *event) -> bool {
|
||||||
|
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
|
||||||
|
LaunchBrowser(url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
ev->signal_button_press_event().connect(sigc::track_obj(cb, *ev));
|
||||||
|
AddPointerCursor(*ev);
|
||||||
|
}
|
||||||
|
ev->add(*box);
|
||||||
|
ev->show_all();
|
||||||
|
attach(*ev, i % 2, i / 2, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_halign(Gtk::ALIGN_FILL);
|
||||||
|
set_hexpand(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
NotesContainer::NotesContainer()
|
||||||
|
: Gtk::Box(Gtk::ORIENTATION_VERTICAL) {
|
||||||
|
get_style_context()->add_class("profile-notes");
|
||||||
|
m_label.get_style_context()->add_class("profile-notes-label");
|
||||||
|
m_note.get_style_context()->add_class("profile-notes-text");
|
||||||
|
|
||||||
|
m_label.set_markup("<b>NOTE</b>");
|
||||||
|
m_label.set_halign(Gtk::ALIGN_START);
|
||||||
|
|
||||||
|
m_note.set_wrap_mode(Gtk::WRAP_WORD_CHAR);
|
||||||
|
m_note.signal_key_press_event().connect(sigc::mem_fun(*this, &NotesContainer::OnNoteKeyPress), false);
|
||||||
|
|
||||||
|
add(m_label);
|
||||||
|
add(m_note);
|
||||||
|
show_all_children();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotesContainer::SetNote(const std::string ¬e) {
|
||||||
|
m_note.get_buffer()->set_text(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotesContainer::UpdateNote() {
|
||||||
|
auto text = m_note.get_buffer()->get_text();
|
||||||
|
if (text.size() > 256)
|
||||||
|
text = text.substr(0, 256);
|
||||||
|
m_signal_update_note.emit(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NotesContainer::OnNoteKeyPress(GdkEventKey *event) {
|
||||||
|
if (event->type != GDK_KEY_PRESS) return false;
|
||||||
|
const auto text = m_note.get_buffer()->get_text();
|
||||||
|
if (event->keyval == GDK_KEY_Return) {
|
||||||
|
if (event->state & GDK_SHIFT_MASK) {
|
||||||
|
int newlines = 0;
|
||||||
|
for (const auto c : text)
|
||||||
|
if (c == '\n') newlines++;
|
||||||
|
return newlines >= 5;
|
||||||
|
} else {
|
||||||
|
UpdateNote();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotesContainer::type_signal_update_note NotesContainer::signal_update_note() {
|
||||||
|
return m_signal_update_note;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileUserInfoPane::ProfileUserInfoPane(Snowflake ID)
|
||||||
|
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
|
||||||
|
, UserID(ID) {
|
||||||
|
get_style_context()->add_class("profile-info-pane");
|
||||||
|
|
||||||
|
m_note.signal_update_note().connect([this](const Glib::ustring ¬e) {
|
||||||
|
auto cb = [this](bool success) {
|
||||||
|
if (!success) {
|
||||||
|
Gtk::MessageDialog dlg("Failed to set note", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||||
|
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||||
|
dlg.run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Abaddon::Get().GetDiscordClient().SetUserNote(UserID, note, sigc::track_obj(cb, *this));
|
||||||
|
});
|
||||||
|
|
||||||
|
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||||
|
discord.signal_note_update().connect([this](Snowflake id, std::string note) {
|
||||||
|
if (id == UserID)
|
||||||
|
m_note.SetNote(note);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto fetch_note_cb = [this](const std::string ¬e) {
|
||||||
|
m_note.SetNote(note);
|
||||||
|
};
|
||||||
|
discord.FetchUserNote(UserID, sigc::track_obj(fetch_note_cb, *this));
|
||||||
|
|
||||||
|
m_conns.set_halign(Gtk::ALIGN_START);
|
||||||
|
m_conns.set_hexpand(true);
|
||||||
|
|
||||||
|
add(m_note);
|
||||||
|
add(m_conns);
|
||||||
|
show_all_children();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileUserInfoPane::SetConnections(const std::vector<ConnectionData> &connections) {
|
||||||
|
m_conns.SetConnections(connections);
|
||||||
|
}
|
||||||
40
windows/profile/userinfopane.hpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <gtkmm.h>
|
||||||
|
#include "../../discord/objects.hpp"
|
||||||
|
|
||||||
|
class ConnectionsContainer : public Gtk::Grid {
|
||||||
|
public:
|
||||||
|
ConnectionsContainer();
|
||||||
|
void SetConnections(const std::vector<ConnectionData> &connections);
|
||||||
|
};
|
||||||
|
|
||||||
|
class NotesContainer : public Gtk::Box {
|
||||||
|
public:
|
||||||
|
NotesContainer();
|
||||||
|
void SetNote(const std::string ¬e);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void UpdateNote();
|
||||||
|
bool OnNoteKeyPress(GdkEventKey *event);
|
||||||
|
|
||||||
|
Gtk::Label m_label;
|
||||||
|
Gtk::TextView m_note;
|
||||||
|
|
||||||
|
typedef sigc::signal<void, Glib::ustring> type_signal_update_note;
|
||||||
|
type_signal_update_note m_signal_update_note;
|
||||||
|
|
||||||
|
public:
|
||||||
|
type_signal_update_note signal_update_note();
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProfileUserInfoPane : public Gtk::Box {
|
||||||
|
public:
|
||||||
|
ProfileUserInfoPane(Snowflake ID);
|
||||||
|
void SetConnections(const std::vector<ConnectionData> &connections);
|
||||||
|
|
||||||
|
Snowflake UserID;
|
||||||
|
|
||||||
|
private:
|
||||||
|
NotesContainer m_note;
|
||||||
|
ConnectionsContainer m_conns;
|
||||||
|
};
|
||||||
124
windows/profilewindow.cpp
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#include "profilewindow.hpp"
|
||||||
|
#include "../abaddon.hpp"
|
||||||
|
|
||||||
|
ProfileWindow::ProfileWindow(Snowflake user_id)
|
||||||
|
: ID(user_id)
|
||||||
|
, m_main(Gtk::ORIENTATION_VERTICAL)
|
||||||
|
, m_upper(Gtk::ORIENTATION_HORIZONTAL)
|
||||||
|
, m_badges(Gtk::ORIENTATION_HORIZONTAL)
|
||||||
|
, m_pane_info(user_id) {
|
||||||
|
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||||
|
auto user = *discord.GetUser(ID);
|
||||||
|
|
||||||
|
Abaddon::Get().GetDiscordClient().FetchUserProfile(user_id, sigc::mem_fun(*this, &ProfileWindow::OnFetchProfile));
|
||||||
|
|
||||||
|
set_name("user-profile");
|
||||||
|
set_default_size(450, 375);
|
||||||
|
set_title(user.Username + "#" + user.Discriminator);
|
||||||
|
set_position(Gtk::WIN_POS_CENTER);
|
||||||
|
get_style_context()->add_class("app-window");
|
||||||
|
get_style_context()->add_class("app-popup");
|
||||||
|
get_style_context()->add_class("user-profile-window");
|
||||||
|
m_main.get_style_context()->add_class("profile-main-container");
|
||||||
|
m_avatar.get_style_context()->add_class("profile-avatar");
|
||||||
|
m_username.get_style_context()->add_class("profile-username");
|
||||||
|
m_switcher.get_style_context()->add_class("profile-switcher");
|
||||||
|
m_stack.get_style_context()->add_class("profile-stack");
|
||||||
|
m_badges.get_style_context()->add_class("profile-badges");
|
||||||
|
|
||||||
|
m_scroll.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
|
||||||
|
m_scroll.set_vexpand(true);
|
||||||
|
m_scroll.set_propagate_natural_height(true);
|
||||||
|
|
||||||
|
if (user.HasAvatar())
|
||||||
|
AddPointerCursor(m_avatar_ev);
|
||||||
|
m_avatar_ev.signal_button_press_event().connect([this, user](GdkEventButton *event) -> bool {
|
||||||
|
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY)
|
||||||
|
if (user.HasAnimatedAvatar())
|
||||||
|
LaunchBrowser(user.GetAvatarURL("gif", "512"));
|
||||||
|
else
|
||||||
|
LaunchBrowser(user.GetAvatarURL("png", "512"));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto &img = Abaddon::Get().GetImageManager();
|
||||||
|
m_avatar.property_pixbuf() = img.GetPlaceholder(64);
|
||||||
|
if (user.HasAvatar()) {
|
||||||
|
auto icon_cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||||
|
set_icon(pb);
|
||||||
|
};
|
||||||
|
img.LoadFromURL(user.GetAvatarURL("png", "64"), sigc::track_obj(icon_cb, *this));
|
||||||
|
|
||||||
|
if (user.HasAnimatedAvatar()) {
|
||||||
|
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
|
||||||
|
m_avatar.property_pixbuf_animation() = pb;
|
||||||
|
};
|
||||||
|
img.LoadAnimationFromURL(user.GetAvatarURL("gif", "64"), 64, 64, sigc::track_obj(cb, *this));
|
||||||
|
} else {
|
||||||
|
auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||||
|
m_avatar.property_pixbuf() = pb->scale_simple(64, 64, Gdk::INTERP_BILINEAR);
|
||||||
|
};
|
||||||
|
img.LoadFromURL(user.GetAvatarURL("png", "64"), sigc::track_obj(cb, *this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_username.set_markup(user.GetEscapedString());
|
||||||
|
|
||||||
|
m_switcher.set_stack(m_stack);
|
||||||
|
m_switcher.set_halign(Gtk::ALIGN_START);
|
||||||
|
m_switcher.set_hexpand(true);
|
||||||
|
|
||||||
|
m_stack.add(m_pane_info, "info", "User Info");
|
||||||
|
|
||||||
|
m_badges.set_valign(Gtk::ALIGN_CENTER);
|
||||||
|
m_badges_scroll.set_hexpand(true);
|
||||||
|
m_badges_scroll.set_propagate_natural_width(true);
|
||||||
|
m_badges_scroll.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_NEVER);
|
||||||
|
|
||||||
|
m_upper.set_halign(Gtk::ALIGN_START);
|
||||||
|
m_avatar.set_halign(Gtk::ALIGN_START);
|
||||||
|
m_username.set_halign(Gtk::ALIGN_START);
|
||||||
|
m_avatar_ev.add(m_avatar);
|
||||||
|
m_upper.add(m_avatar_ev);
|
||||||
|
m_upper.add(m_username);
|
||||||
|
m_badges_scroll.add(m_badges);
|
||||||
|
m_upper.add(m_badges_scroll);
|
||||||
|
m_main.add(m_upper);
|
||||||
|
m_main.add(m_switcher);
|
||||||
|
m_scroll.add(m_stack);
|
||||||
|
m_main.add(m_scroll);
|
||||||
|
add(m_main);
|
||||||
|
show_all_children();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileWindow::on_hide() {
|
||||||
|
Gtk::Window::on_hide();
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileWindow::OnFetchProfile(const UserProfileData &data) {
|
||||||
|
m_pane_info.SetConnections(data.ConnectedAccounts);
|
||||||
|
|
||||||
|
for (auto child : m_badges.get_children())
|
||||||
|
delete child;
|
||||||
|
|
||||||
|
if (!data.User.PublicFlags.has_value()) return;
|
||||||
|
const auto x = *data.User.PublicFlags;
|
||||||
|
for (uint64_t i = 1; i <= UserData::MaxFlag; i <<= 1) {
|
||||||
|
if (!(x & i)) continue;
|
||||||
|
const std::string name = UserData::GetFlagName(i);
|
||||||
|
if (name == "unknown") continue;
|
||||||
|
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||||
|
try {
|
||||||
|
pixbuf = Gdk::Pixbuf::create_from_file("./res/" + name + ".png", 24, 24);
|
||||||
|
} catch (const Glib::Exception &e) {
|
||||||
|
pixbuf = Abaddon::Get().GetImageManager().GetPlaceholder(24);
|
||||||
|
}
|
||||||
|
if (!pixbuf) continue;
|
||||||
|
auto *image = Gtk::manage(new Gtk::Image(pixbuf));
|
||||||
|
image->get_style_context()->add_class("profile-badge");
|
||||||
|
image->set_tooltip_text(UserData::GetFlagReadableName(i));
|
||||||
|
image->show();
|
||||||
|
m_badges.add(*image);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
windows/profilewindow.hpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <gtkmm.h>
|
||||||
|
#include "../discord/snowflake.hpp"
|
||||||
|
#include "profile/userinfopane.hpp"
|
||||||
|
|
||||||
|
class ProfileWindow : public Gtk::Window {
|
||||||
|
public:
|
||||||
|
ProfileWindow(Snowflake user_id);
|
||||||
|
|
||||||
|
void on_hide() override;
|
||||||
|
|
||||||
|
Snowflake ID;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void OnFetchProfile(const UserProfileData &data);
|
||||||
|
|
||||||
|
Gtk::Box m_main;
|
||||||
|
Gtk::Box m_upper;
|
||||||
|
Gtk::Box m_badges;
|
||||||
|
Gtk::ScrolledWindow m_badges_scroll;
|
||||||
|
Gtk::EventBox m_avatar_ev;
|
||||||
|
Gtk::Image m_avatar;
|
||||||
|
Gtk::Label m_username;
|
||||||
|
Gtk::ScrolledWindow m_scroll;
|
||||||
|
Gtk::Stack m_stack;
|
||||||
|
Gtk::StackSwitcher m_switcher;
|
||||||
|
|
||||||
|
ProfileUserInfoPane m_pane_info;
|
||||||
|
};
|
||||||