summaryrefslogtreecommitdiff
path: root/src/discord
diff options
context:
space:
mode:
Diffstat (limited to 'src/discord')
-rw-r--r--src/discord/channel.cpp4
-rw-r--r--src/discord/chatsubmitparams.hpp2
-rw-r--r--src/discord/discord.cpp121
-rw-r--r--src/discord/discord.hpp6
-rw-r--r--src/discord/errors.hpp1
-rw-r--r--src/discord/message.cpp4
-rw-r--r--src/discord/message.hpp36
-rw-r--r--src/discord/objects.cpp9
-rw-r--r--src/discord/objects.hpp44
-rw-r--r--src/discord/snowflake.cpp4
-rw-r--r--src/discord/snowflake.hpp1
-rw-r--r--src/discord/store.cpp78
-rw-r--r--src/discord/user.cpp107
-rw-r--r--src/discord/user.hpp28
-rw-r--r--src/discord/voiceclient.cpp4
-rw-r--r--src/discord/websocket.cpp5
16 files changed, 320 insertions, 134 deletions
diff --git a/src/discord/channel.cpp b/src/discord/channel.cpp
index 4b1d909..a7102ad 100644
--- a/src/discord/channel.cpp
+++ b/src/discord/channel.cpp
@@ -147,13 +147,13 @@ std::string ChannelData::GetRecipientsDisplay() const {
const auto recipients = GetDMRecipients();
if (Type == ChannelType::DM && !recipients.empty()) {
- return recipients[0].Username;
+ return recipients[0].GetDisplayName();
}
Glib::ustring r;
for (size_t i = 0; i < recipients.size(); i++) {
const auto &recipient = recipients[i];
- r += recipient.Username;
+ r += recipient.GetDisplayName();
if (i < recipients.size() - 1) {
r += ", ";
}
diff --git a/src/discord/chatsubmitparams.hpp b/src/discord/chatsubmitparams.hpp
index 6199634..e195189 100644
--- a/src/discord/chatsubmitparams.hpp
+++ b/src/discord/chatsubmitparams.hpp
@@ -17,8 +17,10 @@ struct ChatSubmitParams {
std::string Filename;
};
+ bool Silent = false;
Snowflake ChannelID;
Snowflake InReplyToID;
+ Snowflake EditingID;
Glib::ustring Message;
std::vector<Attachment> Attachments;
};
diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp
index 2a25a26..084e19f 100644
--- a/src/discord/discord.cpp
+++ b/src/discord/discord.cpp
@@ -461,8 +461,13 @@ void DiscordClient::SendChatMessageNoAttachments(const ChatSubmitParams &params,
CreateMessageObject obj;
obj.Content = params.Message;
obj.Nonce = nonce;
- if (params.InReplyToID.IsValid())
+ if (params.Silent) {
+ obj.Flags |= MessageFlags::SUPPRESS_NOTIFICATIONS;
+ }
+
+ if (params.InReplyToID.IsValid()) {
obj.MessageReference.emplace().MessageID = params.InReplyToID;
+ }
m_http.MakePOST("/channels/" + std::to_string(params.ChannelID) + "/messages",
nlohmann::json(obj).dump(),
@@ -494,8 +499,13 @@ void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams &params, c
CreateMessageObject obj;
obj.Content = params.Message;
obj.Nonce = nonce;
- if (params.InReplyToID.IsValid())
+ if (params.Silent) {
+ obj.Flags |= MessageFlags::SUPPRESS_NOTIFICATIONS;
+ }
+
+ if (params.InReplyToID.IsValid()) {
obj.MessageReference.emplace().MessageID = params.InReplyToID;
+ }
auto req = m_http.CreateRequest(http::REQUEST_POST, "/channels/" + std::to_string(params.ChannelID) + "/messages");
m_progress_cb_timer.start();
@@ -545,10 +555,11 @@ void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams &params, c
}
void DiscordClient::SendChatMessage(const ChatSubmitParams &params, const sigc::slot<void(DiscordError)> &callback) {
- if (params.Attachments.empty())
+ if (params.Attachments.empty()) {
SendChatMessageNoAttachments(params, callback);
- else
+ } else {
SendChatMessageAttachments(params, callback);
+ }
}
void DiscordClient::DeleteMessage(Snowflake channel_id, Snowflake id) {
@@ -1186,6 +1197,27 @@ void DiscordClient::AcceptVerificationGate(Snowflake guild_id, VerificationGateI
});
}
+void DiscordClient::RemoteAuthLogin(const std::string &ticket, const sigc::slot<void(std::optional<std::string>, DiscordError code)> &callback) {
+ http::request req(http::REQUEST_POST, "https://discord.com/api/v9/users/@me/remote-auth/login");
+ req.set_header("Content-Type", "application/json");
+ req.set_user_agent(Abaddon::Get().GetSettings().UserAgent);
+ req.set_body("{\"ticket\":\"" + ticket + "\"}");
+ m_http.Execute(std::move(req), [this, callback](const http::response_type &r) {
+ if (CheckCode(r)) {
+ callback(nlohmann::json::parse(r.text).at("encrypted_token").get<std::string>(), DiscordError::NONE);
+ } else {
+ try {
+ const auto j = nlohmann::json::parse(r.text);
+ if (j.contains("captcha_service")) {
+ callback(std::nullopt, DiscordError::CAPTCHA_REQUIRED);
+ return;
+ }
+ } catch (...) {}
+ callback(std::nullopt, GetCodeFromResponse(r));
+ }
+ });
+}
+
#ifdef WITH_VOICE
void DiscordClient::ConnectToVoice(Snowflake channel_id) {
auto channel = GetChannel(channel_id);
@@ -1306,6 +1338,17 @@ int DiscordClient::GetUnreadStateForChannel(Snowflake id) const noexcept {
return iter->second;
}
+int DiscordClient::GetUnreadChannelsCountForCategory(Snowflake id) const noexcept {
+ int result = 0;
+ for (Snowflake channel_id : m_store.GetChannelIDsWithParentID(id)) {
+ if (IsChannelMuted(channel_id)) continue;
+ const auto iter = m_unread.find(channel_id);
+ if (iter == m_unread.end()) continue;
+ result += 1;
+ }
+ return result;
+}
+
bool DiscordClient::GetUnreadStateForGuild(Snowflake id, int &total_mentions) const noexcept {
total_mentions = 0;
bool has_any_unread = false;
@@ -1579,6 +1622,9 @@ void DiscordClient::HandleGatewayMessage(std::string str) {
case GatewayEvent::VOICE_SERVER_UPDATE: {
HandleGatewayVoiceServerUpdate(m);
} break;
+ case GatewayEvent::CALL_CREATE: {
+ HandleGatewayCallCreate(m);
+ } break;
#endif
}
} break;
@@ -1801,9 +1847,12 @@ void DiscordClient::HandleGatewayPresenceUpdate(const GatewayMessage &msg) {
void DiscordClient::HandleGatewayChannelDelete(const GatewayMessage &msg) {
const auto id = msg.Data.at("id").get<Snowflake>();
const auto channel = GetChannel(id);
- auto it = m_guild_to_channels.find(*channel->GuildID);
- if (it != m_guild_to_channels.end())
- it->second.erase(id);
+ if (channel.has_value() && channel->GuildID.has_value()) {
+ auto it = m_guild_to_channels.find(*channel->GuildID);
+ if (it != m_guild_to_channels.end()) {
+ it->second.erase(id);
+ }
+ }
m_store.ClearChannel(id);
m_signal_channel_delete.emit(id);
m_signal_channel_accessibility_changed.emit(id, false);
@@ -2253,8 +2302,39 @@ void DiscordClient::HandleGatewayGuildMembersChunk(const GatewayMessage &msg) {
void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) {
spdlog::get("discord")->trace("VOICE_STATE_UPDATE");
- VoiceState data = msg.Data;
+ CheckVoiceState(msg.Data);
+}
+void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) {
+ spdlog::get("discord")->trace("VOICE_SERVER_UPDATE");
+
+ VoiceServerUpdateData data = msg.Data;
+ spdlog::get("discord")->debug("Voice server endpoint: {}", data.Endpoint);
+ spdlog::get("discord")->debug("Voice token: {}", data.Token);
+ m_voice.SetEndpoint(data.Endpoint);
+ m_voice.SetToken(data.Token);
+ if (data.GuildID.has_value()) {
+ m_voice.SetServerID(*data.GuildID);
+ } else if (data.ChannelID.has_value()) {
+ m_voice.SetServerID(*data.ChannelID);
+ } else {
+ spdlog::get("discord")->error("No guild or channel ID in voice server?");
+ }
+ m_voice.SetUserID(m_user_data.ID);
+ m_voice.Start();
+}
+
+void DiscordClient::HandleGatewayCallCreate(const GatewayMessage &msg) {
+ CallCreateData data = msg.Data;
+
+ spdlog::get("discord")->debug("CALL_CREATE: {}", data.ChannelID);
+
+ for (const auto &state : data.VoiceStates) {
+ CheckVoiceState(state);
+ }
+}
+
+void DiscordClient::CheckVoiceState(const VoiceState &data) {
if (data.UserID == m_user_data.ID) {
spdlog::get("discord")->debug("Voice session ID: {}", data.SessionID);
m_voice.SetSessionID(data.SessionID);
@@ -2292,25 +2372,6 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) {
}
}
}
-
-void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) {
- spdlog::get("discord")->trace("VOICE_SERVER_UPDATE");
-
- VoiceServerUpdateData data = msg.Data;
- spdlog::get("discord")->debug("Voice server endpoint: {}", data.Endpoint);
- spdlog::get("discord")->debug("Voice token: {}", data.Token);
- m_voice.SetEndpoint(data.Endpoint);
- m_voice.SetToken(data.Token);
- if (data.GuildID.has_value()) {
- m_voice.SetServerID(*data.GuildID);
- } else if (data.ChannelID.has_value()) {
- m_voice.SetServerID(*data.ChannelID);
- } else {
- spdlog::get("discord")->error("No guild or channel ID in voice server?");
- }
- m_voice.SetUserID(m_user_data.ID);
- m_voice.Start();
-}
#endif
void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) {
@@ -2556,7 +2617,7 @@ void DiscordClient::HeartbeatThread() {
void DiscordClient::SendIdentify() {
IdentifyMessage msg;
msg.Token = m_token;
- msg.Capabilities = 509; // no idea what this is
+ msg.Capabilities = 4605; // bit 12 is necessary for CALL_CREATE... apparently? need to get this in sync with official client
msg.Properties.OS = "Windows";
msg.Properties.Browser = "Chrome";
msg.Properties.Device = "";
@@ -2575,9 +2636,6 @@ void DiscordClient::SendIdentify() {
msg.Presence.Since = 0;
msg.Presence.IsAFK = false;
msg.DoesSupportCompression = false;
- msg.ClientState.HighestLastMessageID = "0";
- msg.ClientState.ReadStateVersion = 0;
- msg.ClientState.UserGuildSettingsVersion = -1;
SetSuperPropertiesFromIdentity(msg);
const bool b = m_websocket.GetPrintMessages();
m_websocket.SetPrintMessages(false);
@@ -2893,6 +2951,7 @@ void DiscordClient::LoadEventMap() {
m_event_map["GUILD_MEMBERS_CHUNK"] = GatewayEvent::GUILD_MEMBERS_CHUNK;
m_event_map["VOICE_STATE_UPDATE"] = GatewayEvent::VOICE_STATE_UPDATE;
m_event_map["VOICE_SERVER_UPDATE"] = GatewayEvent::VOICE_SERVER_UPDATE;
+ m_event_map["CALL_CREATE"] = GatewayEvent::CALL_CREATE;
}
DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() {
diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp
index 7f7518c..eba190a 100644
--- a/src/discord/discord.hpp
+++ b/src/discord/discord.hpp
@@ -184,6 +184,8 @@ public:
void GetVerificationGateInfo(Snowflake guild_id, const sigc::slot<void(std::optional<VerificationGateInfoObject>)> &callback);
void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, const sigc::slot<void(DiscordError code)> &callback);
+ void RemoteAuthLogin(const std::string &ticket, const sigc::slot<void(std::optional<std::string>, DiscordError code)> &callback);
+
#ifdef WITH_VOICE
void ConnectToVoice(Snowflake channel_id);
void DisconnectFromVoice();
@@ -214,6 +216,7 @@ public:
bool IsChannelMuted(Snowflake id) const noexcept;
bool IsGuildMuted(Snowflake id) const noexcept;
int GetUnreadStateForChannel(Snowflake id) const noexcept;
+ int GetUnreadChannelsCountForCategory(Snowflake id) const noexcept;
bool GetUnreadStateForGuild(Snowflake id, int &total_mentions) const noexcept;
int GetUnreadDMsCount() const;
@@ -291,6 +294,9 @@ private:
#ifdef WITH_VOICE
void HandleGatewayVoiceStateUpdate(const GatewayMessage &msg);
void HandleGatewayVoiceServerUpdate(const GatewayMessage &msg);
+ void HandleGatewayCallCreate(const GatewayMessage &msg);
+
+ void CheckVoiceState(const VoiceState &data);
#endif
void HeartbeatThread();
diff --git a/src/discord/errors.hpp b/src/discord/errors.hpp
index 4579563..8ead4f2 100644
--- a/src/discord/errors.hpp
+++ b/src/discord/errors.hpp
@@ -11,6 +11,7 @@ enum class DiscordError {
RELATIONSHIP_ALREADY_FRIENDS = 80007,
NONE = -1,
+ CAPTCHA_REQUIRED = -2,
};
constexpr const char *GetDiscordErrorDisplayString(DiscordError error) {
diff --git a/src/discord/message.cpp b/src/discord/message.cpp
index f5d8ad8..9adc7e6 100644
--- a/src/discord/message.cpp
+++ b/src/discord/message.cpp
@@ -265,6 +265,10 @@ bool Message::IsEdited() const {
return m_edited;
}
+bool Message::IsEditable() const noexcept {
+ return (Abaddon::Get().GetDiscordClient().GetUserData().ID == Author.ID) && !IsDeleted() && !IsPending && (Type == MessageType::DEFAULT || Type == MessageType::INLINE_REPLY);
+}
+
bool Message::DoesMentionEveryoneOrUser(Snowflake id) const noexcept {
if (DoesMentionEveryone) return true;
return std::any_of(Mentions.begin(), Mentions.end(), [id](const UserData &user) {
diff --git a/src/discord/message.hpp b/src/discord/message.hpp
index df2cb38..fb11604 100644
--- a/src/discord/message.hpp
+++ b/src/discord/message.hpp
@@ -8,6 +8,7 @@
#include "emoji.hpp"
#include "member.hpp"
#include "interactions.hpp"
+#include "misc/bitwise.hpp"
enum class MessageType {
DEFAULT = 0, // yep
@@ -35,14 +36,23 @@ enum class MessageType {
enum class MessageFlags {
NONE = 0,
- CROSSPOSTED = 1 << 0, // this message has been published to subscribed channels (via Channel Following)
- IS_CROSSPOST = 1 << 1, // this message originated from a message in another channel (via Channel Following)
- SUPPRESS_EMBEDS = 1 << 2, // do not include any embeds when serializing this message
- SOURCE_MESSAGE_DELETE = 1 << 3, // the source message for this crosspost has been deleted (via Channel Following)
- URGENT = 1 << 4, // this message came from the urgent message system
- HAS_THREAD = 1 << 5, // this message has an associated thread, with the same id as the message
- EPHEMERAL = 1 << 6, // this message is only visible to the user who invoked the Interaction
- LOADING = 1 << 7, // this message is an Interaction Response and the bot is "thinking"
+ CROSSPOSTED = 1 << 0, // this message has been published to subscribed channels (via Channel Following)
+ IS_CROSSPOST = 1 << 1, // this message originated from a message in another channel (via Channel Following)
+ SUPPRESS_EMBEDS = 1 << 2, // do not include any embeds when serializing this message
+ SOURCE_MESSAGE_DELETE = 1 << 3, // the source message for this crosspost has been deleted (via Channel Following)
+ URGENT = 1 << 4, // this message came from the urgent message system
+ HAS_THREAD = 1 << 5, // this message has an associated thread, with the same id as the message
+ EPHEMERAL = 1 << 6, // this message is only visible to the user who invoked the Interaction
+ LOADING = 1 << 7, // this message is an Interaction Response and the bot is "thinking"
+ FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8, // this message failed to mention some roles and add their members to the thread
+ SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10, //
+ SUPPRESS_NOTIFICATIONS = 1 << 12, // this message will not trigger push and desktop notifications
+ IS_VOICE_MESSAGE = 1 << 13, // this message is a voice message
+};
+
+template<>
+struct Bitwise<MessageFlags> {
+ static const bool enable = true;
};
struct EmbedFooterData {
@@ -209,11 +219,13 @@ struct Message {
void SetDeleted();
void SetEdited();
- [[nodiscard]] bool IsDeleted() const;
- [[nodiscard]] bool IsEdited() const;
+ bool IsDeleted() const;
+ bool IsEdited() const;
+
+ bool IsEditable() const noexcept;
- [[nodiscard]] bool DoesMentionEveryoneOrUser(Snowflake id) const noexcept;
- [[nodiscard]] bool DoesMention(Snowflake id) const noexcept;
+ bool DoesMentionEveryoneOrUser(Snowflake id) const noexcept;
+ bool DoesMention(Snowflake id) const noexcept;
private:
bool m_deleted = false;
diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp
index 86264cc..cb0a685 100644
--- a/src/discord/objects.cpp
+++ b/src/discord/objects.cpp
@@ -251,7 +251,7 @@ void from_json(const nlohmann::json &j, SupplementalMergedPresencesData &m) {
void from_json(const nlohmann::json &j, SupplementalGuildEntry &m) {
JS_D("id", m.ID);
- JS_D("voice_states", m.VoiceStates);
+ JS_ON("voice_states", m.VoiceStates);
}
void from_json(const nlohmann::json &j, ReadySupplementalData &m) {
@@ -308,6 +308,7 @@ void to_json(nlohmann::json &j, const HeartbeatMessage &m) {
void to_json(nlohmann::json &j, const CreateMessageObject &m) {
j["content"] = m.Content;
+ j["flags"] = m.Flags;
JS_IF("message_reference", m.MessageReference);
JS_IF("nonce", m.Nonce);
}
@@ -463,6 +464,7 @@ void from_json(const nlohmann::json &j, UserProfileData &m) {
JS_D("mutual_guilds", m.MutualGuilds);
JS_ON("premium_guild_since", m.PremiumGuildSince);
JS_ON("premium_since", m.PremiumSince);
+ JS_ON("legacy_username", m.LegacyUsername);
JS_D("user", m.User);
}
@@ -686,6 +688,11 @@ void from_json(const nlohmann::json &j, VoiceServerUpdateData &m) {
JS_ON("guild_id", m.GuildID);
JS_ON("channel_id", m.ChannelID);
}
+
+void from_json(const nlohmann::json &j, CallCreateData &m) {
+ JS_D("channel_id", m.ChannelID);
+ JS_ON("voice_states", m.VoiceStates);
+}
#endif
void from_json(const nlohmann::json &j, VoiceState &m) {
diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp
index 3ad4037..305ac65 100644
--- a/src/discord/objects.hpp
+++ b/src/discord/objects.hpp
@@ -102,6 +102,7 @@ enum class GatewayEvent : int {
GUILD_MEMBERS_CHUNK,
VOICE_STATE_UPDATE,
VOICE_SERVER_UPDATE,
+ CALL_CREATE,
};
enum class GatewayCloseCode : uint16_t {
@@ -427,6 +428,7 @@ struct HeartbeatMessage : GatewayMessage {
struct CreateMessageObject {
std::string Content;
+ MessageFlags Flags = MessageFlags::NONE;
std::optional<MessageReferenceData> MessageReference;
std::optional<std::string> Nonce;
@@ -617,6 +619,7 @@ struct UserProfileData {
std::vector<MutualGuildData> MutualGuilds;
std::optional<std::string> PremiumGuildSince; // null
std::optional<std::string> PremiumSince; // null
+ std::optional<std::string> LegacyUsername; // null
UserData User;
friend void from_json(const nlohmann::json &j, UserProfileData &m);
@@ -886,6 +889,23 @@ struct GuildMembersChunkData {
friend void from_json(const nlohmann::json &j, GuildMembersChunkData &m);
};
+struct VoiceState {
+ std::optional<Snowflake> ChannelID;
+ bool IsDeafened;
+ bool IsMuted;
+ std::optional<Snowflake> GuildID;
+ std::optional<GuildMember> Member;
+ bool IsSelfDeafened;
+ bool IsSelfMuted;
+ bool IsSelfVideo;
+ bool IsSelfStream = false;
+ std::string SessionID;
+ bool IsSuppressed;
+ Snowflake UserID;
+
+ friend void from_json(const nlohmann::json &j, VoiceState &m);
+};
+
#ifdef WITH_VOICE
struct VoiceStateUpdateMessage {
std::optional<Snowflake> GuildID;
@@ -906,21 +926,15 @@ struct VoiceServerUpdateData {
friend void from_json(const nlohmann::json &j, VoiceServerUpdateData &m);
};
-#endif
-struct VoiceState {
- std::optional<Snowflake> ChannelID;
- bool IsDeafened;
- bool IsMuted;
- std::optional<Snowflake> GuildID;
- std::optional<GuildMember> Member;
- bool IsSelfDeafened;
- bool IsSelfMuted;
- bool IsSelfVideo;
- bool IsSelfStream = false;
- std::string SessionID;
- bool IsSuppressed;
- Snowflake UserID;
+struct CallCreateData {
+ Snowflake ChannelID;
+ std::vector<VoiceState> VoiceStates;
+ // Snowflake MessageID;
+ // std::string Region;
+ // std::vector<?> Ringing;
+ // std::vector<?> EmbeddedActivities;
- friend void from_json(const nlohmann::json &j, VoiceState &m);
+ friend void from_json(const nlohmann::json &j, CallCreateData &m);
};
+#endif
diff --git a/src/discord/snowflake.cpp b/src/discord/snowflake.cpp
index 15dacae..680d4da 100644
--- a/src/discord/snowflake.cpp
+++ b/src/discord/snowflake.cpp
@@ -60,6 +60,10 @@ Glib::ustring Snowflake::GetLocalTimestamp() const {
return tmp.data();
}
+uint64_t Snowflake::GetUnixMilliseconds() const noexcept {
+ return (m_num >> 22) + DiscordEpochSeconds * 1000;
+}
+
void from_json(const nlohmann::json &j, Snowflake &s) {
if (j.is_string()) {
std::string tmp;
diff --git a/src/discord/snowflake.hpp b/src/discord/snowflake.hpp
index 2ced46b..68cb5ea 100644
--- a/src/discord/snowflake.hpp
+++ b/src/discord/snowflake.hpp
@@ -16,6 +16,7 @@ struct Snowflake {
[[nodiscard]] bool IsValid() const;
[[nodiscard]] Glib::ustring GetLocalTimestamp() const;
+ [[nodiscard]] uint64_t GetUnixMilliseconds() const noexcept;
bool operator==(const Snowflake &s) const noexcept {
return m_num == s.m_num;
diff --git a/src/discord/store.cpp b/src/discord/store.cpp
index 41b2069..7ee4d87 100644
--- a/src/discord/store.cpp
+++ b/src/discord/store.cpp
@@ -438,6 +438,7 @@ void Store::SetUser(Snowflake id, const UserData &user) {
s->Bind(7, user.IsMFAEnabled);
s->Bind(8, user.PremiumType);
s->Bind(9, user.PublicFlags);
+ s->Bind(10, user.GlobalName);
if (!s->Insert())
fprintf(stderr, "user insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
@@ -558,8 +559,9 @@ std::vector<Message> Store::GetMessagesBefore(Snowflake channel_id, Snowflake me
for (auto &msg : msgs) {
if (msg.MessageReference.has_value() && msg.MessageReference->MessageID.has_value()) {
auto ref = GetMessage(*msg.MessageReference->MessageID);
- if (ref.has_value())
+ if (ref.has_value()) {
msg.ReferencedMessage = std::make_shared<Message>(std::move(*ref));
+ }
}
}
@@ -1109,6 +1111,7 @@ std::optional<UserData> Store::GetUser(Snowflake id) const {
s->Get(6, r.IsMFAEnabled);
s->Get(7, r.PremiumType);
s->Get(8, r.PublicFlags);
+ s->Get(9, r.GlobalName);
s->Reset();
@@ -1233,7 +1236,8 @@ bool Store::CreateTables() {
system BOOL,
mfa BOOL,
premium INTEGER,
- pubflags INTEGER
+ pubflags INTEGER,
+ global_name TEXT
)
)";
@@ -1797,7 +1801,7 @@ bool Store::CreateStatements() {
m_stmt_set_user = std::make_unique<Statement>(m_db, R"(
REPLACE INTO users VALUES (
- ?, ?, ?, ?, ?, ?, ?, ?, ?
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
)");
if (!m_stmt_set_user->OK()) {
@@ -2057,26 +2061,22 @@ bool Store::CreateStatements() {
message_interactions.name,
message_interactions.type,
message_interactions.user_id,
- attachments.id,
- attachments.filename,
- attachments.size,
- attachments.url,
- attachments.proxy,
- attachments.height,
- attachments.width,
- message_references.message
+ message_references.message,
+ message_references.channel,
+ message_references.guild,
+ COUNT(attachments.id)
FROM messages
LEFT OUTER JOIN
message_interactions
ON messages.id = message_interactions.message_id
LEFT OUTER JOIN
- attachments
- ON messages.id = attachments.message
- LEFT OUTER JOIN
message_references
ON messages.id = message_references.id
+ LEFT OUTER JOIN
+ attachments
+ ON messages.id = attachments.message
WHERE channel_id = ? AND pending = 0 AND messages.id < ? ORDER BY id DESC LIMIT ?
- ) ORDER BY id ASC
+ ) WHERE id IS NOT NULL ORDER BY id ASC
)");
if (!m_stmt_get_messages_before->OK()) {
fprintf(stderr, "failed to prepare get messages before statement: %s\n", m_db.ErrStr());
@@ -2084,32 +2084,28 @@ bool Store::CreateStatements() {
}
m_stmt_get_pins = std::make_unique<Statement>(m_db, R"(
- SELECT messages.*,
- message_interactions.interaction_id,
- message_interactions.name,
- message_interactions.type,
- message_interactions.user_id,
- attachments.id,
- attachments.filename,
- attachments.size,
- attachments.url,
- attachments.proxy,
- attachments.height,
- attachments.width,
- message_references.message,
- message_references.channel,
- message_references.guild
- FROM messages
- LEFT OUTER JOIN
- message_interactions
- ON messages.id = message_interactions.message_id
- LEFT OUTER JOIN
- attachments
- ON messages.id = attachments.message
- LEFT OUTER JOIN
- message_references
- ON messages.id = message_references.id
- WHERE channel_id = ? AND pinned = 1 ORDER BY id ASC
+ SELECT * FROM (
+ SELECT messages.*,
+ message_interactions.interaction_id,
+ message_interactions.name,
+ message_interactions.type,
+ message_interactions.user_id,
+ message_references.message,
+ message_references.channel,
+ message_references.guild,
+ COUNT(attachments.id)
+ FROM messages
+ LEFT OUTER JOIN
+ message_interactions
+ ON messages.id = message_interactions.message_id
+ LEFT OUTER JOIN
+ message_references
+ ON messages.id = message_references.id
+ LEFT OUTER JOIN
+ attachments
+ ON messages.id = attachments.message
+ WHERE channel_id = ? AND pinned = 1 ORDER BY id ASC
+ ) WHERE id IS NOT NULL
)");
if (!m_stmt_get_pins->OK()) {
fprintf(stderr, "failed to prepare get pins statement: %s\n", m_db.ErrStr());
diff --git a/src/discord/user.cpp b/src/discord/user.cpp
index 2ee7361..45d70e8 100644
--- a/src/discord/user.cpp
+++ b/src/discord/user.cpp
@@ -1,5 +1,9 @@
#include "user.hpp"
+bool UserData::IsPomelo() const noexcept {
+ return Discriminator.size() == 1 && Discriminator[0] == '0';
+}
+
bool UserData::IsABot() const noexcept {
return IsBot.has_value() && *IsBot;
}
@@ -18,49 +22,54 @@ bool UserData::HasAnimatedAvatar() const noexcept {
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] == '_')
+ if (member.has_value() && member->Avatar.has_value() && member->Avatar.value()[0] == 'a' && member->Avatar.value()[1] == '_') {
return true;
- else if (member.has_value() && !member->Avatar.has_value())
+ } else if (member.has_value() && !member->Avatar.has_value()) {
return HasAnimatedAvatar();
+ }
return false;
}
bool UserData::HasAnimatedAvatar(const std::optional<Snowflake> &guild_id) const {
- if (guild_id.has_value())
+ if (guild_id.has_value()) {
return HasAnimatedAvatar(*guild_id);
- else
- return HasAnimatedAvatar();
+ }
+
+ return HasAnimatedAvatar();
}
std::string UserData::GetAvatarURL(Snowflake guild_id, const 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 (ext == "gif" && !(member->Avatar.value()[0] == 'a' && member->Avatar.value()[1] == '_'))
+ 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 {
- return GetAvatarURL(ext, size);
}
+ return GetAvatarURL(ext, size);
}
std::string UserData::GetAvatarURL(const std::optional<Snowflake> &guild_id, const std::string &ext, std::string size) const {
- if (guild_id.has_value())
+ if (guild_id.has_value()) {
return GetAvatarURL(*guild_id, ext, size);
- else
- return GetAvatarURL(ext, size);
+ }
+ return GetAvatarURL(ext, size);
}
std::string UserData::GetAvatarURL(const std::string &ext, std::string size) const {
- if (HasAvatar())
+ if (HasAvatar()) {
return "https://cdn.discordapp.com/avatars/" + std::to_string(ID) + "/" + Avatar + "." + ext + "?size=" + size;
- else
- return GetDefaultAvatarURL();
+ }
+ return GetDefaultAvatarURL();
}
std::string UserData::GetDefaultAvatarURL() const {
+ if (IsPomelo()) {
+ return "https://cdn.discordapp.com/embed/avatars/" + std::to_string((static_cast<uint64_t>(ID) >> 22) % 6) + ".png";
+ }
return "https://cdn.discordapp.com/embed/avatars/" + std::to_string(std::stoul(Discriminator) % 5) + ".png"; // size isn't respected by the cdn
}
@@ -72,18 +81,75 @@ std::string UserData::GetMention() const {
return "<@" + std::to_string(ID) + ">";
}
-std::string UserData::GetEscapedName() const {
- return Glib::Markup::escape_text(Username);
+std::string UserData::GetDisplayName() const {
+ if (IsPomelo() && GlobalName.has_value()) {
+ return *GlobalName;
+ }
+
+ return Username;
+}
+
+std::string UserData::GetDisplayName(Snowflake guild_id) const {
+ const auto member = Abaddon::Get().GetDiscordClient().GetMember(ID, guild_id);
+ if (member.has_value() && !member->Nickname.empty()) {
+ return member->Nickname;
+ }
+ return GetDisplayName();
}
-std::string UserData::GetEscapedBoldName() const {
- return "<b>" + Glib::Markup::escape_text(Username) + "</b>";
+std::string UserData::GetDisplayName(const std::optional<Snowflake> &guild_id) const {
+ if (guild_id.has_value()) {
+ return GetDisplayName(*guild_id);
+ }
+ return GetDisplayName();
}
-std::string UserData::GetEscapedString() const {
+std::string UserData::GetDisplayNameEscaped() const {
+ return Glib::Markup::escape_text(GetDisplayName());
+}
+
+std::string UserData::GetDisplayNameEscaped(Snowflake guild_id) const {
+ return Glib::Markup::escape_text(GetDisplayName(guild_id));
+}
+
+std::string UserData::GetDisplayNameEscapedBold() const {
+ return "<b>" + Glib::Markup::escape_text(GetDisplayName()) + "</b>";
+}
+
+std::string UserData::GetDisplayNameEscapedBold(Snowflake guild_id) const {
+ return "<b>" + Glib::Markup::escape_text(GetDisplayName(guild_id)) + "</b>";
+}
+
+std::string UserData::GetUsername() const {
+ if (IsPomelo()) {
+ return Username;
+ }
+
+ return Username + "#" + Discriminator;
+}
+
+std::string UserData::GetUsernameEscaped() const {
+ if (IsPomelo()) {
+ return Glib::Markup::escape_text(Username);
+ }
+
return Glib::Markup::escape_text(Username) + "#" + Discriminator;
}
+std::string UserData::GetUsernameEscapedBold() const {
+ if (IsPomelo()) {
+ return "<b>" + Glib::Markup::escape_text(Username) + "</b>";
+ }
+ return "<b>" + Glib::Markup::escape_text(Username) + "</b>#" + Discriminator;
+}
+
+std::string UserData::GetUsernameEscapedBoldAt() const {
+ if (IsPomelo()) {
+ return "<b>@" + Glib::Markup::escape_text(Username) + "</b>";
+ }
+ return "<b>@" + Glib::Markup::escape_text(Username) + "</b>#" + Discriminator;
+}
+
void from_json(const nlohmann::json &j, UserData &m) {
JS_D("id", m.ID);
JS_D("username", m.Username);
@@ -104,6 +170,7 @@ void from_json(const nlohmann::json &j, UserData &m) {
JS_ON("phone", m.Phone);
JS_ON("bio", m.Bio);
JS_ON("banner", m.BannerHash);
+ JS_ON("global_name", m.GlobalName);
}
void to_json(nlohmann::json &j, const UserData &m) {
@@ -127,6 +194,7 @@ void to_json(nlohmann::json &j, const UserData &m) {
JS_IF("mobile", m.IsMobile);
JS_IF("nsfw_allowed", m.IsNSFWAllowed);
JS_IF("phone", m.Phone);
+ JS_IF("global_name", m.GlobalName);
}
void UserData::update_from_json(const nlohmann::json &j) {
@@ -146,6 +214,7 @@ void UserData::update_from_json(const nlohmann::json &j) {
JS_RD("mobile", IsMobile);
JS_RD("nsfw_allowed", IsNSFWAllowed);
JS_RD("phone", Phone);
+ JS_RD("global_name", GlobalName);
}
const char *UserData::GetFlagName(uint64_t flag) {
diff --git a/src/discord/user.hpp b/src/discord/user.hpp
index 1b9d517..8b2a2c4 100644
--- a/src/discord/user.hpp
+++ b/src/discord/user.hpp
@@ -25,6 +25,11 @@ struct UserData {
VerifiedBot = 1 << 16,
EarlyVerifiedBotDeveloper = 1 << 17,
CertifiedModerator = 1 << 18,
+ BotHTTPInteractions = 1 << 19,
+ Spammer = 1 << 20,
+ DisablePremium = 1 << 21,
+ ActiveDeveloper = 1 << 22,
+ Quarantined = 1ULL << 44,
MaxFlag_PlusOne,
MaxFlag = MaxFlag_PlusOne - 1,
@@ -37,6 +42,7 @@ struct UserData {
std::string Username;
std::string Discriminator;
std::string Avatar; // null
+ std::optional<std::string> GlobalName;
std::optional<bool> IsBot;
std::optional<bool> IsSystem;
std::optional<bool> IsMFAEnabled;
@@ -60,6 +66,7 @@ struct UserData {
friend void to_json(nlohmann::json &j, const UserData &m);
void update_from_json(const nlohmann::json &j);
+ [[nodiscard]] bool IsPomelo() const noexcept;
[[nodiscard]] bool IsABot() const noexcept;
[[nodiscard]] bool IsDeleted() const;
[[nodiscard]] bool HasAvatar() const;
@@ -72,14 +79,15 @@ struct UserData {
[[nodiscard]] std::string GetDefaultAvatarURL() const;
[[nodiscard]] Snowflake GetHoistedRole(Snowflake guild_id, bool with_color = false) const;
[[nodiscard]] std::string GetMention() const;
- [[nodiscard]] std::string GetEscapedName() const;
- [[nodiscard]] std::string GetEscapedBoldName() const;
- [[nodiscard]] std::string GetEscapedString() const;
- template<bool with_at>
- [[nodiscard]] inline std::string GetEscapedBoldString() const {
- if constexpr (with_at)
- return "<b>@" + Glib::Markup::escape_text(Username) + "</b>#" + Discriminator;
- else
- return "<b>" + Glib::Markup::escape_text(Username) + "</b>#" + Discriminator;
- }
+ [[nodiscard]] std::string GetDisplayName() const;
+ [[nodiscard]] std::string GetDisplayName(Snowflake guild_id) const;
+ [[nodiscard]] std::string GetDisplayName(const std::optional<Snowflake> &guild_id) const;
+ [[nodiscard]] std::string GetDisplayNameEscaped() const;
+ [[nodiscard]] std::string GetDisplayNameEscaped(Snowflake guild_id) const;
+ [[nodiscard]] std::string GetDisplayNameEscapedBold() const;
+ [[nodiscard]] std::string GetDisplayNameEscapedBold(Snowflake guild_id) const;
+ [[nodiscard]] std::string GetUsername() const;
+ [[nodiscard]] std::string GetUsernameEscaped() const;
+ [[nodiscard]] std::string GetUsernameEscapedBold() const;
+ [[nodiscard]] std::string GetUsernameEscapedBoldAt() const;
};
diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp
index 524d930..e9814b6 100644
--- a/src/discord/voiceclient.cpp
+++ b/src/discord/voiceclient.cpp
@@ -112,8 +112,8 @@ void UDPSocket::ReadThread() {
sockaddr_in from;
socklen_t addrlen = sizeof(from);
- tv.tv_sec = 0;
- tv.tv_usec = 1000000;
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
fd_set read_fds;
FD_ZERO(&read_fds);
diff --git a/src/discord/websocket.cpp b/src/discord/websocket.cpp
index f886e69..cdc4db1 100644
--- a/src/discord/websocket.cpp
+++ b/src/discord/websocket.cpp
@@ -26,7 +26,7 @@ void Websocket::StartConnection(const std::string &url) {
m_websocket->disableAutomaticReconnection();
m_websocket->setUrl(url);
m_websocket->setOnMessageCallback([this](auto &&msg) { OnMessage(std::forward<decltype(msg)>(msg)); });
- m_websocket->setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent } }); // idk if this actually works
+ m_websocket->setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent }, { "Origin", "https://discord.com" } }); // idk if this actually works
m_websocket->start();
}
@@ -81,6 +81,9 @@ void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) {
case ix::WebSocketMessageType::Message: {
m_signal_message.emit(msg->str);
} break;
+ case ix::WebSocketMessageType::Error: {
+ m_log->error("Websocket error: Status: {} Reason: {}", msg->errorInfo.http_status, msg->errorInfo.reason);
+ } break;
default:
break;
}