Restructure source and resource files (#46)

importantly, res is now res/res and css is now res/css
This commit is contained in:
Dylam De La Torre
2021-11-23 05:21:56 +01:00
committed by GitHub
parent d88079000a
commit a51a54bc59
216 changed files with 21 additions and 33 deletions

116
src/discord/activity.cpp Normal file
View File

@@ -0,0 +1,116 @@
#include "activity.hpp"
void from_json(const nlohmann::json &j, ActivityTimestamps &m) {
JS_O("start", m.Start);
JS_O("end", m.End);
}
void to_json(nlohmann::json &j, const ActivityTimestamps &m) {
JS_IF("start", m.Start);
JS_IF("end", m.End);
}
void from_json(const nlohmann::json &j, ActivityEmoji &m) {
JS_D("name", m.Name);
JS_O("id", m.ID);
JS_O("animated", m.IsAnimated);
}
void to_json(nlohmann::json &j, const ActivityEmoji &m) {
j["name"] = m.Name;
if (m.ID.has_value())
j["id"] = *m.ID;
if (m.IsAnimated.has_value())
j["animated"] = *m.IsAnimated;
}
void from_json(const nlohmann::json &j, ActivityParty &m) {
JS_O("id", m.ID);
JS_O("size", m.Size);
}
void to_json(nlohmann::json &j, const ActivityParty &m) {
JS_IF("id", m.ID);
JS_IF("size", m.Size);
}
void from_json(const nlohmann::json &j, ActivityAssets &m) {
JS_O("large_image", m.LargeImage);
JS_O("large_text", m.LargeText);
JS_O("small_image", m.SmallImage);
JS_O("small_text", m.SmallText);
}
void to_json(nlohmann::json &j, const ActivityAssets &m) {
JS_IF("large_image", m.LargeImage);
JS_IF("large_text", m.LargeText);
JS_IF("small_image", m.SmallImage);
JS_IF("small_text", m.SmallText);
}
void from_json(const nlohmann::json &j, ActivitySecrets &m) {
JS_O("join", m.Join);
JS_O("spectate", m.Spectate);
JS_O("match", m.Match);
}
void to_json(nlohmann::json &j, const ActivitySecrets &m) {
JS_IF("join", m.Join);
JS_IF("spectate", m.Spectate);
JS_IF("match", m.Match);
}
void from_json(const nlohmann::json &j, ActivityData &m) {
JS_D("name", m.Name);
JS_D("type", m.Type);
JS_ON("url", m.URL);
JS_D("created_at", m.CreatedAt);
JS_O("timestamps", m.Timestamps);
JS_O("application_id", m.ApplicationID);
JS_ON("details", m.Details);
JS_ON("state", m.State);
JS_ON("emoji", m.Emoji);
JS_ON("party", m.Party);
JS_O("assets", m.Assets);
JS_O("secrets", m.Secrets);
JS_O("instance", m.IsInstance);
JS_O("flags", m.Flags);
}
void to_json(nlohmann::json &j, const ActivityData &m) {
if (m.Type == ActivityType::Custom) {
j["name"] = "Custom Status";
j["state"] = m.Name;
} else {
j["name"] = m.Name;
JS_IF("state", m.State);
}
j["type"] = m.Type;
JS_IF("url", m.URL);
JS_IF("created_at", m.CreatedAt);
JS_IF("timestamps", m.Timestamps);
JS_IF("application_id", m.ApplicationID);
JS_IF("details", m.Details);
JS_IF("emoji", m.Emoji);
JS_IF("party", m.Party);
JS_IF("assets", m.Assets);
JS_IF("secrets", m.Secrets);
JS_IF("instance", m.IsInstance);
JS_IF("flags", m.Flags);
}
void from_json(const nlohmann::json &j, PresenceData &m) {
JS_N("activities", m.Activities);
JS_D("status", m.Status);
}
void to_json(nlohmann::json &j, const PresenceData &m) {
j["activities"] = m.Activities;
j["status"] = m.Status;
JS_IF("afk", m.IsAFK);
if (m.Since.has_value())
j["since"] = *m.Since;
else
j["since"] = 0;
}

137
src/discord/activity.hpp Normal file
View File

@@ -0,0 +1,137 @@
#pragma once
#include <string>
#include <optional>
#include "util.hpp"
#include "json.hpp"
#include "snowflake.hpp"
enum class PresenceStatus : uint8_t {
Online,
Offline,
Idle,
DND,
};
constexpr inline const char *GetPresenceString(PresenceStatus s) {
switch (s) {
case PresenceStatus::Online:
return "online";
case PresenceStatus::Offline:
return "offline";
case PresenceStatus::Idle:
return "idle";
case PresenceStatus::DND:
return "dnd";
}
return "";
}
constexpr inline const char* GetPresenceDisplayString(PresenceStatus s) {
switch (s) {
case PresenceStatus::Online:
return "Online";
case PresenceStatus::Offline:
return "Offline";
case PresenceStatus::Idle:
return "Away";
case PresenceStatus::DND:
return "Do Not Disturb";
}
return "";
}
enum class ActivityType : int {
Game = 0,
Streaming = 1,
Listening = 2,
Watching = 3, // not documented
Custom = 4,
Competing = 5,
};
enum class ActivityFlags {
INSTANCE = (1 << 0),
JOIN = (1 << 1),
SPECTATE = (1 << 2),
JOIN_REQUEST = (1 << 3),
SYNC = (1 << 4),
PLAY = (1 << 5),
};
template<>
struct Bitwise<ActivityFlags> {
static const bool enable = true;
};
struct ActivityTimestamps {
std::optional<int> Start;
std::optional<int> End;
friend void from_json(const nlohmann::json &j, ActivityTimestamps &m);
friend void to_json(nlohmann::json &j, const ActivityTimestamps &m);
};
struct ActivityEmoji {
std::string Name;
std::optional<Snowflake> ID;
std::optional<bool> IsAnimated;
friend void from_json(const nlohmann::json &j, ActivityEmoji &m);
friend void to_json(nlohmann::json &j, const ActivityEmoji &m);
};
struct ActivityParty {
std::optional<std::string> ID;
std::optional<std::array<int, 2>> Size;
friend void from_json(const nlohmann::json &j, ActivityParty &m);
friend void to_json(nlohmann::json &j, const ActivityParty &m);
};
struct ActivityAssets {
std::optional<std::string> LargeImage;
std::optional<std::string> LargeText;
std::optional<std::string> SmallImage;
std::optional<std::string> SmallText;
friend void from_json(const nlohmann::json &j, ActivityAssets &m);
friend void to_json(nlohmann::json &j, const ActivityAssets &m);
};
struct ActivitySecrets {
std::optional<std::string> Join;
std::optional<std::string> Spectate;
std::optional<std::string> Match;
friend void from_json(const nlohmann::json &j, ActivitySecrets &m);
friend void to_json(nlohmann::json &j, const ActivitySecrets &m);
};
struct ActivityData {
std::string Name; //
ActivityType Type; //
std::optional<std::string> URL; // null
std::optional<uint64_t> CreatedAt; //
std::optional<ActivityTimestamps> Timestamps; //
std::optional<Snowflake> ApplicationID; //
std::optional<std::string> Details; // null
std::optional<std::string> State; // null
std::optional<ActivityEmoji> Emoji; // null
std::optional<ActivityParty> Party; //
std::optional<ActivityAssets> Assets; //
std::optional<ActivitySecrets> Secrets; //
std::optional<bool> IsInstance; //
std::optional<ActivityFlags> Flags; //
friend void from_json(const nlohmann::json &j, ActivityData &m);
friend void to_json(nlohmann::json &j, const ActivityData &m);
};
struct PresenceData {
std::vector<ActivityData> Activities; // null (but never sent as such)
std::string Status;
std::optional<bool> IsAFK;
std::optional<int> Since;
friend void from_json(const nlohmann::json &j, PresenceData &m);
friend void to_json(nlohmann::json &j, const PresenceData &m);
};

34
src/discord/auditlog.cpp Normal file
View File

@@ -0,0 +1,34 @@
#include "auditlog.hpp"
void from_json(const nlohmann::json &j, AuditLogChange &m) {
JS_D("key", m.Key);
JS_O("old_value", m.OldValue);
JS_O("new_value", m.NewValue);
}
void from_json(const nlohmann::json &j, AuditLogOptions &m) {
JS_O("delete_member_days", m.DeleteMemberDays);
JS_O("members_removed", m.MembersRemoved);
JS_O("channel_id", m.ChannelID);
JS_O("message_id", m.MessageID);
JS_O("count", m.Count);
JS_O("id", m.ID);
JS_O("type", m.Type);
JS_O("role_name", m.RoleName);
}
void from_json(const nlohmann::json &j, AuditLogEntry &m) {
JS_N("target_id", m.TargetID);
JS_O("changes", m.Changes);
JS_N("user_id", m.UserID);
JS_D("id", m.ID);
JS_D("action_type", m.Type);
JS_O("options", m.Options);
JS_O("reason", m.Reason);
}
void from_json(const nlohmann::json &j, AuditLogData &m) {
JS_D("audit_log_entries", m.Entries);
JS_D("users", m.Users);
JS_D("webhooks", m.Webhooks);
}

120
src/discord/auditlog.hpp Normal file
View File

@@ -0,0 +1,120 @@
#pragma once
#include "snowflake.hpp"
#include "user.hpp"
#include "json.hpp"
#include "webhook.hpp"
enum class AuditLogActionType {
GUILD_UPDATE = 1,
CHANNEL_CREATE = 10,
CHANNEL_UPDATE = 11,
CHANNEL_DELETE = 12,
CHANNEL_OVERWRITE_CREATE = 13,
CHANNEL_OVERWRITE_UPDATE = 14,
CHANNEL_OVERWRITE_DELETE = 15,
MEMBER_KICK = 20,
MEMBER_PRUNE = 21,
MEMBER_BAN_ADD = 22,
MEMBER_BAN_REMOVE = 23,
MEMBER_UPDATE = 24,
MEMBER_ROLE_UPDATE = 25,
MEMBER_MOVE = 26,
MEMBER_DISCONNECT = 27,
BOT_ADD = 28,
ROLE_CREATE = 30,
ROLE_UPDATE = 31,
ROLE_DELETE = 32,
INVITE_CREATE = 40,
INVITE_UPDATE = 41,
INVITE_DELETE = 42,
WEBHOOK_CREATE = 50,
WEBHOOK_UPDATE = 51,
WEBHOOK_DELETE = 52,
EMOJI_CREATE = 60,
EMOJI_UPDATE = 61,
EMOJI_DELETE = 62,
MESSAGE_DELETE = 72,
MESSAGE_BULK_DELETE = 73,
MESSAGE_PIN = 74,
MESSAGE_UNPIN = 75,
INTEGRATION_CREATE = 80,
INTEGRATION_UPDATE = 81,
INTEGRATION_DELETE = 82,
STAGE_INSTANCE_CREATE = 83,
STAGE_INSTANCE_UPDATE = 84,
STAGE_INSTANCE_DELETE = 85,
STICKER_CREATE = 90,
STICKER_UPDATE = 91,
STICKER_DELETE = 92,
THREAD_CREATE = 110,
THREAD_UPDATE = 111,
THREAD_DELETE = 112,
};
struct AuditLogChange {
std::string Key;
std::optional<nlohmann::json> OldValue;
std::optional<nlohmann::json> NewValue;
friend void from_json(const nlohmann::json &j, AuditLogChange &m);
};
struct AuditLogOptions {
std::optional<std::string> DeleteMemberDays; // MEMBER_PRUNE
std::optional<std::string> MembersRemoved; // MEMBER_PRUNE
std::optional<Snowflake> ChannelID; // MEMBER_MOVE, MESSAGE_PIN, MESSAGE_UNPIN, MESSAGE_DELETE
std::optional<Snowflake> MessageID; // MESSAGE_PIN, MESSAGE_UNPIN,
std::optional<std::string> Count; // MESSAGE_DELETE, MESSAGE_BULK_DELETE, MEMBER_DISCONNECT, MEMBER_MOVE
std::optional<Snowflake> ID; // CHANNEL_OVERWRITE_CREATE, CHANNEL_OVERWRITE_UPDATE, CHANNEL_OVERWRITE_DELETE
std::optional<std::string> Type; // CHANNEL_OVERWRITE_CREATE, CHANNEL_OVERWRITE_UPDATE, CHANNEL_OVERWRITE_DELETE
std::optional<std::string> RoleName; // CHANNEL_OVERWRITE_CREATE, CHANNEL_OVERWRITE_UPDATE, CHANNEL_OVERWRITE_DELETE
friend void from_json(const nlohmann::json &j, AuditLogOptions &m);
};
struct AuditLogEntry {
Snowflake ID;
std::string TargetID; // null
std::optional<Snowflake> UserID;
AuditLogActionType Type;
std::optional<std::string> Reason;
std::optional<std::vector<AuditLogChange>> Changes;
std::optional<AuditLogOptions> Options;
friend void from_json(const nlohmann::json &j, AuditLogEntry &m);
template<typename T>
std::optional<T> GetOldFromKey(const std::string &key) const;
template<typename T>
std::optional<T> GetNewFromKey(const std::string &key) const;
};
struct AuditLogData {
std::vector<AuditLogEntry> Entries;
std::vector<UserData> Users;
std::vector<WebhookData> Webhooks;
// std::vector<IntegrationData> Integrations;
friend void from_json(const nlohmann::json &j, AuditLogData &m);
};
template<typename T>
inline std::optional<T> AuditLogEntry::GetOldFromKey(const std::string &key) const {
if (!Changes.has_value()) return std::nullopt;
for (const auto &change : *Changes)
if (change.Key == key && change.OldValue.has_value())
return change.OldValue->get<T>();
return std::nullopt;
}
template<typename T>
inline std::optional<T> AuditLogEntry::GetNewFromKey(const std::string &key) const {
if (!Changes.has_value()) return std::nullopt;
for (const auto &change : *Changes)
if (change.Key == key && change.NewValue.has_value())
return change.NewValue->get<T>();
return std::nullopt;
}

6
src/discord/ban.cpp Normal file
View File

@@ -0,0 +1,6 @@
#include "ban.hpp"
void from_json(const nlohmann::json &j, BanData &m) {
JS_N("reason", m.Reason);
JS_D("user", m.User);
}

10
src/discord/ban.hpp Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include <string>
#include "user.hpp"
struct BanData {
std::string Reason; // null
UserData User; // access id
friend void from_json(const nlohmann::json &j, BanData &m);
};

97
src/discord/channel.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include "abaddon.hpp"
#include "channel.hpp"
void from_json(const nlohmann::json &j, ThreadMetadataData &m) {
JS_D("archived", m.IsArchived);
JS_D("auto_archive_duration", m.AutoArchiveDuration);
JS_D("archive_timestamp", m.ArchiveTimestamp);
JS_O("locked", m.IsLocked);
}
void from_json(const nlohmann::json &j, ThreadMemberObject &m) {
JS_O("id", m.ThreadID);
JS_O("user_id", m.UserID);
JS_D("join_timestamp", m.JoinTimestamp);
JS_D("flags", m.Flags);
}
void from_json(const nlohmann::json &j, ChannelData &m) {
JS_D("id", m.ID);
JS_D("type", m.Type);
JS_O("guild_id", m.GuildID);
JS_O("position", m.Position);
JS_O("permission_overwrites", m.PermissionOverwrites);
JS_ON("name", m.Name);
JS_ON("topic", m.Topic);
JS_O("nsfw", m.IsNSFW);
JS_ON("last_message_id", m.LastMessageID);
JS_O("bitrate", m.Bitrate);
JS_O("user_limit", m.UserLimit);
JS_O("rate_limit_per_user", m.RateLimitPerUser);
JS_O("recipients", m.Recipients);
JS_O("recipient_ids", m.RecipientIDs);
JS_ON("icon", m.Icon);
JS_O("owner_id", m.OwnerID);
JS_O("application_id", m.ApplicationID);
JS_ON("parent_id", m.ParentID);
JS_ON("last_pin_timestamp", m.LastPinTimestamp);
JS_O("thread_metadata", m.ThreadMetadata);
JS_O("member", m.ThreadMember);
}
void ChannelData::update_from_json(const nlohmann::json &j) {
JS_RD("type", Type);
JS_RD("guild_id", GuildID);
JS_RV("position", Position, -1);
JS_RD("permission_overwrites", PermissionOverwrites);
JS_RD("name", Name);
JS_RD("topic", Topic);
JS_RD("nsfw", IsNSFW);
JS_RD("last_message_id", LastMessageID);
JS_RD("bitrate", Bitrate);
JS_RD("user_limit", UserLimit);
JS_RD("rate_limit_per_user", RateLimitPerUser);
JS_RD("recipients", Recipients);
JS_RD("icon", Icon);
JS_RD("owner_id", OwnerID);
JS_RD("application_id", ApplicationID);
JS_RD("parent_id", ParentID);
JS_RD("last_pin_timestamp", LastPinTimestamp);
}
bool ChannelData::NSFW() const {
return IsNSFW.has_value() && *IsNSFW;
}
bool ChannelData::IsThread() const noexcept {
return Type == ChannelType::GUILD_PUBLIC_THREAD ||
Type == ChannelType::GUILD_PRIVATE_THREAD ||
Type == ChannelType::GUILD_NEWS_THREAD;
}
bool ChannelData::IsJoinedThread() const {
return Abaddon::Get().GetDiscordClient().IsThreadJoined(ID);
}
std::optional<PermissionOverwrite> ChannelData::GetOverwrite(Snowflake id) const {
return Abaddon::Get().GetDiscordClient().GetPermissionOverwrite(ID, id);
}
std::vector<UserData> ChannelData::GetDMRecipients() const {
const auto &discord = Abaddon::Get().GetDiscordClient();
if (Recipients.has_value())
return *Recipients;
if (RecipientIDs.has_value()) {
std::vector<UserData> ret;
for (const auto &id : *RecipientIDs) {
auto user = discord.GetUser(id);
if (user.has_value())
ret.push_back(std::move(*user));
}
return ret;
}
return std::vector<UserData>();
}

92
src/discord/channel.hpp Normal file
View File

@@ -0,0 +1,92 @@
#pragma once
#include "snowflake.hpp"
#include "json.hpp"
#include "user.hpp"
#include "permissions.hpp"
#include <string>
#include <vector>
enum class ChannelType : int {
GUILD_TEXT = 0,
DM = 1,
GUILD_VOICE = 2,
GROUP_DM = 3,
GUILD_CATEGORY = 4,
GUILD_NEWS = 5,
GUILD_STORE = 6,
/* 7 and 8 were used for LFG */
/* 9 was used for threads */
GUILD_NEWS_THREAD = 10,
GUILD_PUBLIC_THREAD = 11,
GUILD_PRIVATE_THREAD = 12,
GUILD_STAGE_VOICE = 13,
};
enum class StagePrivacy {
PUBLIC = 1,
GUILD_ONLY = 2,
};
constexpr const char *GetStagePrivacyDisplayString(StagePrivacy e) {
switch (e) {
case StagePrivacy::PUBLIC:
return "Public";
case StagePrivacy::GUILD_ONLY:
return "Guild Only";
default:
return "Unknown";
}
}
// should be moved somewhere?
struct ThreadMetadataData {
bool IsArchived;
int AutoArchiveDuration;
std::string ArchiveTimestamp;
std::optional<bool> IsLocked;
friend void from_json(const nlohmann::json &j, ThreadMetadataData &m);
};
struct ThreadMemberObject {
std::optional<Snowflake> ThreadID;
std::optional<Snowflake> UserID;
std::string JoinTimestamp;
int Flags;
friend void from_json(const nlohmann::json &j, ThreadMemberObject &m);
};
struct ChannelData {
Snowflake ID;
ChannelType Type;
std::optional<Snowflake> GuildID;
std::optional<int> Position;
std::optional<std::vector<PermissionOverwrite>> PermissionOverwrites; // shouldnt be accessed
std::optional<std::string> Name; // null for dm's
std::optional<std::string> Topic; // null
std::optional<bool> IsNSFW;
std::optional<Snowflake> LastMessageID; // null
std::optional<int> Bitrate;
std::optional<int> UserLimit;
std::optional<int> RateLimitPerUser;
std::optional<std::vector<UserData>> Recipients; // only access id
std::optional<std::vector<Snowflake>> RecipientIDs;
std::optional<std::string> Icon; // null
std::optional<Snowflake> OwnerID;
std::optional<Snowflake> ApplicationID;
std::optional<Snowflake> ParentID; // null
std::optional<std::string> LastPinTimestamp; // null
std::optional<ThreadMetadataData> ThreadMetadata;
std::optional<ThreadMemberObject> ThreadMember;
friend void from_json(const nlohmann::json &j, ChannelData &m);
void update_from_json(const nlohmann::json &j);
bool NSFW() const;
bool IsThread() const noexcept;
bool IsJoinedThread() const;
std::optional<PermissionOverwrite> GetOverwrite(Snowflake id) const;
std::vector<UserData> GetDMRecipients() const;
};

2310
src/discord/discord.cpp Normal file

File diff suppressed because it is too large Load Diff

449
src/discord/discord.hpp Normal file
View File

@@ -0,0 +1,449 @@
#pragma once
#include "websocket.hpp"
#include "httpclient.hpp"
#include "objects.hpp"
#include "store.hpp"
#include <sigc++/sigc++.h>
#include <nlohmann/json.hpp>
#include <thread>
#include <map>
#include <set>
#include <mutex>
#include <zlib.h>
#include <glibmm.h>
#include <queue>
#ifdef GetMessage
#undef GetMessage
#endif
class HeartbeatWaiter {
public:
template<class R, class P>
bool wait_for(std::chrono::duration<R, P> const &time) const {
std::unique_lock<std::mutex> lock(m);
return !cv.wait_for(lock, time, [&] { return terminate; });
}
void kill() {
std::unique_lock<std::mutex> lock(m);
terminate = true;
cv.notify_all();
}
void revive() {
std::unique_lock<std::mutex> lock(m);
terminate = false;
}
private:
mutable std::condition_variable cv;
mutable std::mutex m;
bool terminate = false;
};
class Abaddon;
class DiscordClient {
friend class Abaddon;
public:
DiscordClient(bool mem_store = false);
void Start();
void Stop();
bool IsStarted() const;
bool IsStoreValid() const;
std::unordered_set<Snowflake> GetGuilds() const;
const UserData &GetUserData() const;
const UserSettings &GetUserSettings() const;
std::vector<Snowflake> GetUserSortedGuilds() const;
std::vector<Message> GetMessagesForChannel(Snowflake id, size_t limit = 50) const;
std::vector<Message> GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit = 50) const;
std::set<Snowflake> GetPrivateChannels() const;
EPremiumType GetSelfPremiumType() const;
void FetchMessagesInChannel(Snowflake id, sigc::slot<void(const std::vector<Message> &)> cb);
void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, sigc::slot<void(const std::vector<Message> &)> cb);
std::optional<Message> GetMessage(Snowflake id) const;
std::optional<ChannelData> GetChannel(Snowflake id) const;
std::optional<EmojiData> GetEmoji(Snowflake id) const;
std::optional<PermissionOverwrite> GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const;
std::optional<UserData> GetUser(Snowflake id) const;
std::optional<RoleData> GetRole(Snowflake id) const;
std::optional<GuildData> GetGuild(Snowflake id) const;
std::optional<GuildMember> GetMember(Snowflake user_id, Snowflake guild_id) const;
std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
Snowflake GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color = false) const;
std::optional<RoleData> GetMemberHighestRole(Snowflake guild_id, Snowflake user_id) const;
std::set<Snowflake> GetUsersInGuild(Snowflake id) const;
std::set<Snowflake> GetChannelsInGuild(Snowflake id) const;
std::vector<Snowflake> GetUsersInThread(Snowflake id) const;
std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const;
void GetArchivedPublicThreads(Snowflake channel_id, sigc::slot<void(DiscordError, const ArchivedThreadsResponseData &)> callback);
bool IsThreadJoined(Snowflake thread_id) const;
bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const;
bool HasAnyChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const;
bool HasChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const;
Permission ComputePermissions(Snowflake member_id, Snowflake guild_id) const;
Permission ComputeOverwrites(Permission base, Snowflake member_id, Snowflake channel_id) const;
bool CanManageMember(Snowflake guild_id, Snowflake actor, Snowflake target) const; // kick, ban, edit nickname (cant think of a better name)
void ChatMessageCallback(std::string nonce, const http::response_type &response);
void SendChatMessage(const std::string &content, Snowflake channel);
void SendChatMessage(const std::string &content, Snowflake channel, Snowflake referenced_message);
void DeleteMessage(Snowflake channel_id, Snowflake id);
void EditMessage(Snowflake channel_id, Snowflake id, std::string content);
void SendLazyLoad(Snowflake id);
void SendThreadLazyLoad(Snowflake id);
void JoinGuild(std::string code);
void LeaveGuild(Snowflake id);
void KickUser(Snowflake user_id, Snowflake guild_id);
void BanUser(Snowflake user_id, Snowflake guild_id); // todo: reason, delete messages
void UpdateStatus(PresenceStatus status, bool is_afk);
void UpdateStatus(PresenceStatus status, bool is_afk, const ActivityData &obj);
void CreateDM(Snowflake user_id);
void CreateDM(Snowflake user_id, sigc::slot<void(DiscordError code, Snowflake channel_id)> callback);
void CloseDM(Snowflake channel_id);
std::optional<Snowflake> FindDM(Snowflake user_id); // wont find group dms
void AddReaction(Snowflake id, Glib::ustring param);
void RemoveReaction(Snowflake id, Glib::ustring param);
void SetGuildName(Snowflake id, const Glib::ustring &name);
void SetGuildName(Snowflake id, const Glib::ustring &name, sigc::slot<void(DiscordError code)> callback);
void SetGuildIcon(Snowflake id, const std::string &data);
void SetGuildIcon(Snowflake id, const std::string &data, sigc::slot<void(DiscordError code)> callback);
void UnbanUser(Snowflake guild_id, Snowflake user_id);
void UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot<void(DiscordError code)> callback);
void DeleteInvite(const std::string &code);
void DeleteInvite(const std::string &code, sigc::slot<void(DiscordError code)> callback);
void AddGroupDMRecipient(Snowflake channel_id, Snowflake user_id);
void RemoveGroupDMRecipient(Snowflake channel_id, Snowflake user_id);
void ModifyRolePermissions(Snowflake guild_id, Snowflake role_id, Permission permissions, sigc::slot<void(DiscordError code)> callback);
void ModifyRoleName(Snowflake guild_id, Snowflake role_id, const Glib::ustring &name, sigc::slot<void(DiscordError code)> callback);
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, uint32_t color, sigc::slot<void(DiscordError code)> callback);
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, Gdk::RGBA color, sigc::slot<void(DiscordError code)> callback);
void ModifyRolePosition(Snowflake guild_id, Snowflake role_id, int position, sigc::slot<void(DiscordError code)> callback);
void ModifyEmojiName(Snowflake guild_id, Snowflake emoji_id, const Glib::ustring &name, sigc::slot<void(DiscordError code)> callback);
void DeleteEmoji(Snowflake guild_id, Snowflake emoji_id, sigc::slot<void(DiscordError code)> callback);
std::optional<GuildApplicationData> GetGuildApplication(Snowflake guild_id) const;
void RemoveRelationship(Snowflake id, sigc::slot<void(DiscordError code)> callback);
void SendFriendRequest(const Glib::ustring &username, int discriminator, sigc::slot<void(DiscordError code)> callback);
void PutRelationship(Snowflake id, sigc::slot<void(DiscordError code)> callback); // send fr by id, accept incoming
void Pin(Snowflake channel_id, Snowflake message_id, sigc::slot<void(DiscordError code)> callback);
void Unpin(Snowflake channel_id, Snowflake message_id, sigc::slot<void(DiscordError code)> callback);
void LeaveThread(Snowflake channel_id, const std::string &location, sigc::slot<void(DiscordError code)> callback);
void ArchiveThread(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback);
void UnArchiveThread(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback);
bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const;
bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const;
// real client doesn't seem to use the single role endpoints so neither do we
template<typename Iter>
auto SetMemberRoles(Snowflake guild_id, Snowflake user_id, Iter begin, Iter end, sigc::slot<void(DiscordError code)> callback) {
ModifyGuildMemberObject obj;
obj.Roles = { begin, end };
m_http.MakePATCH("/guilds/" + std::to_string(guild_id) + "/members/" + std::to_string(user_id), nlohmann::json(obj).dump(), [this, callback](const http::response_type &response) {
if (CheckCode(response))
callback(DiscordError::NONE);
else
callback(GetCodeFromResponse(response));
});
}
// FetchGuildBans fetches all bans+reasons via api, this func fetches stored bans (so usually just GUILD_BAN_ADD data)
std::vector<BanData> GetBansInGuild(Snowflake guild_id);
void FetchGuildBan(Snowflake guild_id, Snowflake user_id, sigc::slot<void(BanData)> callback);
void FetchGuildBans(Snowflake guild_id, sigc::slot<void(std::vector<BanData>)> callback);
void FetchInvite(std::string code, sigc::slot<void(std::optional<InviteData>)> callback);
void FetchGuildInvites(Snowflake guild_id, sigc::slot<void(std::vector<InviteData>)> callback);
void FetchAuditLog(Snowflake guild_id, sigc::slot<void(AuditLogData)> callback);
void FetchGuildEmojis(Snowflake guild_id, sigc::slot<void(std::vector<EmojiData>)> 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(DiscordError code)> callback);
void FetchUserRelationships(Snowflake user_id, sigc::slot<void(std::vector<UserData>)> callback);
void FetchPinned(Snowflake id, sigc::slot<void(std::vector<Message>, DiscordError code)> callback);
bool IsVerificationRequired(Snowflake guild_id);
void GetVerificationGateInfo(Snowflake guild_id, sigc::slot<void(std::optional<VerificationGateInfoObject>)> callback);
void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, sigc::slot<void(DiscordError code)> callback);
void UpdateToken(std::string token);
void SetUserAgent(std::string agent);
PresenceStatus GetUserStatus(Snowflake id) const;
std::map<Snowflake, RelationshipType> GetRelationships() const;
std::set<Snowflake> GetRelationships(RelationshipType type) const;
std::optional<RelationshipType> GetRelationship(Snowflake id) const;
private:
static const constexpr int InflateChunkSize = 0x10000;
std::vector<uint8_t> m_compressed_buf;
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);
void HandleGatewayMessageRaw(std::string str);
void HandleGatewayMessage(std::string str);
void HandleGatewayHello(const GatewayMessage &msg);
void HandleGatewayReady(const GatewayMessage &msg);
void HandleGatewayMessageCreate(const GatewayMessage &msg);
void HandleGatewayMessageDelete(const GatewayMessage &msg);
void HandleGatewayMessageUpdate(const GatewayMessage &msg);
void HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg);
void HandleGatewayGuildCreate(const GatewayMessage &msg);
void HandleGatewayGuildDelete(const GatewayMessage &msg);
void HandleGatewayMessageDeleteBulk(const GatewayMessage &msg);
void HandleGatewayGuildMemberUpdate(const GatewayMessage &msg);
void HandleGatewayPresenceUpdate(const GatewayMessage &msg);
void HandleGatewayChannelDelete(const GatewayMessage &msg);
void HandleGatewayChannelUpdate(const GatewayMessage &msg);
void HandleGatewayChannelCreate(const GatewayMessage &msg);
void HandleGatewayGuildUpdate(const GatewayMessage &msg);
void HandleGatewayGuildRoleUpdate(const GatewayMessage &msg);
void HandleGatewayGuildRoleCreate(const GatewayMessage &msg);
void HandleGatewayGuildRoleDelete(const GatewayMessage &msg);
void HandleGatewayMessageReactionAdd(const GatewayMessage &msg);
void HandleGatewayMessageReactionRemove(const GatewayMessage &msg);
void HandleGatewayChannelRecipientAdd(const GatewayMessage &msg);
void HandleGatewayChannelRecipientRemove(const GatewayMessage &msg);
void HandleGatewayTypingStart(const GatewayMessage &msg);
void HandleGatewayGuildBanRemove(const GatewayMessage &msg);
void HandleGatewayGuildBanAdd(const GatewayMessage &msg);
void HandleGatewayInviteCreate(const GatewayMessage &msg);
void HandleGatewayInviteDelete(const GatewayMessage &msg);
void HandleGatewayUserNoteUpdate(const GatewayMessage &msg);
void HandleGatewayGuildEmojisUpdate(const GatewayMessage &msg);
void HandleGatewayGuildJoinRequestCreate(const GatewayMessage &msg);
void HandleGatewayGuildJoinRequestUpdate(const GatewayMessage &msg);
void HandleGatewayGuildJoinRequestDelete(const GatewayMessage &msg);
void HandleGatewayRelationshipRemove(const GatewayMessage &msg);
void HandleGatewayRelationshipAdd(const GatewayMessage &msg);
void HandleGatewayThreadCreate(const GatewayMessage &msg);
void HandleGatewayThreadDelete(const GatewayMessage &msg);
void HandleGatewayThreadListSync(const GatewayMessage &msg);
void HandleGatewayThreadMembersUpdate(const GatewayMessage &msg);
void HandleGatewayThreadMemberUpdate(const GatewayMessage &msg);
void HandleGatewayThreadUpdate(const GatewayMessage &msg);
void HandleGatewayThreadMemberListUpdate(const GatewayMessage &msg);
void HandleGatewayReadySupplemental(const GatewayMessage &msg);
void HandleGatewayReconnect(const GatewayMessage &msg);
void HandleGatewayInvalidSession(const GatewayMessage &msg);
void HeartbeatThread();
void SendIdentify();
void SendResume();
void HandleSocketOpen();
void HandleSocketClose(uint16_t code);
bool CheckCode(const http::response_type &r);
bool CheckCode(const http::response_type &r, int expected);
void StoreMessageData(Message &msg);
std::string m_token;
void AddUserToGuild(Snowflake user_id, Snowflake guild_id);
std::map<Snowflake, std::set<Snowflake>> m_guild_to_users;
std::map<Snowflake, std::set<Snowflake>> m_guild_to_channels;
std::map<Snowflake, GuildApplicationData> m_guild_join_requests;
std::map<Snowflake, PresenceStatus> m_user_to_status;
std::map<Snowflake, RelationshipType> m_user_relationships;
std::set<Snowflake> m_joined_threads;
std::map<Snowflake, std::vector<Snowflake>> m_thread_members;
UserData m_user_data;
UserSettings m_user_settings;
Store m_store;
HTTPClient m_http;
Websocket m_websocket;
std::atomic<bool> m_client_connected = false;
std::atomic<bool> m_ready_received = false;
bool m_client_started = false;
std::map<std::string, GatewayEvent> m_event_map;
void LoadEventMap();
std::thread m_heartbeat_thread;
std::atomic<int> m_last_sequence = -1;
std::atomic<int> m_heartbeat_msec = 0;
HeartbeatWaiter m_heartbeat_waiter;
std::atomic<bool> m_heartbeat_acked = true;
bool m_reconnecting = false; // reconnecting either to resume or reidentify
bool m_wants_resume = false; // reconnecting specifically to resume
std::string m_session_id;
mutable std::mutex m_msg_mutex;
Glib::Dispatcher m_msg_dispatch;
std::queue<std::string> m_msg_queue;
void MessageDispatch();
mutable std::mutex m_generic_mutex;
Glib::Dispatcher m_generic_dispatch;
std::queue<std::function<void()>> m_generic_queue;
std::set<Snowflake> m_channels_pinned_requested;
std::set<Snowflake> m_channels_lazy_loaded;
// signals
public:
typedef sigc::signal<void> type_signal_gateway_ready;
typedef sigc::signal<void, Message> type_signal_message_create;
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_message_delete;
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_message_update;
typedef sigc::signal<void, Snowflake> type_signal_guild_member_list_update;
typedef sigc::signal<void, GuildData> type_signal_guild_create;
typedef sigc::signal<void, Snowflake> type_signal_guild_delete;
typedef sigc::signal<void, Snowflake> type_signal_channel_delete;
typedef sigc::signal<void, Snowflake> type_signal_channel_update;
typedef sigc::signal<void, ChannelData> type_signal_channel_create;
typedef sigc::signal<void, Snowflake> type_signal_guild_update;
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_role_update; // guild id, role id
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_role_create; // guild id, role id
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_role_delete; // guild id, role id
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_reaction_add;
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_reaction_remove;
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_typing_start; // user id, channel id
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_guild_member_update; // guild id, user id
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_guild_ban_remove; // guild id, user id
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_guild_ban_add; // guild id, user id
typedef sigc::signal<void, InviteData> type_signal_invite_create;
typedef sigc::signal<void, InviteDeleteObject> type_signal_invite_delete;
typedef sigc::signal<void, UserData, PresenceStatus> type_signal_presence_update;
typedef sigc::signal<void, Snowflake, std::string> type_signal_note_update;
typedef sigc::signal<void, Snowflake, std::vector<EmojiData>> type_signal_guild_emojis_update; // guild id
typedef sigc::signal<void, GuildJoinRequestCreateData> type_signal_guild_join_request_create;
typedef sigc::signal<void, GuildJoinRequestUpdateData> type_signal_guild_join_request_update;
typedef sigc::signal<void, GuildJoinRequestDeleteData> type_signal_guild_join_request_delete;
typedef sigc::signal<void, Snowflake, RelationshipType> type_signal_relationship_remove;
typedef sigc::signal<void, RelationshipAddData> type_signal_relationship_add;
typedef sigc::signal<void, ChannelData> type_signal_thread_create;
typedef sigc::signal<void, ThreadDeleteData> type_signal_thread_delete;
typedef sigc::signal<void, ThreadListSyncData> type_signal_thread_list_sync;
typedef sigc::signal<void, ThreadMembersUpdateData> type_signal_thread_members_update;
typedef sigc::signal<void, ThreadUpdateData> type_signal_thread_update;
typedef sigc::signal<void, ThreadMemberListUpdateData> type_signal_thread_member_list_update;
// not discord dispatch events
typedef sigc::signal<void, Snowflake> type_signal_added_to_thread;
typedef sigc::signal<void, Snowflake> type_signal_removed_from_thread;
typedef sigc::signal<void, Message> type_signal_message_unpinned;
typedef sigc::signal<void, Message> type_signal_message_pinned;
typedef sigc::signal<void, Message> type_signal_message_sent;
typedef sigc::signal<void, std::string /* nonce */, float /* retry_after */> type_signal_message_send_fail; // retry after param will be 0 if it failed for a reason that isnt slowmode
typedef sigc::signal<void, bool, GatewayCloseCode> type_signal_disconnected; // bool true if reconnecting
typedef sigc::signal<void> type_signal_connected;
type_signal_gateway_ready signal_gateway_ready();
type_signal_message_create signal_message_create();
type_signal_message_delete signal_message_delete();
type_signal_message_update signal_message_update();
type_signal_guild_member_list_update signal_guild_member_list_update();
type_signal_guild_create signal_guild_create(); // structs are complete in this signal
type_signal_guild_delete signal_guild_delete();
type_signal_channel_delete signal_channel_delete();
type_signal_channel_update signal_channel_update();
type_signal_channel_create signal_channel_create();
type_signal_guild_update signal_guild_update();
type_signal_role_update signal_role_update();
type_signal_role_create signal_role_create();
type_signal_role_delete signal_role_delete();
type_signal_reaction_add signal_reaction_add();
type_signal_reaction_remove signal_reaction_remove();
type_signal_typing_start signal_typing_start();
type_signal_guild_member_update signal_guild_member_update();
type_signal_guild_ban_remove signal_guild_ban_remove();
type_signal_guild_ban_add signal_guild_ban_add();
type_signal_invite_create signal_invite_create();
type_signal_invite_delete signal_invite_delete(); // safe to assume guild id is set
type_signal_presence_update signal_presence_update();
type_signal_note_update signal_note_update();
type_signal_guild_emojis_update signal_guild_emojis_update();
type_signal_guild_join_request_create signal_guild_join_request_create();
type_signal_guild_join_request_update signal_guild_join_request_update();
type_signal_guild_join_request_delete signal_guild_join_request_delete();
type_signal_relationship_remove signal_relationship_remove();
type_signal_relationship_add signal_relationship_add();
type_signal_message_unpinned signal_message_unpinned();
type_signal_message_pinned signal_message_pinned();
type_signal_thread_create signal_thread_create();
type_signal_thread_delete signal_thread_delete();
type_signal_thread_list_sync signal_thread_list_sync();
type_signal_thread_members_update signal_thread_members_update();
type_signal_thread_update signal_thread_update();
type_signal_thread_member_list_update signal_thread_member_list_update();
type_signal_added_to_thread signal_added_to_thread();
type_signal_removed_from_thread signal_removed_from_thread();
type_signal_message_sent signal_message_sent();
type_signal_message_send_fail signal_message_send_fail();
type_signal_disconnected signal_disconnected();
type_signal_connected signal_connected();
protected:
type_signal_gateway_ready m_signal_gateway_ready;
type_signal_message_create m_signal_message_create;
type_signal_message_delete m_signal_message_delete;
type_signal_message_update m_signal_message_update;
type_signal_guild_member_list_update m_signal_guild_member_list_update;
type_signal_guild_create m_signal_guild_create;
type_signal_guild_delete m_signal_guild_delete;
type_signal_channel_delete m_signal_channel_delete;
type_signal_channel_update m_signal_channel_update;
type_signal_channel_create m_signal_channel_create;
type_signal_guild_update m_signal_guild_update;
type_signal_role_update m_signal_role_update;
type_signal_role_create m_signal_role_create;
type_signal_role_delete m_signal_role_delete;
type_signal_reaction_add m_signal_reaction_add;
type_signal_reaction_remove m_signal_reaction_remove;
type_signal_typing_start m_signal_typing_start;
type_signal_guild_member_update m_signal_guild_member_update;
type_signal_guild_ban_remove m_signal_guild_ban_remove;
type_signal_guild_ban_add m_signal_guild_ban_add;
type_signal_invite_create m_signal_invite_create;
type_signal_invite_delete m_signal_invite_delete;
type_signal_presence_update m_signal_presence_update;
type_signal_note_update m_signal_note_update;
type_signal_guild_emojis_update m_signal_guild_emojis_update;
type_signal_guild_join_request_create m_signal_guild_join_request_create;
type_signal_guild_join_request_update m_signal_guild_join_request_update;
type_signal_guild_join_request_delete m_signal_guild_join_request_delete;
type_signal_relationship_remove m_signal_relationship_remove;
type_signal_relationship_add m_signal_relationship_add;
type_signal_message_unpinned m_signal_message_unpinned;
type_signal_message_pinned m_signal_message_pinned;
type_signal_thread_create m_signal_thread_create;
type_signal_thread_delete m_signal_thread_delete;
type_signal_thread_list_sync m_signal_thread_list_sync;
type_signal_thread_members_update m_signal_thread_members_update;
type_signal_thread_update m_signal_thread_update;
type_signal_thread_member_list_update m_signal_thread_member_list_update;
type_signal_removed_from_thread m_signal_removed_from_thread;
type_signal_added_to_thread m_signal_added_to_thread;
type_signal_message_sent m_signal_message_sent;
type_signal_message_send_fail m_signal_message_send_fail;
type_signal_disconnected m_signal_disconnected;
type_signal_connected m_signal_connected;
};

51
src/discord/emoji.cpp Normal file
View File

@@ -0,0 +1,51 @@
#include "emoji.hpp"
void from_json(const nlohmann::json &j, EmojiData &m) {
JS_N("id", m.ID);
JS_N("name", m.Name);
JS_O("roles", m.Roles);
JS_O("user", m.Creator);
JS_O("require_colons", m.NeedsColons);
JS_O("managed", m.IsManaged);
JS_O("animated", m.IsAnimated);
JS_O("available", m.IsAvailable);
}
void to_json(nlohmann::json &j, const EmojiData &m) {
if (m.ID.IsValid())
j["id"] = m.ID;
else
j["id"] = nullptr;
if (m.Name != "")
j["name"] = m.Name;
else
j["name"] = nullptr;
JS_IF("roles", m.Roles);
JS_IF("user", m.Creator);
JS_IF("require_colons", m.NeedsColons);
JS_IF("managed", m.IsManaged);
JS_IF("animated", m.IsAnimated);
JS_IF("available", m.IsAvailable);
}
std::string EmojiData::GetURL(const char *ext, const char *size) const {
if (size != nullptr)
return "https://cdn.discordapp.com/emojis/" + std::to_string(ID) + "." + ext + "?size=" + size;
else
return "https://cdn.discordapp.com/emojis/" + std::to_string(ID) + "." + ext;
}
std::string EmojiData::URLFromID(const std::string &emoji_id, const char *ext, const char *size) {
if (size != nullptr)
return "https://cdn.discordapp.com/emojis/" + emoji_id + "." + ext + "?size=" + size;
else
return "https://cdn.discordapp.com/emojis/" + emoji_id + "." + ext;
}
std::string EmojiData::URLFromID(Snowflake emoji_id, const char *ext, const char *size) {
return URLFromID(std::to_string(emoji_id), ext, size);
}
std::string EmojiData::URLFromID(const Glib::ustring &emoji_id, const char *ext, const char *size) {
return URLFromID(emoji_id.raw(), ext, size);
}

25
src/discord/emoji.hpp Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include <string>
#include <vector>
#include "json.hpp"
#include "snowflake.hpp"
#include "user.hpp"
struct EmojiData {
Snowflake ID; // null
std::string Name; // null (in reactions)
std::optional<std::vector<Snowflake>> Roles;
std::optional<UserData> Creator; // only reliable to access ID
std::optional<bool> NeedsColons;
std::optional<bool> IsManaged;
std::optional<bool> IsAnimated;
std::optional<bool> IsAvailable;
friend void from_json(const nlohmann::json &j, EmojiData &m);
friend void to_json(nlohmann::json &j, const EmojiData &m);
std::string GetURL(const char *ext = "png", const char *size = nullptr) const;
static std::string URLFromID(const std::string &emoji_id, const char *ext = "png", const char *size = nullptr);
static std::string URLFromID(Snowflake emoji_id, const char *ext = "png", const char *size = nullptr);
static std::string URLFromID(const Glib::ustring &emoji_id, const char *ext = "png", const char *size = nullptr);
};

36
src/discord/errors.hpp Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
enum class DiscordError {
GENERIC = 0,
INVALID_FORM_BODY = 50035,
RELATIONSHIP_INCOMING_DISABLED = 80000,
RELATIONSHIP_INCOMING_BLOCKED = 80001,
RELATIONSHIP_INVALID_USER_BOT = 80002, // this is misspelled in discord's source lul
RELATIONSHIP_INVALID_SELF = 80003,
RELATIONSHIP_INVALID_DISCORD_TAG = 80004,
RELATIONSHIP_ALREADY_FRIENDS = 80007,
NONE = -1,
};
constexpr const char *GetDiscordErrorDisplayString(DiscordError error) {
switch (error) {
case DiscordError::INVALID_FORM_BODY:
return "Something's wrong with your input";
case DiscordError::RELATIONSHIP_INCOMING_DISABLED:
return "This user isn't accepting friend requests";
case DiscordError::RELATIONSHIP_INCOMING_BLOCKED:
return "You are blocked by this user";
case DiscordError::RELATIONSHIP_INVALID_USER_BOT:
return "You can't send a request to a bot";
case DiscordError::RELATIONSHIP_INVALID_SELF:
return "You can't send a request to yourself";
case DiscordError::RELATIONSHIP_INVALID_DISCORD_TAG:
return "No users with that tag exist";
case DiscordError::RELATIONSHIP_ALREADY_FRIENDS:
return "You are already friends with that user";
case DiscordError::GENERIC:
default:
return "An error occurred";
}
}

221
src/discord/guild.cpp Normal file
View File

@@ -0,0 +1,221 @@
#include "guild.hpp"
#include "abaddon.hpp"
void from_json(const nlohmann::json &j, GuildData &m) {
JS_D("id", m.ID);
if (j.contains("unavailable")) {
m.IsUnavailable = true;
return;
}
JS_D("name", m.Name);
JS_N("icon", m.Icon);
JS_N("splash", m.Splash);
JS_ON("discovery_splash", m.DiscoverySplash);
JS_O("owner", m.IsOwner);
JS_O("owner_id", m.OwnerID);
std::optional<std::string> tmp;
JS_O("permissions", tmp);
if (tmp.has_value())
m.Permissions = std::stoull(*tmp);
JS_O("region", m.VoiceRegion);
JS_ON("afk_channel_id", m.AFKChannelID);
JS_O("afk_timeout", m.AFKTimeout);
JS_O("embed_enabled", m.IsEmbedEnabled);
JS_ON("embed_channel_id", m.EmbedChannelID);
JS_O("verification_level", m.VerificationLevel);
JS_O("default_message_notifications", m.DefaultMessageNotifications);
JS_O("explicit_content_filter", m.ExplicitContentFilter);
JS_O("roles", m.Roles);
JS_O("emojis", m.Emojis);
JS_O("features", m.Features);
JS_O("mfa_level", m.MFALevel);
JS_ON("application_id", m.ApplicationID);
JS_O("widget_enabled", m.IsWidgetEnabled);
JS_ON("widget_channel_id", m.WidgetChannelID);
JS_ON("system_channel_id", m.SystemChannelID);
JS_O("system_channel_flags", m.SystemChannelFlags);
JS_ON("rules_channel_id", m.RulesChannelID);
JS_O("joined_at", m.JoinedAt);
JS_O("large", m.IsLarge);
JS_O("unavailable", m.IsUnavailable);
JS_O("member_count", m.MemberCount);
// JS_O("voice_states", m.VoiceStates);
// JS_O("members", m.Members);
JS_O("channels", m.Channels);
JS_O("threads", m.Threads);
// JS_O("presences", m.Presences);
JS_ON("max_presences", m.MaxPresences);
JS_O("max_members", m.MaxMembers);
JS_ON("vanity_url_code", m.VanityURL);
JS_ON("description", m.Description);
JS_ON("banner", m.BannerHash);
JS_O("premium_tier", m.PremiumTier);
JS_O("premium_subscription_count", m.PremiumSubscriptionCount);
JS_O("preferred_locale", m.PreferredLocale);
JS_ON("public_updates_channel_id", m.PublicUpdatesChannelID);
JS_O("max_video_channel_users", m.MaxVideoChannelUsers);
JS_O("approximate_member_count", tmp);
if (tmp.has_value())
m.ApproximateMemberCount = std::stol(*tmp);
JS_O("approximate_presence_count", tmp);
if (tmp.has_value())
m.ApproximatePresenceCount = std::stol(*tmp);
}
void GuildData::update_from_json(const nlohmann::json &j) {
if (j.contains("unavailable")) {
IsUnavailable = true;
return;
}
JS_RD("name", Name);
JS_RD("icon", Icon);
JS_RD("splash", Splash);
JS_RD("discovery_splash", DiscoverySplash);
JS_RD("owner", IsOwner);
JS_RD("owner_id", OwnerID);
std::string tmp;
JS_RD("permissions", tmp);
if (tmp != "")
Permissions = std::stoull(tmp);
JS_RD("region", VoiceRegion);
JS_RD("afk_channel_id", AFKChannelID);
JS_RD("afk_timeout", AFKTimeout);
JS_RD("embed_enabled", IsEmbedEnabled);
JS_RD("embed_channel_id", EmbedChannelID);
JS_RD("verification_level", VerificationLevel);
JS_RD("default_message_notifications", DefaultMessageNotifications);
JS_RD("explicit_content_filter", ExplicitContentFilter);
JS_RD("roles", Roles);
JS_RD("emojis", Emojis);
JS_RD("features", Features);
JS_RD("mfa_level", MFALevel);
JS_RD("application_id", ApplicationID);
JS_RD("widget_enabled", IsWidgetEnabled);
JS_RD("widget_channel_id", WidgetChannelID);
JS_RD("system_channel_id", SystemChannelID);
JS_RD("system_channel_flags", SystemChannelFlags);
JS_RD("rules_channel_id", RulesChannelID);
JS_RD("joined_at", JoinedAt);
JS_RD("large", IsLarge);
JS_RD("unavailable", IsUnavailable);
JS_RD("member_count", MemberCount);
// JS_O("voice_states", m.VoiceStates);
// JS_O("members", m.Members);
JS_RD("channels", Channels);
// JS_O("presences", m.Presences);
JS_RD("max_presences", MaxPresences);
JS_RD("max_members", MaxMembers);
JS_RD("vanity_url_code", VanityURL);
JS_RD("description", Description);
JS_RD("banner", BannerHash);
JS_RD("premium_tier", PremiumTier);
JS_RD("premium_subscription_count", PremiumSubscriptionCount);
JS_RD("preferred_locale", PreferredLocale);
JS_RD("public_updates_channel_id", PublicUpdatesChannelID);
JS_RD("max_video_channel_users", MaxVideoChannelUsers);
JS_RD("approximate_member_count", ApproximateMemberCount);
JS_RD("approximate_presence_count", ApproximatePresenceCount);
}
bool GuildData::HasFeature(const std::string &search_feature) {
if (!Features.has_value()) return false;
for (const auto &feature : *Features)
if (search_feature == feature)
return true;
return false;
}
bool GuildData::HasIcon() const {
return Icon != "";
}
bool GuildData::HasAnimatedIcon() const {
return HasIcon() && Icon[0] == 'a' && Icon[1] == '_';
}
std::string GuildData::GetIconURL(std::string ext, std::string size) const {
return "https://cdn.discordapp.com/icons/" + std::to_string(ID) + "/" + Icon + "." + ext + "?size=" + size;
}
std::vector<Snowflake> GuildData::GetSortedChannels(Snowflake ignore) const {
std::vector<Snowflake> ret;
const auto &discord = Abaddon::Get().GetDiscordClient();
auto channels = discord.GetChannelsInGuild(ID);
std::unordered_map<Snowflake, std::vector<ChannelData>> category_to_channels;
std::map<int, std::vector<ChannelData>> position_to_categories;
std::map<int, std::vector<ChannelData>> orphan_channels;
for (const auto &channel_id : channels) {
const auto data = discord.GetChannel(channel_id);
if (!data->ParentID.has_value() && (data->Type == ChannelType::GUILD_TEXT || data->Type == ChannelType::GUILD_NEWS))
orphan_channels[*data->Position].push_back(*data);
else if (data->ParentID.has_value() && (data->Type == ChannelType::GUILD_TEXT || data->Type == ChannelType::GUILD_NEWS))
category_to_channels[*data->ParentID].push_back(*data);
else if (data->Type == ChannelType::GUILD_CATEGORY)
position_to_categories[*data->Position].push_back(*data);
}
for (auto &[pos, channels] : orphan_channels) {
std::sort(channels.begin(), channels.end(), [&](const ChannelData &a, const ChannelData &b) -> bool {
return a.ID < b.ID;
});
for (const auto &chan : channels)
ret.push_back(chan.ID);
}
for (auto &[pos, categories] : position_to_categories) {
std::sort(categories.begin(), categories.end(), [&](const ChannelData &a, const ChannelData &b) -> bool {
return a.ID < b.ID;
});
for (const auto &category : categories) {
ret.push_back(category.ID);
if (ignore == category.ID) continue; // stupid hack to save me some time
auto it = category_to_channels.find(category.ID);
if (it == category_to_channels.end()) continue;
auto &channels = it->second;
std::sort(channels.begin(), channels.end(), [&](const ChannelData &a, const ChannelData &b) -> bool {
return a.Position < b.Position;
});
for (auto &channel : channels) {
ret.push_back(channel.ID);
}
}
}
return ret;
}
std::vector<RoleData> GuildData::FetchRoles() const {
if (!Roles.has_value()) return {};
std::vector<RoleData> ret;
ret.reserve(Roles->size());
for (const auto thing : *Roles) {
auto r = Abaddon::Get().GetDiscordClient().GetRole(thing.ID);
if (r.has_value())
ret.push_back(*r);
}
std::sort(ret.begin(), ret.end(), [](const RoleData &a, const RoleData &b) -> bool {
return a.Position > b.Position;
});
return ret;
}
void from_json(const nlohmann::json &j, GuildApplicationData &m) {
JS_D("user_id", m.UserID);
JS_D("guild_id", m.GuildID);
auto tmp = j.at("application_status").get<std::string_view>();
if (tmp == "STARTED")
m.ApplicationStatus = GuildApplicationStatus::STARTED;
else if (tmp == "PENDING")
m.ApplicationStatus = GuildApplicationStatus::PENDING;
else if (tmp == "REJECTED")
m.ApplicationStatus = GuildApplicationStatus::REJECTED;
else if (tmp == "APPROVED")
m.ApplicationStatus = GuildApplicationStatus::APPROVED;
JS_N("rejection_reason", m.RejectionReason);
JS_N("last_seen", m.LastSeen);
JS_N("created_at", m.CreatedAt);
}

100
src/discord/guild.hpp Normal file
View File

@@ -0,0 +1,100 @@
#pragma once
#include "json.hpp"
#include "snowflake.hpp"
#include "role.hpp"
#include "channel.hpp"
#include "emoji.hpp"
#include <vector>
#include <string>
#include <unordered_set>
enum class GuildApplicationStatus {
STARTED,
PENDING,
REJECTED,
APPROVED,
UNKNOWN,
};
struct GuildApplicationData {
Snowflake UserID;
Snowflake GuildID;
GuildApplicationStatus ApplicationStatus;
std::optional<std::string> RejectionReason;
std::optional<std::string> LastSeen;
std::optional<std::string> CreatedAt;
friend void from_json(const nlohmann::json &j, GuildApplicationData &m);
};
// a bot is apparently only supposed to receive the `id` and `unavailable` as false
// but user tokens seem to get the full objects (minus users)
// everythings optional cuz of muh partial guild object
// anything not marked optional in https://discord.com/developers/docs/resources/guild#guild-object is guaranteed to be set when returned from Store::GetGuild
struct GuildData {
Snowflake ID;
std::string Name;
std::string Icon; // null
std::string Splash; // null
std::optional<std::string> DiscoverySplash; // null
std::optional<bool> IsOwner;
std::optional<Snowflake> OwnerID;
std::optional<uint64_t> Permissions;
std::optional<std::string> PermissionsNew;
std::optional<std::string> VoiceRegion;
std::optional<Snowflake> AFKChannelID; // null
std::optional<int> AFKTimeout;
std::optional<bool> IsEmbedEnabled; // deprecated
std::optional<Snowflake> EmbedChannelID; // null, deprecated
std::optional<int> VerificationLevel;
std::optional<int> DefaultMessageNotifications;
std::optional<int> ExplicitContentFilter;
std::optional<std::vector<RoleData>> Roles; // only access id
std::optional<std::vector<EmojiData>> Emojis; // only access id
std::optional<std::unordered_set<std::string>> Features;
std::optional<int> MFALevel;
std::optional<Snowflake> ApplicationID; // null
std::optional<bool> IsWidgetEnabled;
std::optional<Snowflake> WidgetChannelID; // null
std::optional<Snowflake> SystemChannelID; // null
std::optional<int> SystemChannelFlags;
std::optional<Snowflake> RulesChannelID; // null
std::optional<std::string> JoinedAt; // *
std::optional<bool> IsLarge; // *
std::optional<bool> IsUnavailable; // *
std::optional<int> MemberCount; // *
// std::vector<VoiceStateData> VoiceStates; // opt*
// std::vector<MemberData> Members; // opt* - incomplete anyways
std::optional<std::vector<ChannelData>> Channels; // *
// std::vector<PresenceUpdateData> Presences; // opt*
std::optional<int> MaxPresences; // null
std::optional<int> MaxMembers;
std::optional<std::string> VanityURL; // null
std::optional<std::string> Description; // null
std::optional<std::string> BannerHash; // null
std::optional<int> PremiumTier;
std::optional<int> PremiumSubscriptionCount;
std::optional<std::string> PreferredLocale;
std::optional<Snowflake> PublicUpdatesChannelID; // null
std::optional<int> MaxVideoChannelUsers;
std::optional<int> ApproximateMemberCount;
std::optional<int> ApproximatePresenceCount;
std::optional<std::vector<ChannelData>> Threads; // only with permissions to view, id only
// undocumented
// std::map<std::string, Unknown> GuildHashes;
bool IsLazy = false;
// * - documentation says only sent in GUILD_CREATE, but these can be sent anyways in the READY event
friend void from_json(const nlohmann::json &j, GuildData &m);
void update_from_json(const nlohmann::json &j);
bool HasFeature(const std::string &feature);
bool HasIcon() const;
bool HasAnimatedIcon() const;
std::string GetIconURL(std::string ext = "png", std::string size = "32") const;
std::vector<Snowflake> GetSortedChannels(Snowflake ignore = Snowflake::Invalid) const;
std::vector<RoleData> FetchRoles() const; // sorted
};

139
src/discord/httpclient.cpp Normal file
View File

@@ -0,0 +1,139 @@
#include "httpclient.hpp"
//#define USE_LOCAL_PROXY
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;
}
void HTTPClient::SetAuth(std::string auth) {
m_authorization = auth;
}
void HTTPClient::MakeDELETE(const std::string &path, std::function<void(http::response_type r)> cb) {
printf("DELETE %s\n", path.c_str());
m_futures.push_back(std::async(std::launch::async, [this, path, cb] {
http::request req(http::REQUEST_DELETE, m_api_base + path);
req.set_header("Authorization", m_authorization);
req.set_user_agent(m_agent != "" ? m_agent : "Abaddon");
#ifdef USE_LOCAL_PROXY
req.set_proxy("http://127.0.0.1:8888");
req.set_verify_ssl(false);
#endif
auto res = req.execute();
OnResponse(res, cb);
}));
}
void HTTPClient::MakePATCH(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb) {
printf("PATCH %s\n", path.c_str());
m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] {
http::request req(http::REQUEST_PATCH, m_api_base + path);
req.set_header("Authorization", m_authorization);
req.set_header("Content-Type", "application/json");
req.set_user_agent(m_agent != "" ? m_agent : "Abaddon");
req.set_body(payload);
#ifdef USE_LOCAL_PROXY
req.set_proxy("http://127.0.0.1:8888");
req.set_verify_ssl(false);
#endif
auto res = req.execute();
OnResponse(res, cb);
}));
}
void HTTPClient::MakePOST(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb) {
printf("POST %s\n", path.c_str());
m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] {
http::request req(http::REQUEST_POST, m_api_base + path);
req.set_header("Authorization", m_authorization);
req.set_header("Content-Type", "application/json");
req.set_user_agent(m_agent != "" ? m_agent : "Abaddon");
req.set_body(payload);
#ifdef USE_LOCAL_PROXY
req.set_proxy("http://127.0.0.1:8888");
req.set_verify_ssl(false);
#endif
auto res = req.execute();
OnResponse(res, cb);
}));
}
void HTTPClient::MakePUT(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb) {
printf("PUT %s\n", path.c_str());
m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] {
http::request req(http::REQUEST_PUT, m_api_base + path);
req.set_header("Authorization", m_authorization);
if (payload != "")
req.set_header("Content-Type", "application/json");
req.set_user_agent(m_agent != "" ? m_agent : "Abaddon");
req.set_body(payload);
#ifdef USE_LOCAL_PROXY
req.set_proxy("http://127.0.0.1:8888");
req.set_verify_ssl(false);
#endif
auto res = req.execute();
OnResponse(res, cb);
}));
}
void HTTPClient::MakeGET(const std::string &path, std::function<void(http::response_type r)> cb) {
printf("GET %s\n", path.c_str());
m_futures.push_back(std::async(std::launch::async, [this, path, cb] {
http::request req(http::REQUEST_GET, m_api_base + path);
req.set_header("Authorization", m_authorization);
req.set_header("Content-Type", "application/json");
req.set_user_agent(m_agent != "" ? m_agent : "Abaddon");
#ifdef USE_LOCAL_PROXY
req.set_proxy("http://127.0.0.1:8888");
req.set_verify_ssl(false);
#endif
auto res = req.execute();
OnResponse(res, cb);
}));
}
void HTTPClient::CleanupFutures() {
for (auto it = m_futures.begin(); it != m_futures.end();) {
if (it->wait_for(std::chrono::seconds(0)) == std::future_status::ready)
it = m_futures.erase(it);
else
it++;
}
}
void HTTPClient::RunCallbacks() {
m_mutex.lock();
m_queue.front()();
m_queue.pop();
m_mutex.unlock();
}
void HTTPClient::OnResponse(const http::response_type &r, std::function<void(http::response_type r)> cb) {
CleanupFutures();
try {
m_mutex.lock();
m_queue.push([this, r, cb] { cb(r); });
m_dispatcher.emit();
m_mutex.unlock();
} catch (const std::exception &e) {
fprintf(stderr, "error handling response (%s, code %d): %s\n", r.url.c_str(), r.status_code, e.what());
}
}

View File

@@ -0,0 +1,39 @@
#pragma once
#include <functional>
#include <future>
#include <string>
#include <unordered_map>
#include <memory>
#include <mutex>
#include <queue>
#include <glibmm.h>
#include "http.hpp"
class HTTPClient {
public:
HTTPClient();
void SetBase(const std::string &url);
void SetUserAgent(std::string agent);
void SetAuth(std::string auth);
void MakeDELETE(const std::string &path, std::function<void(http::response_type r)> cb);
void MakeGET(const std::string &path, std::function<void(http::response_type r)> cb);
void MakePATCH(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb);
void MakePOST(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb);
void MakePUT(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb);
private:
void OnResponse(const http::response_type &r, std::function<void(http::response_type r)> cb);
void CleanupFutures();
mutable std::mutex m_mutex;
Glib::Dispatcher m_dispatcher;
std::queue<std::function<void()>> m_queue;
void RunCallbacks();
std::vector<std::future<void>> m_futures;
std::string m_api_base;
std::string m_authorization;
std::string m_agent;
};

View File

@@ -0,0 +1,11 @@
#include "interactions.hpp"
#include "json.hpp"
#include "abaddon.hpp"
void from_json(const nlohmann::json &j, MessageInteractionData &m) {
JS_D("id", m.ID);
JS_D("type", m.Type);
JS_D("name", m.Name);
JS_D("user", m.User);
JS_O("member", m.Member);
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <string>
#include <optional>
#include "member.hpp"
#include "user.hpp"
#include "snowflake.hpp"
enum class InteractionType {
Pong = 1, // ACK a Ping
Acknowledge = 2, // DEPRECATED ACK a command without sending a message, eating the user's input
ChannelMessage = 3, // DEPRECATED respond with a message, eating the user's input
ChannelMessageWithSource = 4, // respond to an interaction with a message
DeferredChannelMessageWithSource = 5, // ACK an interaction and edit to a response later, the user sees a loading state
};
struct MessageInteractionData {
Snowflake ID; // id of the interaction
InteractionType Type; // the type of interaction
std::string Name; // the name of the ApplicationCommand
UserData User; // the user who invoked the interaction
// undocumented???
std::optional<GuildMember> Member; // the member who invoked the interaction (in a guild)
friend void from_json(const nlohmann::json &j, MessageInteractionData &m);
};

39
src/discord/invite.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include "invite.hpp"
void from_json(const nlohmann::json &j, InviteChannelData &m) {
JS_D("id", m.ID);
JS_D("type", m.Type);
JS_ON("name", m.Name);
if (j.contains("recipients") && j.at("recipients").is_null()) {
m.RecipientUsernames.emplace();
for (const auto &x : j.at("recipients"))
m.RecipientUsernames->push_back(x.at("username").get<std::string>());
}
}
void from_json(const nlohmann::json &j, InviteData &m) {
JS_D("code", m.Code);
JS_O("guild", m.Guild);
JS_O("channel", m.Channel);
JS_O("inviter", m.Inviter);
JS_O("target_user", m.TargetUser);
JS_O("target_user_type", m.TargetUserType);
JS_O("approximate_presence_count", m.PresenceCount);
JS_O("approximate_member_count", m.MemberCount);
JS_O("uses", m.Uses);
JS_O("max_uses", m.MaxUses);
JS_O("max_age", m.MaxAge);
JS_O("temporary", m.IsTemporary);
JS_O("created_at", m.CreatedAt);
}
InviteChannelData::InviteChannelData(const ChannelData &c) {
ID = c.ID;
Type = c.Type;
Name = c.Name;
if (Type == ChannelType::GROUP_DM) {
RecipientUsernames.emplace();
for (const auto &r : c.GetDMRecipients())
RecipientUsernames->push_back(r.Username);
}
}

41
src/discord/invite.hpp Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include "json.hpp"
#include "guild.hpp"
#include <string>
enum class ETargetUserType {
STREAM = 1
};
class InviteChannelData {
public:
InviteChannelData() = default;
InviteChannelData(const ChannelData &c);
Snowflake ID;
ChannelType Type;
std::optional<std::string> Name;
std::optional<std::vector<std::string>> RecipientUsernames;
// std::optional<??> Icon;
friend void from_json(const nlohmann::json &j, InviteChannelData &m);
};
class InviteData {
public:
std::string Code;
std::optional<GuildData> Guild;
std::optional<InviteChannelData> Channel;
std::optional<UserData> Inviter;
std::optional<UserData> TargetUser;
std::optional<ETargetUserType> TargetUserType;
std::optional<int> PresenceCount;
std::optional<int> MemberCount;
std::optional<int> Uses;
std::optional<int> MaxUses;
std::optional<int> MaxAge;
std::optional<bool> IsTemporary;
std::optional<std::string> CreatedAt;
friend void from_json(const nlohmann::json &j, InviteData &m);
};

148
src/discord/json.hpp Normal file
View File

@@ -0,0 +1,148 @@
#pragma once
#include <nlohmann/json.hpp>
#include <optional>
#include "util.hpp"
namespace detail { // more or less because idk what to name this stuff
template<typename T>
inline void json_direct(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (::util::is_optional<T>::value)
val = j.at(key).get<typename T::value_type>();
else
j.at(key).get_to(val);
}
template<typename T>
inline void json_optional(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (::util::is_optional<T>::value) {
if (j.contains(key))
val = j.at(key).get<typename T::value_type>();
else
val = ::std::nullopt;
} else {
if (j.contains(key))
j.at(key).get_to(val);
}
}
template<typename T>
inline void json_nullable(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (::util::is_optional<T>::value) {
const auto &at = j.at(key);
if (!at.is_null())
val = at.get<typename T::value_type>();
else
val = ::std::nullopt;
} else {
const auto &at = j.at(key);
if (!at.is_null())
at.get_to(val);
}
}
template<typename T>
inline void json_optional_nullable(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (::util::is_optional<T>::value) {
if (j.contains(key)) {
const auto &at = j.at(key);
if (!at.is_null())
val = at.get<typename T::value_type>();
else
val = ::std::nullopt;
} else {
val = ::std::nullopt;
}
} else {
if (j.contains(key)) {
const auto &at = j.at(key);
if (!at.is_null())
at.get_to(val);
}
}
}
template<typename T>
inline void json_update_optional_nullable(const ::nlohmann::json &j, const char *key, T &val) {
if constexpr (::util::is_optional<T>::value) {
if (j.contains(key)) {
const auto &at = j.at(key);
if (!at.is_null())
val = at.get<typename T::value_type>();
else
val = ::std::nullopt;
}
} else {
if (j.contains(key)) {
const auto &at = j.at(key);
if (!at.is_null())
at.get_to(val);
else
val = T();
}
}
}
template<typename T, typename U>
inline void json_update_optional_nullable_default(const ::nlohmann::json &j, const char *key, T &val, const U &fallback) {
if constexpr (::util::is_optional<T>::value) {
if (j.contains(key)) {
const auto &at = j.at(key);
if (at.is_null())
val = fallback;
else
val = at.get<typename T::value_type>();
}
} else {
if (j.contains(key)) {
const auto &at = j.at(key);
if (at.is_null())
val = fallback;
else
at.get_to(val);
}
}
}
} // namespace detail
// get a json value that is guaranteed to be present and non-null
#define JS_D(k, t) \
do { \
detail::json_direct(j, k, t); \
} while (0)
// get a json value that may not be present
#define JS_O(k, t) \
do { \
detail::json_optional(j, k, t); \
} while (0)
// get a json value that may be null
#define JS_N(k, t) \
do { \
detail::json_nullable(j, k, t); \
} while (0)
// get a json value that may not be present or may be null
#define JS_ON(k, t) \
do { \
detail::json_optional_nullable(j, k, t); \
} while (0)
// set from a json value only if it is present. null will assign default-constructed value
#define JS_RD(k, t) \
do { \
detail::json_update_optional_nullable(j, k, t); \
} while (0)
// set from a json value only if it is present. null will assign the given default
#define JS_RV(k, t, d) \
do { \
detail::json_update_optional_nullable_default(j, k, t, d); \
} while (0)
// set a json value from a std::optional only if it has a value
#define JS_IF(k, v) \
do { \
if (v.has_value()) \
j[k] = *v; \
} while (0)

40
src/discord/member.cpp Normal file
View File

@@ -0,0 +1,40 @@
#include "member.hpp"
#include "abaddon.hpp"
void from_json(const nlohmann::json &j, GuildMember &m) {
JS_O("user", m.User);
JS_ON("nick", m.Nickname);
JS_D("roles", m.Roles);
JS_D("joined_at", m.JoinedAt);
JS_ON("premium_since", m.PremiumSince);
JS_D("deaf", m.IsDeafened);
JS_D("mute", m.IsMuted);
JS_O("user_id", m.UserID);
JS_ON("avatar", m.Avatar);
JS_O("pending", m.IsPending);
}
std::vector<RoleData> GuildMember::GetSortedRoles() const {
std::vector<RoleData> roles;
for (const auto role_id : Roles) {
const auto role = Abaddon::Get().GetDiscordClient().GetRole(role_id);
if (!role.has_value()) continue;
roles.push_back(std::move(*role));
}
std::sort(roles.begin(), roles.end(), [](const RoleData &a, const RoleData &b) {
return a.Position > b.Position;
});
return roles;
}
void GuildMember::update_from_json(const nlohmann::json &j) {
JS_RD("roles", Roles);
JS_RD("user", User);
JS_RD("nick", Nickname);
JS_RD("joined_at", JoinedAt);
JS_RD("premium_since", PremiumSince);
JS_RD("avatar", Avatar);
JS_RD("pending", IsPending);
}

27
src/discord/member.hpp Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include "snowflake.hpp"
#include "json.hpp"
#include "user.hpp"
#include "role.hpp"
#include <string>
#include <vector>
struct GuildMember {
std::optional<UserData> User; // only reliable to access id. only opt in MESSAGE_*
std::string Nickname;
std::vector<Snowflake> Roles;
std::string JoinedAt;
std::optional<std::string> PremiumSince; // null
bool IsDeafened;
bool IsMuted;
std::optional<Snowflake> UserID; // present in merged_members
std::optional<bool> IsPending; // this uses `pending` not `is_pending`
// undocuemtned moment !!!1
std::optional<std::string> Avatar;
std::vector<RoleData> GetSortedRoles() const;
void update_from_json(const nlohmann::json &j);
friend void from_json(const nlohmann::json &j, GuildMember &m);
};

265
src/discord/message.cpp Normal file
View File

@@ -0,0 +1,265 @@
#include "message.hpp"
void to_json(nlohmann::json &j, const EmbedFooterData &m) {
j["text"] = m.Text;
JS_IF("icon_url", m.IconURL);
JS_IF("proxy_icon_url", m.ProxyIconURL);
}
void from_json(const nlohmann::json &j, EmbedFooterData &m) {
JS_D("text", m.Text);
JS_O("icon_url", m.IconURL);
JS_O("proxy_icon_url", m.ProxyIconURL);
}
void to_json(nlohmann::json &j, const EmbedImageData &m) {
JS_IF("url", m.URL);
JS_IF("proxy_url", m.ProxyURL);
JS_IF("height", m.Height);
JS_IF("width", m.Width);
}
void from_json(const nlohmann::json &j, EmbedImageData &m) {
JS_O("url", m.URL);
JS_O("proxy_url", m.ProxyURL);
JS_O("height", m.Height);
JS_O("width", m.Width);
}
void to_json(nlohmann::json &j, const EmbedThumbnailData &m) {
JS_IF("url", m.URL);
JS_IF("proxy_url", m.ProxyURL);
JS_IF("height", m.Height);
JS_IF("width", m.Width);
}
void from_json(const nlohmann::json &j, EmbedThumbnailData &m) {
JS_O("url", m.URL);
JS_O("proxy_url", m.ProxyURL);
JS_O("height", m.Height);
JS_O("width", m.Width);
}
void to_json(nlohmann::json &j, const EmbedVideoData &m) {
JS_IF("url", m.URL);
JS_IF("height", m.Height);
JS_IF("width", m.Width);
}
void from_json(const nlohmann::json &j, EmbedVideoData &m) {
JS_O("url", m.URL);
JS_O("height", m.Height);
JS_O("width", m.Width);
}
void to_json(nlohmann::json &j, const EmbedProviderData &m) {
JS_IF("name", m.Name);
JS_IF("url", m.URL);
}
void from_json(const nlohmann::json &j, EmbedProviderData &m) {
JS_O("name", m.Name);
JS_ON("url", m.URL);
}
void to_json(nlohmann::json &j, const EmbedAuthorData &m) {
JS_IF("name", m.Name);
JS_IF("url", m.URL);
JS_IF("icon_url", m.IconURL);
JS_IF("proxy_icon_url", m.ProxyIconURL);
}
void from_json(const nlohmann::json &j, EmbedAuthorData &m) {
JS_O("name", m.Name);
JS_O("url", m.URL);
JS_O("icon_url", m.IconURL);
JS_O("proxy_icon_url", m.ProxyIconURL);
}
void to_json(nlohmann::json &j, const EmbedFieldData &m) {
j["name"] = m.Name;
j["value"] = m.Value;
JS_IF("inline", m.Inline);
}
void from_json(const nlohmann::json &j, EmbedFieldData &m) {
JS_D("name", m.Name);
JS_D("value", m.Value);
JS_O("inline", m.Inline);
}
void to_json(nlohmann::json &j, const EmbedData &m) {
JS_IF("title", m.Title);
JS_IF("type", m.Type);
JS_IF("description", m.Description);
JS_IF("url", m.URL);
JS_IF("timestamp", m.Timestamp);
JS_IF("color", m.Color);
JS_IF("footer", m.Footer);
JS_IF("image", m.Image);
JS_IF("thumbnail", m.Thumbnail);
JS_IF("video", m.Video);
JS_IF("provider", m.Provider);
JS_IF("author", m.Author);
JS_IF("fields", m.Fields);
}
void from_json(const nlohmann::json &j, EmbedData &m) {
JS_O("title", m.Title);
JS_O("type", m.Type);
JS_O("description", m.Description);
JS_O("url", m.URL);
JS_O("timestamp", m.Timestamp);
JS_O("color", m.Color);
JS_O("footer", m.Footer);
JS_O("image", m.Image);
JS_O("thumbnail", m.Thumbnail);
JS_O("video", m.Video);
JS_O("provider", m.Provider);
JS_O("author", m.Author);
JS_O("fields", m.Fields);
}
void to_json(nlohmann::json &j, const AttachmentData &m) {
j["id"] = m.ID;
j["filename"] = m.Filename;
j["size"] = m.Bytes;
j["url"] = m.URL;
j["proxy_url"] = m.ProxyURL;
JS_IF("height", m.Height);
JS_IF("width", m.Width);
}
void from_json(const nlohmann::json &j, AttachmentData &m) {
JS_D("id", m.ID);
JS_D("filename", m.Filename);
JS_D("size", m.Bytes);
JS_D("url", m.URL);
JS_D("proxy_url", m.ProxyURL);
JS_ON("height", m.Height);
JS_ON("width", m.Width);
}
void from_json(const nlohmann::json &j, MessageReferenceData &m) {
JS_O("message_id", m.MessageID);
JS_O("channel_id", m.ChannelID);
JS_O("guild_id", m.GuildID);
}
void to_json(nlohmann::json &j, const MessageReferenceData &m) {
JS_IF("message_id", m.MessageID);
JS_IF("channel_id", m.ChannelID);
JS_IF("guild_id", m.GuildID);
}
void from_json(const nlohmann::json &j, ReactionData &m) {
JS_D("count", m.Count);
JS_D("me", m.HasReactedWith);
JS_D("emoji", m.Emoji);
}
void to_json(nlohmann::json &j, const ReactionData &m) {
j["count"] = m.Count;
j["me"] = m.HasReactedWith;
j["emoji"] = m.Emoji;
}
void from_json(const nlohmann::json &j, MessageApplicationData &m) {
JS_D("id", m.ID);
JS_O("cover_image", m.CoverImage);
JS_D("description", m.Description);
JS_N("icon", m.Icon);
JS_D("name", m.Name);
}
void to_json(nlohmann::json &j, const MessageApplicationData &m) {
j["id"] = m.ID;
JS_IF("cover_image", m.CoverImage);
j["description"] = m.Description;
if (m.Icon == "")
j["icon"] = nullptr;
else
j["icon"] = m.Icon;
j["name"] = m.Name;
}
void from_json(const nlohmann::json &j, Message &m) {
JS_D("id", m.ID);
JS_D("channel_id", m.ChannelID);
JS_O("guild_id", m.GuildID);
JS_D("author", m.Author);
JS_O("member", m.Member);
JS_D("content", m.Content);
JS_D("timestamp", m.Timestamp);
JS_N("edited_timestamp", m.EditedTimestamp);
if (!j.at("edited_timestamp").is_null())
m.SetEdited();
JS_D("tts", m.IsTTS);
JS_D("mention_everyone", m.DoesMentionEveryone);
JS_D("mentions", m.Mentions);
// JS_D("mention_roles", m.MentionRoles);
// JS_O("mention_channels", m.MentionChannels);
JS_D("attachments", m.Attachments);
JS_D("embeds", m.Embeds);
JS_O("reactions", m.Reactions);
JS_O("nonce", m.Nonce);
JS_D("pinned", m.IsPinned);
JS_O("webhook_id", m.WebhookID);
JS_D("type", m.Type);
// JS_O("activity", m.Activity);
JS_O("application", m.Application);
JS_O("message_reference", m.MessageReference);
JS_O("flags", m.Flags);
JS_O("stickers", m.Stickers);
if (j.contains("referenced_message")) {
if (!j.at("referenced_message").is_null()) {
m.ReferencedMessage = std::make_shared<Message>(j.at("referenced_message").get<Message>());
} else
m.ReferencedMessage = nullptr;
}
JS_O("interaction", m.Interaction);
JS_O("sticker_items", m.StickerItems);
}
void Message::from_json_edited(const nlohmann::json &j) {
JS_D("id", ID);
JS_D("channel_id", ChannelID);
JS_O("guild_id", GuildID);
JS_O("author", Author);
JS_O("member", Member);
JS_O("content", Content);
JS_O("timestamp", Timestamp);
JS_ON("edited_timestamp", EditedTimestamp);
if (EditedTimestamp.size() > 0)
SetEdited();
JS_O("tts", IsTTS);
JS_O("mention_everyone", DoesMentionEveryone);
JS_O("mentions", Mentions);
JS_O("embeds", Embeds);
JS_O("nonce", Nonce);
JS_O("pinned", IsPinned);
JS_O("webhook_id", WebhookID);
JS_O("type", Type);
JS_O("application", Application);
JS_O("message_reference", MessageReference);
JS_O("flags", Flags);
JS_O("stickers", Stickers);
JS_O("interaction", Interaction);
JS_O("sticker_items", StickerItems);
}
void Message::SetDeleted() {
m_deleted = true;
}
void Message::SetEdited() {
m_edited = true;
}
bool Message::IsDeleted() const {
return m_deleted;
}
bool Message::IsEdited() const {
return m_edited;
}

218
src/discord/message.hpp Normal file
View File

@@ -0,0 +1,218 @@
#pragma once
#include <string>
#include <vector>
#include "snowflake.hpp"
#include "json.hpp"
#include "user.hpp"
#include "sticker.hpp"
#include "emoji.hpp"
#include "member.hpp"
#include "interactions.hpp"
enum class MessageType {
DEFAULT = 0, // yep
RECIPIENT_ADD = 1, // yep
RECIPIENT_REMOVE = 2, // yep
CALL = 3, // yep (sorta)
CHANNEL_NAME_CHANGE = 4, // yep
CHANNEL_ICON_CHANGE = 5, // yep
CHANNEL_PINNED_MESSAGE = 6, // yep
GUILD_MEMBER_JOIN = 7, // yep
USER_PREMIUM_GUILD_SUBSCRIPTION = 8, // yep
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9, // yep
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10, // yep
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11, // yep
CHANNEL_FOLLOW_ADD = 12, // yep
GUILD_DISCOVERY_DISQUALIFIED = 14, // yep
GUILD_DISCOVERY_REQUALIFIED = 15, // yep
GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16, // yep
GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17, // yep
THREAD_CREATED = 18, // yep
INLINE_REPLY = 19, // yep
APPLICATION_COMMAND = 20, // yep
THREAD_STARTER_MESSAGE = 21, // nope
};
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"
};
struct EmbedFooterData {
std::string Text;
std::optional<std::string> IconURL;
std::optional<std::string> ProxyIconURL;
friend void to_json(nlohmann::json &j, const EmbedFooterData &m);
friend void from_json(const nlohmann::json &j, EmbedFooterData &m);
};
struct EmbedImageData {
std::optional<std::string> URL;
std::optional<std::string> ProxyURL;
std::optional<int> Height;
std::optional<int> Width;
friend void to_json(nlohmann::json &j, const EmbedImageData &m);
friend void from_json(const nlohmann::json &j, EmbedImageData &m);
};
struct EmbedThumbnailData {
std::optional<std::string> URL;
std::optional<std::string> ProxyURL;
std::optional<int> Height;
std::optional<int> Width;
friend void to_json(nlohmann::json &j, const EmbedThumbnailData &m);
friend void from_json(const nlohmann::json &j, EmbedThumbnailData &m);
};
struct EmbedVideoData {
std::optional<std::string> URL;
std::optional<int> Height;
std::optional<int> Width;
friend void to_json(nlohmann::json &j, const EmbedVideoData &m);
friend void from_json(const nlohmann::json &j, EmbedVideoData &m);
};
struct EmbedProviderData {
std::optional<std::string> Name;
std::optional<std::string> URL; // null
friend void to_json(nlohmann::json &j, const EmbedProviderData &m);
friend void from_json(const nlohmann::json &j, EmbedProviderData &m);
};
struct EmbedAuthorData {
std::optional<std::string> Name;
std::optional<std::string> URL;
std::optional<std::string> IconURL;
std::optional<std::string> ProxyIconURL;
friend void to_json(nlohmann::json &j, const EmbedAuthorData &m);
friend void from_json(const nlohmann::json &j, EmbedAuthorData &m);
};
struct EmbedFieldData {
std::string Name;
std::string Value;
std::optional<bool> Inline;
friend void to_json(nlohmann::json &j, const EmbedFieldData &m);
friend void from_json(const nlohmann::json &j, EmbedFieldData &m);
};
struct EmbedData {
std::optional<std::string> Title;
std::optional<std::string> Type;
std::optional<std::string> Description;
std::optional<std::string> URL;
std::optional<std::string> Timestamp;
std::optional<int> Color;
std::optional<EmbedFooterData> Footer;
std::optional<EmbedImageData> Image;
std::optional<EmbedThumbnailData> Thumbnail;
std::optional<EmbedVideoData> Video;
std::optional<EmbedProviderData> Provider;
std::optional<EmbedAuthorData> Author;
std::optional<std::vector<EmbedFieldData>> Fields;
friend void to_json(nlohmann::json &j, const EmbedData &m);
friend void from_json(const nlohmann::json &j, EmbedData &m);
};
struct AttachmentData {
Snowflake ID;
std::string Filename;
int Bytes;
std::string URL;
std::string ProxyURL;
std::optional<int> Height; // null
std::optional<int> Width; // null
friend void to_json(nlohmann::json &j, const AttachmentData &m);
friend void from_json(const nlohmann::json &j, AttachmentData &m);
};
struct MessageReferenceData {
std::optional<Snowflake> MessageID;
std::optional<Snowflake> ChannelID;
std::optional<Snowflake> GuildID;
friend void from_json(const nlohmann::json &j, MessageReferenceData &m);
friend void to_json(nlohmann::json &j, const MessageReferenceData &m);
};
struct ReactionData {
int Count;
bool HasReactedWith;
EmojiData Emoji;
friend void from_json(const nlohmann::json &j, ReactionData &m);
friend void to_json(nlohmann::json &j, const ReactionData &m);
};
struct MessageApplicationData {
Snowflake ID;
std::optional<std::string> CoverImage;
std::string Description;
std::string Icon; // null
std::string Name;
friend void from_json(const nlohmann::json &j, MessageApplicationData &m);
friend void to_json(nlohmann::json &j, const MessageApplicationData &m);
};
struct Message {
Snowflake ID;
Snowflake ChannelID;
std::optional<Snowflake> GuildID;
UserData Author;
std::optional<GuildMember> Member;
std::string Content;
std::string Timestamp;
std::string EditedTimestamp; // null
bool IsTTS;
bool DoesMentionEveryone;
std::vector<UserData> Mentions; // full user accessible
// std::vector<RoleData> MentionRoles;
// std::optional<std::vector<ChannelMentionData>> MentionChannels;
std::vector<AttachmentData> Attachments;
std::vector<EmbedData> Embeds;
std::optional<std::vector<ReactionData>> Reactions;
std::optional<std::string> Nonce;
bool IsPinned;
std::optional<Snowflake> WebhookID;
MessageType Type;
// std::optional<MessageActivityData> ActivityData;
std::optional<MessageApplicationData> Application;
std::optional<MessageReferenceData> MessageReference;
std::optional<MessageFlags> Flags = MessageFlags::NONE;
std::optional<std::vector<StickerData>> Stickers;
std::optional<std::shared_ptr<Message>> ReferencedMessage; // has_value && null means deleted
std::optional<MessageInteractionData> Interaction;
std::optional<std::vector<StickerItem>> StickerItems;
friend void from_json(const nlohmann::json &j, Message &m);
void from_json_edited(const nlohmann::json &j); // for MESSAGE_UPDATE
// custom fields to track changes
bool IsPending = false; // for user-sent messages yet to be received in a MESSAGE_CREATE
void SetDeleted();
void SetEdited();
bool IsDeleted() const;
bool IsEdited() const;
private:
bool m_deleted = false;
bool m_edited = false;
};

534
src/discord/objects.cpp Normal file
View File

@@ -0,0 +1,534 @@
#include "objects.hpp"
void from_json(const nlohmann::json &j, GatewayMessage &m) {
JS_D("op", m.Opcode);
m.Data = j.at("d");
JS_ON("t", m.Type);
JS_ON("s", m.Sequence);
}
void from_json(const nlohmann::json &j, HelloMessageData &m) {
JS_D("heartbeat_interval", m.HeartbeatInterval);
}
void from_json(const nlohmann::json &j, MessageDeleteData &m) {
JS_D("id", m.ID);
JS_D("channel_id", m.ChannelID);
JS_O("guild_id", m.GuildID);
}
void from_json(const nlohmann::json &j, MessageDeleteBulkData &m) {
JS_D("ids", m.IDs);
JS_D("channel_id", m.ChannelID);
JS_O("guild_id", m.GuildID);
}
void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage::GroupItem &m) {
m.Type = "group";
JS_D("id", m.ID);
JS_D("count", m.Count);
}
GuildMember GuildMemberListUpdateMessage::MemberItem::GetAsMemberData() const {
return m_member_data;
}
void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage::MemberItem &m) {
m.Type = "member";
JS_D("user", m.User);
JS_D("roles", m.Roles);
JS_D("mute", m.IsMuted);
JS_D("joined_at", m.JoinedAt);
JS_D("deaf", m.IsDefeaned);
JS_N("hoisted_role", m.HoistedRole);
JS_ON("premium_since", m.PremiumSince);
JS_ON("nick", m.Nickname);
JS_ON("presence", m.Presence);
m.m_member_data = j;
}
void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage::OpObject &m) {
JS_D("op", m.Op);
if (m.Op == "SYNC") {
m.Items.emplace();
JS_D("range", m.Range);
for (const auto &ij : j.at("items")) {
if (ij.contains("group"))
m.Items->push_back(std::make_unique<GuildMemberListUpdateMessage::GroupItem>(ij.at("group")));
else if (ij.contains("member"))
m.Items->push_back(std::make_unique<GuildMemberListUpdateMessage::MemberItem>(ij.at("member")));
}
} else if (m.Op == "UPDATE") {
JS_D("index", m.Index);
const auto &ij = j.at("item");
if (ij.contains("member"))
m.OpItem = std::make_unique<GuildMemberListUpdateMessage::MemberItem>(ij.at("member"));
}
}
void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage &m) {
JS_D("online_count", m.OnlineCount);
JS_D("member_count", m.MemberCount);
JS_D("id", m.ListIDHash);
JS_D("guild_id", m.GuildID);
JS_D("groups", m.Groups);
JS_D("ops", m.Ops);
}
void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m) {
j["op"] = GatewayOp::LazyLoadRequest;
j["d"] = nlohmann::json::object();
j["d"]["guild_id"] = m.GuildID;
if (m.Channels.has_value()) {
j["d"]["channels"] = nlohmann::json::object();
for (const auto &[key, chans] : *m.Channels)
j["d"]["channels"][std::to_string(key)] = chans;
}
if (m.ShouldGetTyping)
j["d"]["typing"] = *m.ShouldGetTyping;
if (m.ShouldGetActivities)
j["d"]["activities"] = *m.ShouldGetActivities;
if (m.ShouldGetThreads)
j["d"]["threads"] = *m.ShouldGetThreads;
if (m.Members.has_value())
j["d"]["members"] = *m.Members;
if (m.ThreadIDs.has_value())
j["d"]["thread_member_lists"] = *m.ThreadIDs;
}
void to_json(nlohmann::json &j, const UpdateStatusMessage &m) {
j["op"] = GatewayOp::UpdateStatus;
j["d"] = nlohmann::json::object();
j["d"]["since"] = m.Since;
j["d"]["activities"] = m.Activities;
j["d"]["afk"] = m.IsAFK;
switch (m.Status) {
case PresenceStatus::Online:
j["d"]["status"] = "online";
break;
case PresenceStatus::Offline:
j["d"]["status"] = "invisible";
break;
case PresenceStatus::Idle:
j["d"]["status"] = "idle";
break;
case PresenceStatus::DND:
j["d"]["status"] = "dnd";
break;
}
}
void from_json(const nlohmann::json &j, ReadyEventData &m) {
JS_D("v", m.GatewayVersion);
JS_D("user", m.SelfUser);
JS_D("guilds", m.Guilds);
JS_D("session_id", m.SessionID);
JS_O("analytics_token", m.AnalyticsToken);
JS_O("friend_suggestion_count", m.FriendSuggestionCount);
JS_D("user_settings", m.Settings);
JS_D("private_channels", m.PrivateChannels);
JS_O("users", m.Users);
JS_ON("merged_members", m.MergedMembers);
JS_O("relationships", m.Relationships);
JS_O("guild_join_requests", m.GuildJoinRequests);
}
void from_json(const nlohmann::json &j, MergedPresence &m) {
JS_D("user_id", m.UserID);
JS_O("last_modified", m.LastModified);
m.Presence = j;
}
void from_json(const nlohmann::json &j, SupplementalMergedPresencesData &m) {
JS_D("guilds", m.Guilds);
JS_D("friends", m.Friends);
}
void from_json(const nlohmann::json &j, ReadySupplementalData &m) {
JS_D("merged_presences", m.MergedPresences);
}
void to_json(nlohmann::json &j, const IdentifyProperties &m) {
j["os"] = m.OS;
j["browser"] = m.Browser;
j["device"] = m.Device;
j["system_locale"] = m.SystemLocale;
j["browser_user_agent"] = m.BrowserUserAgent;
j["browser_version"] = m.BrowserVersion;
j["os_version"] = m.OSVersion;
j["referrer"] = m.Referrer;
j["referring_domain"] = m.ReferringDomain;
j["referrer_current"] = m.ReferrerCurrent;
j["referring_domain_current"] = m.ReferringDomainCurrent;
j["release_channel"] = m.ReleaseChannel;
j["client_build_number"] = m.ClientBuildNumber;
if (m.ClientEventSource == "")
j["client_event_source"] = nullptr;
else
j["client_event_source"] = m.ClientEventSource;
}
void to_json(nlohmann::json &j, const ClientStateProperties &m) {
j["guild_hashes"] = m.GuildHashes;
j["highest_last_message_id"] = m.HighestLastMessageID;
j["read_state_version"] = m.ReadStateVersion;
j["user_guild_settings_version"] = m.UserGuildSettingsVersion;
}
void to_json(nlohmann::json &j, const IdentifyMessage &m) {
j["op"] = GatewayOp::Identify;
j["d"] = nlohmann::json::object();
j["d"]["token"] = m.Token;
j["d"]["capabilities"] = m.Capabilities;
j["d"]["properties"] = m.Properties;
j["d"]["presence"] = m.Presence;
j["d"]["compress"] = m.DoesSupportCompression;
j["d"]["client_state"] = m.ClientState;
}
void to_json(nlohmann::json &j, const HeartbeatMessage &m) {
j["op"] = GatewayOp::Heartbeat;
if (m.Sequence == -1)
j["d"] = nullptr;
else
j["d"] = m.Sequence;
}
void to_json(nlohmann::json &j, const CreateMessageObject &m) {
j["content"] = m.Content;
JS_IF("message_reference", m.MessageReference);
JS_IF("nonce", m.Nonce);
}
void to_json(nlohmann::json &j, const MessageEditObject &m) {
if (m.Content.size() > 0)
j["content"] = m.Content;
// todo EmbedData to_json
// if (m.Embeds.size() > 0)
// j["embeds"] = m.Embeds;
if (m.Flags != -1)
j["flags"] = m.Flags;
}
void from_json(const nlohmann::json &j, GuildMemberUpdateMessage &m) {
JS_D("guild_id", m.GuildID);
JS_D("roles", m.Roles);
JS_D("user", m.User);
JS_ON("nick", m.Nick);
JS_D("joined_at", m.JoinedAt);
}
void from_json(const nlohmann::json &j, ClientStatusData &m) {
JS_O("desktop", m.Desktop);
JS_O("mobile", m.Mobile);
JS_O("web", m.Web);
}
void from_json(const nlohmann::json &j, PresenceUpdateMessage &m) {
m.User = j.at("user");
JS_O("guild_id", m.GuildID);
JS_D("status", m.StatusMessage);
JS_D("activities", m.Activities);
JS_D("client_status", m.ClientStatus);
}
void to_json(nlohmann::json &j, const CreateDMObject &m) {
std::vector<std::string> conv;
for (const auto &id : m.Recipients)
conv.push_back(std::to_string(id));
j["recipients"] = conv;
}
void to_json(nlohmann::json &j, const ResumeMessage &m) {
j["op"] = GatewayOp::Resume;
j["d"] = nlohmann::json::object();
j["d"]["token"] = m.Token;
j["d"]["session_id"] = m.SessionID;
j["d"]["seq"] = m.Sequence;
}
void from_json(const nlohmann::json &j, GuildRoleUpdateObject &m) {
JS_D("guild_id", m.GuildID);
JS_D("role", m.Role);
}
void from_json(const nlohmann::json &j, GuildRoleCreateObject &m) {
JS_D("guild_id", m.GuildID);
JS_D("role", m.Role);
}
void from_json(const nlohmann::json &j, GuildRoleDeleteObject &m) {
JS_D("guild_id", m.GuildID);
JS_D("role_id", m.RoleID);
}
void from_json(const nlohmann::json &j, MessageReactionAddObject &m) {
JS_D("user_id", m.UserID);
JS_D("channel_id", m.ChannelID);
JS_D("message_id", m.MessageID);
JS_O("guild_id", m.GuildID);
JS_O("member", m.Member);
JS_D("emoji", m.Emoji);
}
void from_json(const nlohmann::json &j, MessageReactionRemoveObject &m) {
JS_D("user_id", m.UserID);
JS_D("channel_id", m.ChannelID);
JS_D("message_id", m.MessageID);
JS_O("guild_id", m.GuildID);
JS_D("emoji", m.Emoji);
}
void from_json(const nlohmann::json &j, ChannelRecipientAdd &m) {
JS_D("user", m.User);
JS_D("channel_id", m.ChannelID);
}
void from_json(const nlohmann::json &j, ChannelRecipientRemove &m) {
JS_D("user", m.User);
JS_D("channel_id", m.ChannelID);
}
void from_json(const nlohmann::json &j, TypingStartObject &m) {
JS_D("channel_id", m.ChannelID);
JS_O("guild_id", m.GuildID);
JS_D("user_id", m.UserID);
JS_D("timestamp", m.Timestamp);
JS_O("member", m.Member);
}
void to_json(nlohmann::json &j, const ModifyGuildObject &m) {
JS_IF("name", m.Name);
JS_IF("icon", m.IconData);
}
void from_json(const nlohmann::json &j, GuildBanRemoveObject &m) {
JS_D("guild_id", m.GuildID);
JS_D("user", m.User);
}
void from_json(const nlohmann::json &j, GuildBanAddObject &m) {
JS_D("guild_id", m.GuildID);
JS_D("user", m.User);
}
void from_json(const nlohmann::json &j, InviteCreateObject &m) {
JS_D("channel_id", m.ChannelID);
JS_D("code", m.Code);
JS_D("created_at", m.CreatedAt);
JS_O("guild_id", m.GuildID);
JS_O("inviter", m.Inviter);
JS_D("max_age", m.MaxAge);
JS_D("max_uses", m.MaxUses);
JS_O("target_user", m.TargetUser);
JS_O("target_user_type", m.TargetUserType);
JS_D("temporary", m.IsTemporary);
JS_D("uses", m.Uses);
}
void from_json(const nlohmann::json &j, InviteDeleteObject &m) {
JS_D("channel_id", m.ChannelID);
JS_O("guild_id", m.GuildID);
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, const 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);
}
void from_json(const nlohmann::json &j, RelationshipsData &m) {
j.get_to(m.Users);
}
void to_json(nlohmann::json &j, const ModifyGuildMemberObject &m) {
JS_IF("roles", m.Roles);
}
void to_json(nlohmann::json &j, const ModifyGuildRoleObject &m) {
JS_IF("name", m.Name);
JS_IF("color", m.Color);
JS_IF("hoist", m.IsHoisted);
JS_IF("mentionable", m.Mentionable);
if (m.Permissions.has_value())
j["permissions"] = std::to_string(static_cast<uint64_t>(*m.Permissions));
}
void to_json(nlohmann::json &j, const ModifyGuildRolePositionsObject::PositionParam &m) {
j["id"] = m.ID;
JS_IF("position", m.Position);
}
void to_json(nlohmann::json &j, const ModifyGuildRolePositionsObject &m) {
j = m.Positions;
}
void from_json(const nlohmann::json &j, GuildEmojisUpdateObject &m) {
JS_D("guild_id", m.GuildID);
}
void to_json(nlohmann::json &j, const ModifyGuildEmojiObject &m) {
JS_IF("name", m.Name);
}
void from_json(const nlohmann::json &j, GuildJoinRequestCreateData &m) {
auto tmp = j.at("status").get<std::string_view>();
if (tmp == "STARTED")
m.Status = GuildApplicationStatus::STARTED;
else if (tmp == "PENDING")
m.Status = GuildApplicationStatus::PENDING;
else if (tmp == "REJECTED")
m.Status = GuildApplicationStatus::REJECTED;
else if (tmp == "APPROVED")
m.Status = GuildApplicationStatus::APPROVED;
JS_D("request", m.Request);
JS_D("guild_id", m.GuildID);
}
void from_json(const nlohmann::json &j, GuildJoinRequestDeleteData &m) {
JS_D("user_id", m.UserID);
JS_D("guild_id", m.GuildID);
}
void from_json(const nlohmann::json &j, VerificationFieldObject &m) {
JS_D("field_type", m.Type);
JS_D("label", m.Label);
JS_D("required", m.Required);
JS_D("values", m.Values);
}
void from_json(const nlohmann::json &j, VerificationGateInfoObject &m) {
JS_O("description", m.Description);
JS_O("form_fields", m.VerificationFields);
JS_O("version", m.Version);
JS_O("enabled", m.Enabled);
}
void to_json(nlohmann::json &j, const VerificationFieldObject &m) {
j["field_type"] = m.Type;
j["label"] = m.Label;
j["required"] = m.Required;
j["values"] = m.Values;
JS_IF("response", m.Response);
}
void to_json(nlohmann::json &j, const VerificationGateInfoObject &m) {
JS_IF("description", m.Description);
JS_IF("form_fields", m.VerificationFields);
JS_IF("version", m.Version);
JS_IF("enabled", m.Enabled);
}
void from_json(const nlohmann::json &j, RateLimitedResponse &m) {
JS_D("code", m.Code);
JS_D("global", m.Global);
JS_O("message", m.Message);
JS_D("retry_after", m.RetryAfter);
}
void from_json(const nlohmann::json &j, RelationshipRemoveData &m) {
JS_D("id", m.ID);
JS_D("type", m.Type);
}
void from_json(const nlohmann::json &j, RelationshipAddData &m) {
JS_D("id", m.ID);
JS_D("type", m.Type);
JS_D("user", m.User);
}
void to_json(nlohmann::json &j, const FriendRequestObject &m) {
j["username"] = m.Username;
j["discriminator"] = m.Discriminator;
}
void to_json(nlohmann::json &j, const PutRelationshipObject &m) {
JS_IF("type", m.Type);
}
void from_json(const nlohmann::json &j, ThreadCreateData &m) {
j.get_to(m.Channel);
}
void from_json(const nlohmann::json &j, ThreadDeleteData &m) {
JS_D("id", m.ID);
JS_D("guild_id", m.GuildID);
JS_D("parent_id", m.ParentID);
JS_D("type", m.Type);
}
void from_json(const nlohmann::json &j, ThreadListSyncData &m) {
JS_D("threads", m.Threads);
JS_D("guild_id", m.GuildID);
}
void from_json(const nlohmann::json &j, ThreadMembersUpdateData &m) {
JS_D("id", m.ID);
JS_D("guild_id", m.GuildID);
JS_D("member_count", m.MemberCount);
JS_O("added_members", m.AddedMembers);
JS_O("removed_member_ids", m.RemovedMemberIDs);
}
void from_json(const nlohmann::json &j, ArchivedThreadsResponseData &m) {
JS_D("threads", m.Threads);
JS_D("members", m.Members);
JS_D("has_more", m.HasMore);
}
void from_json(const nlohmann::json &j, ThreadMemberUpdateData &m) {
m.Member = j;
}
void from_json(const nlohmann::json &j, ThreadUpdateData &m) {
m.Thread = j;
}
void from_json(const nlohmann::json &j, ThreadMemberListUpdateData::UserEntry &m) {
JS_D("user_id", m.UserID);
JS_D("member", m.Member);
}
void from_json(const nlohmann::json &j, ThreadMemberListUpdateData &m) {
JS_D("thread_id", m.ThreadID);
JS_D("guild_id", m.GuildID);
JS_D("members", m.Members);
}
void to_json(nlohmann::json &j, const ModifyChannelObject &m) {
JS_IF("archived", m.Archived);
JS_IF("locked", m.Locked);
}

747
src/discord/objects.hpp Normal file
View File

@@ -0,0 +1,747 @@
#pragma once
#include <algorithm>
#include <nlohmann/json.hpp>
#include <vector>
#include <string>
#include "snowflake.hpp"
#include "user.hpp"
#include "role.hpp"
#include "member.hpp"
#include "channel.hpp"
#include "guild.hpp"
#include "usersettings.hpp"
#include "message.hpp"
#include "invite.hpp"
#include "permissions.hpp"
#include "emoji.hpp"
#include "activity.hpp"
#include "sticker.hpp"
#include "ban.hpp"
#include "auditlog.hpp"
#include "relationship.hpp"
#include "errors.hpp"
// most stuff below should just be objects that get processed and thrown away immediately
enum class GatewayOp : int {
Event = 0,
Heartbeat = 1,
Identify = 2,
UpdateStatus = 3,
Resume = 6,
Reconnect = 7,
InvalidSession = 9,
Hello = 10,
HeartbeatAck = 11,
LazyLoadRequest = 14,
};
enum class GatewayEvent : int {
READY,
MESSAGE_CREATE,
MESSAGE_DELETE,
MESSAGE_UPDATE,
GUILD_MEMBER_LIST_UPDATE,
GUILD_CREATE,
GUILD_DELETE,
MESSAGE_DELETE_BULK,
GUILD_MEMBER_UPDATE,
PRESENCE_UPDATE,
CHANNEL_DELETE,
CHANNEL_UPDATE,
CHANNEL_CREATE,
GUILD_UPDATE,
GUILD_ROLE_UPDATE,
GUILD_ROLE_CREATE,
GUILD_ROLE_DELETE,
MESSAGE_REACTION_ADD,
MESSAGE_REACTION_REMOVE,
CHANNEL_RECIPIENT_ADD,
CHANNEL_RECIPIENT_REMOVE,
TYPING_START,
GUILD_BAN_REMOVE,
GUILD_BAN_ADD,
INVITE_CREATE,
INVITE_DELETE,
USER_NOTE_UPDATE,
READY_SUPPLEMENTAL,
GUILD_EMOJIS_UPDATE,
GUILD_JOIN_REQUEST_CREATE,
GUILD_JOIN_REQUEST_UPDATE,
GUILD_JOIN_REQUEST_DELETE,
RELATIONSHIP_REMOVE,
RELATIONSHIP_ADD,
THREAD_CREATE,
THREAD_UPDATE,
THREAD_DELETE,
THREAD_LIST_SYNC,
THREAD_MEMBER_UPDATE,
THREAD_MEMBERS_UPDATE,
THREAD_MEMBER_LIST_UPDATE,
};
enum class GatewayCloseCode : uint16_t {
// standard
Normal = 1000,
GoingAway = 1001,
ProtocolError = 1002,
Unsupported = 1003,
NoStatus = 1005,
Abnormal = 1006,
UnsupportedPayload = 1007,
PolicyViolation = 1008,
TooLarge = 1009,
MandatoryExtension = 1010,
ServerError = 1011,
ServiceRestart = 1012,
TryAgainLater = 1013,
BadGateway = 1014,
TLSHandshakeFailed = 1015,
// discord
UnknownError = 4000,
UnknownOpcode = 4001,
DecodeError = 4002,
NotAuthenticated = 4003,
AuthenticationFailed = 4004,
AlreadyAuthenticated = 4005,
InvalidSequence = 4007,
RateLimited = 4008,
SessionTimedOut = 4009,
InvalidShard = 4010,
ShardingRequired = 4011,
InvalidAPIVersion = 4012,
InvalidIntents = 4013,
DisallowedIntents = 4014,
// internal
UserDisconnect = 4091,
Reconnecting = 4092,
};
struct GatewayMessage {
GatewayOp Opcode;
nlohmann::json Data;
std::string Type;
int Sequence = -1;
friend void from_json(const nlohmann::json &j, GatewayMessage &m);
};
struct HelloMessageData {
int HeartbeatInterval;
friend void from_json(const nlohmann::json &j, HelloMessageData &m);
};
struct MessageDeleteData {
Snowflake ID; //
Snowflake ChannelID; //
Snowflake GuildID; // opt
friend void from_json(const nlohmann::json &j, MessageDeleteData &m);
};
struct MessageDeleteBulkData {
std::vector<Snowflake> IDs; //
Snowflake ChannelID; //
Snowflake GuildID; // opt
friend void from_json(const nlohmann::json &j, MessageDeleteBulkData &m);
};
struct GuildMemberListUpdateMessage {
struct Item {
virtual ~Item() = default;
std::string Type;
};
struct GroupItem : Item {
std::string ID;
int Count;
friend void from_json(const nlohmann::json &j, GroupItem &m);
};
struct MemberItem : Item {
UserData User;
std::vector<Snowflake> Roles;
std::optional<PresenceData> Presence;
std::string PremiumSince; // opt
std::string Nickname; // opt
bool IsMuted;
std::string JoinedAt;
std::string HoistedRole; // null
bool IsDefeaned;
GuildMember GetAsMemberData() const;
friend void from_json(const nlohmann::json &j, MemberItem &m);
private:
GuildMember m_member_data;
};
struct OpObject {
std::string Op;
std::optional<int> Index;
std::optional<std::vector<std::unique_ptr<Item>>> Items; // SYNC
std::optional<std::pair<int, int>> Range; // SYNC
std::optional<std::unique_ptr<Item>> OpItem; // UPDATE
friend void from_json(const nlohmann::json &j, OpObject &m);
};
int OnlineCount;
int MemberCount;
std::string ListIDHash;
std::string GuildID;
std::vector<GroupItem> Groups;
std::vector<OpObject> Ops;
friend void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage &m);
};
struct LazyLoadRequestMessage {
Snowflake GuildID;
std::optional<bool> ShouldGetTyping;
std::optional<bool> ShouldGetActivities;
std::optional<bool> ShouldGetThreads;
std::optional<std::vector<std::string>> Members; // snowflake?
std::optional<std::map<Snowflake, std::vector<std::pair<int, int>>>> Channels; // channel ID -> range of sidebar
std::optional<std::vector<Snowflake>> ThreadIDs;
friend void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m);
};
struct UpdateStatusMessage {
int Since = 0;
std::vector<ActivityData> Activities;
PresenceStatus Status;
bool IsAFK = false;
friend void to_json(nlohmann::json &j, const UpdateStatusMessage &m);
};
struct ReadyEventData {
int GatewayVersion;
UserData SelfUser;
std::vector<GuildData> Guilds;
std::string SessionID;
std::vector<ChannelData> PrivateChannels;
// undocumented
std::optional<std::vector<UserData>> Users;
std::optional<std::string> AnalyticsToken;
std::optional<int> FriendSuggestionCount;
UserSettings Settings;
std::optional<std::vector<std::vector<GuildMember>>> MergedMembers;
std::optional<std::vector<RelationshipData>> Relationships;
std::optional<std::vector<GuildApplicationData>> GuildJoinRequests;
// std::vector<Unknown> ConnectedAccounts; // opt
// std::map<std::string, Unknown> Consents; // opt
// std::vector<Unknown> Experiments; // opt
// std::vector<Unknown> GuildExperiments; // opt
// std::map<Unknown, Unknown> Notes; // opt
// std::vector<PresenceData> Presences; // opt
// std::vector<ReadStateData> ReadStates; // opt
// Unknown Tutorial; // opt, null
// std::vector<GuildSettingData> UserGuildSettings; // opt
friend void from_json(const nlohmann::json &j, ReadyEventData &m);
};
struct MergedPresence {
Snowflake UserID;
std::optional<uint64_t> LastModified;
PresenceData Presence;
friend void from_json(const nlohmann::json &j, MergedPresence &m);
};
struct SupplementalMergedPresencesData {
std::vector<std::vector<MergedPresence>> Guilds;
std::vector<MergedPresence> Friends;
friend void from_json(const nlohmann::json &j, SupplementalMergedPresencesData &m);
};
struct ReadySupplementalData {
SupplementalMergedPresencesData MergedPresences;
friend void from_json(const nlohmann::json &j, ReadySupplementalData &m);
};
struct IdentifyProperties {
std::string OS;
std::string Browser;
std::string Device;
std::string SystemLocale;
std::string BrowserUserAgent;
std::string BrowserVersion;
std::string OSVersion;
std::string Referrer;
std::string ReferringDomain;
std::string ReferrerCurrent;
std::string ReferringDomainCurrent;
std::string ReleaseChannel;
int ClientBuildNumber;
std::string ClientEventSource; // empty -> null
friend void to_json(nlohmann::json &j, const IdentifyProperties &m);
};
struct ClientStateProperties {
std::map<std::string, std::string> GuildHashes;
std::string HighestLastMessageID = "0";
int ReadStateVersion = 0;
int UserGuildSettingsVersion = -1;
friend void to_json(nlohmann::json &j, const ClientStateProperties &m);
};
struct IdentifyMessage : GatewayMessage {
std::string Token;
IdentifyProperties Properties;
PresenceData Presence;
ClientStateProperties ClientState;
bool DoesSupportCompression = false;
int Capabilities;
friend void to_json(nlohmann::json &j, const IdentifyMessage &m);
};
struct HeartbeatMessage : GatewayMessage {
int Sequence;
friend void to_json(nlohmann::json &j, const HeartbeatMessage &m);
};
struct CreateMessageObject {
std::string Content;
std::optional<MessageReferenceData> MessageReference;
std::optional<std::string> Nonce;
friend void to_json(nlohmann::json &j, const CreateMessageObject &m);
};
struct MessageEditObject {
std::string Content; // opt, null
std::vector<EmbedData> Embeds; // opt, null
int Flags = -1; // opt, null
friend void to_json(nlohmann::json &j, const MessageEditObject &m);
};
struct GuildMemberUpdateMessage {
Snowflake GuildID; //
std::vector<Snowflake> Roles; //
UserData User; //
std::string Nick; // opt, null
std::string JoinedAt;
std::string PremiumSince; // opt, null
friend void from_json(const nlohmann::json &j, GuildMemberUpdateMessage &m);
};
struct ClientStatusData {
std::optional<std::string> Desktop;
std::optional<std::string> Mobile;
std::optional<std::string> Web;
friend void from_json(const nlohmann::json &j, ClientStatusData &m);
};
struct PresenceUpdateMessage {
nlohmann::json User; // the client updates an existing object from this data
std::optional<Snowflake> GuildID;
std::string StatusMessage;
std::vector<ActivityData> Activities;
ClientStatusData ClientStatus;
friend void from_json(const nlohmann::json &j, PresenceUpdateMessage &m);
};
struct CreateDMObject {
std::vector<Snowflake> Recipients;
friend void to_json(nlohmann::json &j, const CreateDMObject &m);
};
struct ResumeMessage : GatewayMessage {
std::string Token;
std::string SessionID;
int Sequence;
friend void to_json(nlohmann::json &j, const ResumeMessage &m);
};
struct GuildRoleUpdateObject {
Snowflake GuildID;
RoleData Role;
friend void from_json(const nlohmann::json &j, GuildRoleUpdateObject &m);
};
struct GuildRoleCreateObject {
Snowflake GuildID;
RoleData Role;
friend void from_json(const nlohmann::json &j, GuildRoleCreateObject &m);
};
struct GuildRoleDeleteObject {
Snowflake GuildID;
Snowflake RoleID;
friend void from_json(const nlohmann::json &j, GuildRoleDeleteObject &m);
};
struct MessageReactionAddObject {
Snowflake UserID;
Snowflake ChannelID;
Snowflake MessageID;
std::optional<Snowflake> GuildID;
std::optional<GuildMember> Member;
EmojiData Emoji;
friend void from_json(const nlohmann::json &j, MessageReactionAddObject &m);
};
struct MessageReactionRemoveObject {
Snowflake UserID;
Snowflake ChannelID;
Snowflake MessageID;
std::optional<Snowflake> GuildID;
EmojiData Emoji;
friend void from_json(const nlohmann::json &j, MessageReactionRemoveObject &m);
};
struct ChannelRecipientAdd {
UserData User;
Snowflake ChannelID;
friend void from_json(const nlohmann::json &j, ChannelRecipientAdd &m);
};
struct ChannelRecipientRemove {
UserData User;
Snowflake ChannelID;
friend void from_json(const nlohmann::json &j, ChannelRecipientRemove &m);
};
struct TypingStartObject {
Snowflake ChannelID;
std::optional<Snowflake> GuildID;
Snowflake UserID;
uint64_t Timestamp;
std::optional<GuildMember> Member;
friend void from_json(const nlohmann::json &j, TypingStartObject &m);
};
// implement rest as needed
struct ModifyGuildObject {
std::optional<std::string> Name;
std::optional<std::string> IconData;
friend void to_json(nlohmann::json &j, const ModifyGuildObject &m);
};
struct GuildBanRemoveObject {
Snowflake GuildID;
UserData User;
friend void from_json(const nlohmann::json &j, GuildBanRemoveObject &m);
};
struct GuildBanAddObject {
Snowflake GuildID;
UserData User;
friend void from_json(const nlohmann::json &j, GuildBanAddObject &m);
};
struct InviteCreateObject {
Snowflake ChannelID;
std::string Code;
std::string CreatedAt;
std::optional<Snowflake> GuildID;
std::optional<UserData> Inviter;
int MaxAge;
int MaxUses;
UserData TargetUser;
std::optional<ETargetUserType> TargetUserType;
bool IsTemporary;
int Uses;
friend void from_json(const nlohmann::json &j, InviteCreateObject &m);
};
struct InviteDeleteObject {
Snowflake ChannelID;
std::optional<Snowflake> GuildID;
std::string Code;
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, const UserSetNoteObject &m);
};
struct UserNoteUpdateMessage {
std::string Note;
Snowflake ID;
friend void from_json(const nlohmann::json &j, UserNoteUpdateMessage &m);
};
struct RelationshipsData {
std::vector<UserData> Users;
friend void from_json(const nlohmann::json &j, RelationshipsData &m);
};
struct ModifyGuildMemberObject {
// std::optional<std::string> Nick;
// std::optional<bool> IsMuted;
// std::optional<bool> IsDeaf;
// std::optional<Snowflake> ChannelID;
std::optional<std::vector<Snowflake>> Roles;
friend void to_json(nlohmann::json &j, const ModifyGuildMemberObject &m);
};
struct ModifyGuildRoleObject {
std::optional<std::string> Name;
std::optional<Permission> Permissions;
std::optional<uint32_t> Color;
std::optional<bool> IsHoisted;
std::optional<bool> Mentionable;
friend void to_json(nlohmann::json &j, const ModifyGuildRoleObject &m);
};
struct ModifyGuildRolePositionsObject {
struct PositionParam {
Snowflake ID;
std::optional<int> Position; // no idea why this can be optional
friend void to_json(nlohmann::json &j, const PositionParam &m);
};
std::vector<PositionParam> Positions;
friend void to_json(nlohmann::json &j, const ModifyGuildRolePositionsObject &m);
};
struct GuildEmojisUpdateObject {
Snowflake GuildID;
// std::vector<EmojiData> Emojis;
// GuildHashes, undocumented
friend void from_json(const nlohmann::json &j, GuildEmojisUpdateObject &m);
};
struct ModifyGuildEmojiObject {
std::optional<std::string> Name;
// std::optional<std::vector<Snowflake>> Roles;
friend void to_json(nlohmann::json &j, const ModifyGuildEmojiObject &m);
};
struct GuildJoinRequestCreateData {
GuildApplicationStatus Status;
GuildApplicationData Request;
Snowflake GuildID;
friend void from_json(const nlohmann::json &j, GuildJoinRequestCreateData &m);
};
using GuildJoinRequestUpdateData = GuildJoinRequestCreateData;
struct GuildJoinRequestDeleteData {
Snowflake UserID;
Snowflake GuildID;
friend void from_json(const nlohmann::json &j, GuildJoinRequestDeleteData &m);
};
struct VerificationFieldObject {
std::string Type;
std::string Label;
bool Required;
std::vector<std::string> Values;
std::optional<bool> Response; // present in client to server
friend void from_json(const nlohmann::json &j, VerificationFieldObject &m);
friend void to_json(nlohmann::json &j, const VerificationFieldObject &m);
};
struct VerificationGateInfoObject {
std::optional<std::string> Description;
std::optional<std::vector<VerificationFieldObject>> VerificationFields;
std::optional<std::string> Version;
std::optional<bool> Enabled; // present only in client to server in modify gate
friend void from_json(const nlohmann::json &j, VerificationGateInfoObject &m);
friend void to_json(nlohmann::json &j, const VerificationGateInfoObject &m);
};
// not sure what the structure for this really is
struct RateLimitedResponse {
int Code;
bool Global;
std::optional<std::string> Message;
float RetryAfter;
friend void from_json(const nlohmann::json &j, RateLimitedResponse &m);
};
struct RelationshipRemoveData {
Snowflake ID;
RelationshipType Type;
friend void from_json(const nlohmann::json &j, RelationshipRemoveData &m);
};
struct RelationshipAddData {
Snowflake ID;
// Nickname; same deal as the other comment somewhere else
RelationshipType Type;
UserData User;
// std::optional<bool> ShouldNotify; // i guess if the client should send a notification. not worth caring about
friend void from_json(const nlohmann::json &j, RelationshipAddData &m);
};
struct FriendRequestObject {
std::string Username;
int Discriminator;
friend void to_json(nlohmann::json &j, const FriendRequestObject &m);
};
struct PutRelationshipObject {
std::optional<RelationshipType> Type;
friend void to_json(nlohmann::json &j, const PutRelationshipObject &m);
};
struct ThreadCreateData {
ChannelData Channel;
friend void from_json(const nlohmann::json &j, ThreadCreateData &m);
};
struct ThreadDeleteData {
Snowflake ID;
Snowflake GuildID;
Snowflake ParentID;
ChannelType Type;
friend void from_json(const nlohmann::json &j, ThreadDeleteData &m);
};
// pretty different from docs
struct ThreadListSyncData {
std::vector<ChannelData> Threads;
Snowflake GuildID;
// std::optional<std::vector<???>> MostRecentMessages;
friend void from_json(const nlohmann::json &j, ThreadListSyncData &m);
};
struct ThreadMembersUpdateData {
Snowflake ID;
Snowflake GuildID;
int MemberCount;
std::optional<std::vector<ThreadMemberObject>> AddedMembers;
std::optional<std::vector<Snowflake>> RemovedMemberIDs;
friend void from_json(const nlohmann::json &j, ThreadMembersUpdateData &m);
};
struct ArchivedThreadsResponseData {
std::vector<ChannelData> Threads;
std::vector<ThreadMemberObject> Members;
bool HasMore;
friend void from_json(const nlohmann::json &j, ArchivedThreadsResponseData &m);
};
struct ThreadMemberUpdateData {
ThreadMemberObject Member;
friend void from_json(const nlohmann::json &j, ThreadMemberUpdateData &m);
};
struct ThreadUpdateData {
ChannelData Thread;
friend void from_json(const nlohmann::json &j, ThreadUpdateData &m);
};
struct ThreadMemberListUpdateData {
struct UserEntry {
Snowflake UserID;
// PresenceData Presence;
GuildMember Member;
friend void from_json(const nlohmann::json &j, UserEntry &m);
};
Snowflake ThreadID;
Snowflake GuildID;
std::vector<UserEntry> Members;
friend void from_json(const nlohmann::json &j, ThreadMemberListUpdateData &m);
};
struct ModifyChannelObject {
std::optional<bool> Archived;
std::optional<bool> Locked;
friend void to_json(nlohmann::json &j, const ModifyChannelObject &m);
};

View File

@@ -0,0 +1,11 @@
#include "permissions.hpp"
void from_json(const nlohmann::json &j, PermissionOverwrite &m) {
JS_D("id", m.ID);
std::string tmp;
m.Type = j.at("type").get<int>() == 0 ? PermissionOverwrite::ROLE : PermissionOverwrite::MEMBER;
JS_D("allow", tmp);
m.Allow = static_cast<Permission>(std::stoull(tmp));
JS_D("deny", tmp);
m.Deny = static_cast<Permission>(std::stoull(tmp));
}

224
src/discord/permissions.hpp Normal file
View File

@@ -0,0 +1,224 @@
#pragma once
#include <cstdint>
#include "snowflake.hpp"
#include "json.hpp"
#include "util.hpp"
constexpr static uint64_t PERMISSION_MAX_BIT = 36;
enum class Permission : uint64_t {
NONE = 0,
CREATE_INSTANT_INVITE = (1ULL << 0), // Allows creation of instant invites
KICK_MEMBERS = (1ULL << 1), // Allows kicking members
BAN_MEMBERS = (1ULL << 2), // Allows banning members
ADMINISTRATOR = (1ULL << 3), // Allows all permissions and bypasses channel permission overwrites
MANAGE_CHANNELS = (1ULL << 4), // Allows management and editing of channels
MANAGE_GUILD = (1ULL << 5), // Allows management and editing of the guild
ADD_REACTIONS = (1ULL << 6), // Allows for the addition of reactions to messages
VIEW_AUDIT_LOG = (1ULL << 7), // Allows for viewing of audit logs
PRIORITY_SPEAKER = (1ULL << 8), // Allows for using priority speaker in a voice channel
STREAM = (1ULL << 9), // Allows the user to go live
VIEW_CHANNEL = (1ULL << 10), // Allows guild members to view a channel, which includes reading messages in text channels
SEND_MESSAGES = (1ULL << 11), // Allows for sending messages in a channel
SEND_TTS_MESSAGES = (1ULL << 12), // Allows for sending of /tts messages
MANAGE_MESSAGES = (1ULL << 13), // Allows for deletion of other users messages
EMBED_LINKS = (1ULL << 14), // Links sent by users with this permission will be auto-embedded
ATTACH_FILES = (1ULL << 15), // Allows for uploading images and files
READ_MESSAGE_HISTORY = (1ULL << 16), // Allows for reading of message history
MENTION_EVERYONE = (1ULL << 17), // Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all online users in a channel
USE_EXTERNAL_EMOJIS = (1ULL << 18), // Allows the usage of custom emojis from other servers
VIEW_GUILD_INSIGHTS = (1ULL << 19), // Allows for viewing guild insights
CONNECT = (1ULL << 20), // Allows for joining of a voice channel
SPEAK = (1ULL << 21), // Allows for speaking in a voice channel
MUTE_MEMBERS = (1ULL << 22), // Allows for muting members in a voice channel
DEAFEN_MEMBERS = (1ULL << 23), // Allows for deafening of members in a voice channel
MOVE_MEMBERS = (1ULL << 24), // Allows for moving of members between voice channels
USE_VAD = (1ULL << 25), // Allows for using voice-activity-detection in a voice channel
CHANGE_NICKNAME = (1ULL << 26), // Allows for modification of own nickname
MANAGE_NICKNAMES = (1ULL << 27), // Allows for modification of other users nicknames
MANAGE_ROLES = (1ULL << 28), // Allows management and editing of roles
MANAGE_WEBHOOKS = (1ULL << 29), // Allows management and editing of webhooks
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
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 = 0x1FFFFFFFFFULL,
};
template<>
struct Bitwise<Permission> {
static const bool enable = true;
};
struct PermissionOverwrite {
enum OverwriteType : uint8_t {
ROLE = 0,
MEMBER = 1,
};
Snowflake ID;
OverwriteType Type;
Permission Allow;
Permission Deny;
friend void from_json(const nlohmann::json &j, PermissionOverwrite &m);
};
constexpr const char *GetPermissionString(Permission perm) {
switch (perm) {
case Permission::NONE:
return "None";
case Permission::CREATE_INSTANT_INVITE:
return "Create Invite";
case Permission::KICK_MEMBERS:
return "Kick Members";
case Permission::BAN_MEMBERS:
return "Ban Members";
case Permission::ADMINISTRATOR:
return "Administrator";
case Permission::MANAGE_CHANNELS:
return "Manage Channels";
case Permission::MANAGE_GUILD:
return "Manage Server";
case Permission::ADD_REACTIONS:
return "Add Reactions";
case Permission::VIEW_AUDIT_LOG:
return "View Audit Log";
case Permission::PRIORITY_SPEAKER:
return "Use Priority Speaker";
case Permission::STREAM:
return "Video";
case Permission::VIEW_CHANNEL:
return "View Channel";
case Permission::SEND_MESSAGES:
return "Send Messages";
case Permission::SEND_TTS_MESSAGES:
return "Use TTS";
case Permission::MANAGE_MESSAGES:
return "Manage Messages";
case Permission::EMBED_LINKS:
return "Embed Links";
case Permission::ATTACH_FILES:
return "Attach Files";
case Permission::READ_MESSAGE_HISTORY:
return "Read Message History";
case Permission::MENTION_EVERYONE:
return "Mention @everyone";
case Permission::USE_EXTERNAL_EMOJIS:
return "Use External Emojis";
case Permission::VIEW_GUILD_INSIGHTS:
return "View Server Insights";
case Permission::CONNECT:
return "Connect to Voice";
case Permission::SPEAK:
return "Speak in Voice";
case Permission::MUTE_MEMBERS:
return "Mute Members";
case Permission::DEAFEN_MEMBERS:
return "Deafen Members";
case Permission::MOVE_MEMBERS:
return "Move Members";
case Permission::USE_VAD:
return "Use Voice Activation";
case Permission::CHANGE_NICKNAME:
return "Change Nickname";
case Permission::MANAGE_NICKNAMES:
return "Manage Nicknames";
case Permission::MANAGE_ROLES:
return "Manage Roles";
case Permission::MANAGE_WEBHOOKS:
return "Manage Webhooks";
case Permission::MANAGE_EMOJIS:
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";
}
}
constexpr const char *GetPermissionDescription(Permission perm) {
switch (perm) {
case Permission::NONE:
return "";
case Permission::CREATE_INSTANT_INVITE:
return "Allows members to invite new people to this server.";
case Permission::KICK_MEMBERS:
return "Allows members to remove other members from this server. Kicked members will be able to rejoin if they have another invite.";
case Permission::BAN_MEMBERS:
return "Allows members to permanently ban other members from this server.";
case Permission::ADMINISTRATOR:
return "Members with this permission will have every permission and will also bypass all channel specific permissions or restrictions (for example, these members would get access to all private channels). This is a dangerous permission to grant.";
case Permission::MANAGE_CHANNELS:
return "Allows members to create, edit, or delete channels.";
case Permission::MANAGE_GUILD:
return "Allows members to change this server's name, switch regions, and add bots to this server.";
case Permission::ADD_REACTIONS:
return "Allows members to add new emoji reactions to a message. If this permission is disabled, members can still react using any existing reactions on a message.";
case Permission::VIEW_AUDIT_LOG:
return "Allows members to view a record of who made which changes in this server.";
case Permission::PRIORITY_SPEAKER:
return "Allows members to be more easily heard in voice channels. When activated, the volume of others without this permission will be automatically lowered. Priority Speaker is activated by using the Push to Talk (Priority) keybind.";
case Permission::STREAM:
return "Allows members to share their video, screen share, or stream a game in this server.";
case Permission::VIEW_CHANNEL:
return "Allows members to view channels by default (excluding private channels).";
case Permission::SEND_MESSAGES:
return "Allows members to send messages in text channels.";
case Permission::SEND_TTS_MESSAGES:
return "Allows members to send text-to-speech messages by starting a message with /tts. These messages can be heard by anyone focused on thsi channel.";
case Permission::MANAGE_MESSAGES:
return "Allows members to delete messages by other members or pin any message";
case Permission::EMBED_LINKS:
return "Allows links that members share to show embedded content in text channels.";
case Permission::ATTACH_FILES:
return "Allows members to upload files or media in text channels.";
case Permission::READ_MESSAGE_HISTORY:
return "Allows members to read previous messages sent in channels. If this permission is disabled, members only see messages sent when they are online and focused on that channel.";
case Permission::MENTION_EVERYONE:
return "Allows members to use @everyone (everyone in the server) or @here (only online members in that channel). They can also @mention all roles, even if the role's \"Allow anyone to mention this role\" permission is disabled.";
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 "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:
return "Allows members to talk in voice channels. If this permission is disabled, members are default muted until somebody with the \"Mute Members\" permission un-mutes them.";
case Permission::MUTE_MEMBERS:
return "Allows members to mute other members in voice channels for everyone.";
case Permission::DEAFEN_MEMBERS:
return "Allows members to deafen other members in voice channels, which means they won't be able to speak or hear others.";
case Permission::MOVE_MEMBERS:
return "Allows members to move other members between voice channels that the member with the permission has access to.";
case Permission::USE_VAD:
return "Allows members to speak in voice channels by simply talking. If this permission is disabled, members are required to use Push-to-talk. Good for controlling background noise or noisy members.";
case Permission::CHANGE_NICKNAME:
return "Allows members to change their own nickname, a custom name for just this server.";
case Permission::MANAGE_NICKNAMES:
return "Allows members to change the nicknames of other members.";
case Permission::MANAGE_ROLES:
return "Allows members to create new roles and edit or delete roles lower than their highest role. Also allows members to change permissions of individual channels that they have access to.";
case Permission::MANAGE_WEBHOOKS:
return "Allows members to create, edit, or delete webhooks, which can post messages from other apps or sites into this server.";
case Permission::MANAGE_EMOJIS:
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

@@ -0,0 +1,6 @@
#include "relationship.hpp"
void from_json(const nlohmann::json &j, RelationshipData &m) {
JS_D("type", m.Type);
JS_D("id", m.ID);
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include "json.hpp"
#include "user.hpp"
enum class RelationshipType {
None = 0,
Friend = 1,
Blocked = 2,
PendingIncoming = 3,
PendingOutgoing = 4,
Implicit = 5,
};
struct RelationshipData {
// Snowflake UserID; this is the same as ID apparently but it looks new so i wont touch it
RelationshipType Type;
Snowflake ID;
// Unknown Nickname; // null
friend void from_json(const nlohmann::json &j, RelationshipData &m);
};

14
src/discord/role.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include "role.hpp"
void from_json(const nlohmann::json &j, RoleData &m) {
JS_D("id", m.ID);
JS_D("name", m.Name);
JS_D("color", m.Color);
JS_D("hoist", m.IsHoisted);
JS_D("position", m.Position);
std::string tmp;
JS_D("permissions", tmp);
m.Permissions = static_cast<Permission>(std::stoull(tmp));
JS_D("managed", m.IsManaged);
JS_D("mentionable", m.IsMentionable);
}

20
src/discord/role.hpp Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include "snowflake.hpp"
#include "json.hpp"
#include "permissions.hpp"
#include <string>
#include <cstdint>
struct RoleData {
Snowflake ID;
std::string Name;
int Color;
bool IsHoisted;
int Position;
int PermissionsLegacy;
Permission Permissions;
bool IsManaged;
bool IsMentionable;
friend void from_json(const nlohmann::json &j, RoleData &m);
};

67
src/discord/snowflake.cpp Normal file
View File

@@ -0,0 +1,67 @@
#include "snowflake.hpp"
#include <ctime>
#include <iomanip>
#include <chrono>
constexpr static uint64_t DiscordEpochSeconds = 1420070400;
const Snowflake Snowflake::Invalid = -1ULL;
Snowflake::Snowflake()
: m_num(Invalid) {}
Snowflake::Snowflake(uint64_t n)
: m_num(n) {}
Snowflake::Snowflake(const std::string &str) {
if (str.size())
m_num = std::stoull(str);
else
m_num = Invalid;
}
Snowflake::Snowflake(const Glib::ustring &str) {
if (str.size())
m_num = std::strtoull(str.c_str(), nullptr, 10);
else
m_num = Invalid;
};
Snowflake Snowflake::FromNow() {
using namespace std::chrono;
// not guaranteed to work but it probably will anyway
static uint64_t counter = 0;
const auto millis_since_epoch = static_cast<uint64_t>(duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count());
const auto epoch = millis_since_epoch - DiscordEpochSeconds * 1000;
uint64_t snowflake = epoch << 22;
// worker id and process id would be OR'd in here but there's no point
snowflake |= counter++ % 4096;
return snowflake;
}
bool Snowflake::IsValid() const {
return m_num != Invalid;
}
std::string Snowflake::GetLocalTimestamp() const {
const time_t secs_since_epoch = (m_num / SecondsInterval) + DiscordEpochSeconds;
const std::tm tm = *localtime(&secs_since_epoch);
std::stringstream ss;
const static std::locale locale("");
ss.imbue(locale);
ss << std::put_time(&tm, "%X %x");
return ss.str();
}
void from_json(const nlohmann::json &j, Snowflake &s) {
if (j.is_string()) {
std::string tmp;
j.get_to(tmp);
s.m_num = std::stoull(tmp);
} else {
j.get_to(s.m_num);
}
}
void to_json(nlohmann::json &j, const Snowflake &s) {
j = std::to_string(s);
}

55
src/discord/snowflake.hpp Normal file
View File

@@ -0,0 +1,55 @@
#pragma once
#include <cstdint>
#include <nlohmann/json.hpp>
#include <glibmm/ustring.h>
struct Snowflake {
Snowflake();
Snowflake(uint64_t n);
Snowflake(const std::string &str);
Snowflake(const Glib::ustring &str);
static Snowflake FromNow(); // not thread safe
bool IsValid() const;
std::string GetLocalTimestamp() const;
bool operator==(const Snowflake &s) const noexcept {
return m_num == s.m_num;
}
bool operator<(const Snowflake &s) const noexcept {
return m_num < s.m_num;
}
operator uint64_t() const noexcept {
return m_num;
}
const static Snowflake Invalid; // makes sense to me
const static uint64_t SecondsInterval = 4194304000ULL; // the "difference" between two snowflakes one second apart
friend void from_json(const nlohmann::json &j, Snowflake &s);
friend void to_json(nlohmann::json &j, const Snowflake &s);
private:
friend struct std::hash<Snowflake>;
friend struct std::less<Snowflake>;
unsigned long long m_num;
};
namespace std {
template<>
struct hash<Snowflake> {
std::size_t operator()(const Snowflake &k) const {
return k.m_num;
}
};
template<>
struct less<Snowflake> {
bool operator()(const Snowflake &l, const Snowflake &r) const {
return l.m_num < r.m_num;
}
};
} // namespace std

52
src/discord/sticker.cpp Normal file
View File

@@ -0,0 +1,52 @@
#include "sticker.hpp"
void to_json(nlohmann::json &j, const StickerData &m) {
j["id"] = m.ID;
j["pack_id"] = m.PackID;
j["name"] = m.Name;
j["description"] = m.Description;
JS_IF("tags", m.Tags);
JS_IF("asset", m.AssetHash);
JS_IF("preview_asset", m.PreviewAssetHash);
j["format_type"] = m.FormatType;
}
void from_json(const nlohmann::json &j, StickerData &m) {
JS_D("id", m.ID);
JS_D("pack_id", m.PackID);
JS_D("name", m.Name);
JS_D("description", m.Description);
JS_O("tags", m.Tags);
JS_O("asset", m.AssetHash);
JS_ON("preview_asset", m.PreviewAssetHash);
JS_D("format_type", m.FormatType);
}
std::string StickerData::GetURL() const {
if (!AssetHash.has_value()) return "";
if (FormatType == StickerFormatType::PNG || FormatType == StickerFormatType::APNG)
return "https://media.discordapp.net/stickers/" + std::to_string(ID) + "/" + *AssetHash + ".png?size=256";
else if (FormatType == StickerFormatType::LOTTIE)
return "https://media.discordapp.net/stickers/" + std::to_string(ID) + "/" + *AssetHash + ".json";
return "";
}
void to_json(nlohmann::json &j, const StickerItem &m) {
j["id"] = m.ID;
j["name"] = m.Name;
j["format_type"] = m.FormatType;
}
void from_json(const nlohmann::json &j, StickerItem &m) {
JS_D("id", m.ID);
JS_D("name", m.Name);
JS_D("format_type", m.FormatType);
}
std::string StickerItem::GetURL() const {
if (FormatType == StickerFormatType::PNG || FormatType == StickerFormatType::APNG)
return "https://media.discordapp.net/stickers/" + std::to_string(ID) + ".png?size=256";
else if (FormatType == StickerFormatType::LOTTIE)
return "https://media.discordapp.net/stickers/" + std::to_string(ID) + ".json";
return "";
}

40
src/discord/sticker.hpp Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include <optional>
#include <string>
#include "snowflake.hpp"
#include "json.hpp"
// unstable
enum class StickerFormatType {
PNG = 1,
APNG = 2,
LOTTIE = 3,
};
struct StickerData {
Snowflake ID;
Snowflake PackID;
std::string Name;
std::string Description;
std::optional<std::string> Tags;
std::optional<std::string> AssetHash;
std::optional<std::string> PreviewAssetHash;
StickerFormatType FormatType;
friend void to_json(nlohmann::json &j, const StickerData &m);
friend void from_json(const nlohmann::json &j, StickerData &m);
std::string GetURL() const;
};
struct StickerItem {
StickerFormatType FormatType;
Snowflake ID;
std::string Name;
friend void to_json(nlohmann::json &j, const StickerItem &m);
friend void from_json(const nlohmann::json &j, StickerItem &m);
std::string GetURL() const;
};

2232
src/discord/store.cpp Normal file

File diff suppressed because it is too large Load Diff

302
src/discord/store.hpp Normal file
View File

@@ -0,0 +1,302 @@
#pragma once
#include "util.hpp"
#include "objects.hpp"
#include <unordered_map>
#include <unordered_set>
#include <mutex>
#include <filesystem>
#include <sqlite3.h>
#ifdef GetMessage // fuck you windows.h
#undef GetMessage
#endif
class Store {
public:
Store(bool mem_store = false);
~Store();
bool IsValid() const;
void SetUser(Snowflake id, const UserData &user);
void SetChannel(Snowflake id, const ChannelData &chan);
void SetGuild(Snowflake id, const GuildData &guild);
void SetRole(Snowflake guild_id, const RoleData &role);
void SetMessage(Snowflake id, const Message &message);
void SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMember &data);
void SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm);
void SetEmoji(Snowflake id, const EmojiData &emoji);
void SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban);
std::optional<ChannelData> GetChannel(Snowflake id) const;
std::optional<EmojiData> GetEmoji(Snowflake id) const;
std::optional<GuildData> GetGuild(Snowflake id) const;
std::optional<GuildMember> GetGuildMember(Snowflake guild_id, Snowflake user_id) const;
std::optional<Message> GetMessage(Snowflake id) const;
std::optional<PermissionOverwrite> GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const;
std::optional<RoleData> GetRole(Snowflake id) const;
std::optional<UserData> GetUser(Snowflake id) const;
std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
std::vector<BanData> GetBans(Snowflake guild_id) const;
std::vector<Message> GetLastMessages(Snowflake id, size_t num) const;
std::vector<Message> GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit) const;
std::vector<Message> GetPinnedMessages(Snowflake channel_id) const;
std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const; // public
void AddReaction(const MessageReactionAddObject &data, bool byself);
void RemoveReaction(const MessageReactionRemoveObject &data, bool byself);
void ClearGuild(Snowflake id);
void ClearChannel(Snowflake id);
void ClearBan(Snowflake guild_id, Snowflake user_id);
void ClearRecipient(Snowflake channel_id, Snowflake user_id);
std::unordered_set<Snowflake> GetChannels() const;
std::unordered_set<Snowflake> GetGuilds() const;
void ClearAll();
void BeginTransaction();
void EndTransaction();
private:
class Statement;
class Database {
public:
Database(const char *path);
~Database();
int Close();
int StartTransaction();
int EndTransaction();
int Execute(const char *command);
int Error() const;
bool OK() const;
const char *ErrStr() const;
int SetError(int err);
sqlite3 *obj();
private:
sqlite3 *m_db;
int m_err = SQLITE_OK;
mutable char m_err_scratch[256] { 0 };
// stupid shit i dont like to allow closing properly
using type_signal_close = sigc::signal<void>;
type_signal_close m_signal_close;
public:
type_signal_close signal_close();
};
class Statement {
public:
Statement() = delete;
Statement(const Statement &other) = delete;
Statement(Database &db, const char *command);
~Statement();
Statement &operator=(Statement &other) = delete;
bool OK() const;
int Bind(int index, Snowflake id);
int Bind(int index, const char *str, size_t len = -1);
int Bind(int index, const std::string &str);
int Bind(int index);
template<typename T>
int Bind(int index, std::optional<T> opt) {
if (opt.has_value())
return Bind(index, opt.value());
else
return Bind(index);
}
template<typename Iter>
int BindIDsAsJSON(int index, Iter start, Iter end) {
std::vector<Snowflake> x;
for (Iter it = start; it != end; it++) {
x.push_back((*it).ID);
}
return Bind(index, nlohmann::json(x).dump());
}
template<typename T>
int BindAsJSONArray(int index, const std::optional<T> &obj) {
if (obj.has_value())
return Bind(index, nlohmann::json(obj.value()).dump());
else
return Bind(index, std::string("[]"));
}
template<typename T>
int BindAsJSON(int index, const T &obj) {
return Bind(index, nlohmann::json(obj).dump());
}
template<typename T>
inline typename std::enable_if<std::is_enum<T>::value, int>::type
Bind(int index, T val) {
return Bind(index, static_cast<typename std::underlying_type<T>::type>(val));
}
template<typename T>
typename std::enable_if<std::is_integral<T>::value, int>::type
Bind(int index, T val) {
return m_db->SetError(sqlite3_bind_int64(m_stmt, index, val));
}
template<typename T>
int BindAsJSON(int index, const std::optional<T> &obj) {
if (obj.has_value())
return Bind(index, nlohmann::json(obj.value()).dump());
else
return Bind(index);
}
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type
Get(int index, T &out) const {
out = static_cast<T>(sqlite3_column_int64(m_stmt, index));
}
void Get(int index, Snowflake &out) const;
void Get(int index, std::string &out) const;
template<typename T>
void GetJSON(int index, std::optional<T> &out) const {
if (IsNull(index))
out = std::nullopt;
else {
std::string stuff;
Get(index, stuff);
if (stuff == "")
out = std::nullopt;
else
out = nlohmann::json::parse(stuff).get<T>();
}
}
template<typename T>
void GetJSON(int index, T &out) const {
std::string stuff;
Get(index, stuff);
nlohmann::json::parse(stuff).get_to(out);
}
template<typename T>
void Get(int index, std::optional<T> &out) const {
if (IsNull(index))
out = std::nullopt;
else {
T tmp;
Get(index, tmp);
out = std::optional<T>(std::move(tmp));
}
}
template<typename T>
inline typename std::enable_if<std::is_enum<T>::value, void>::type
Get(int index, T &val) const {
typename std::underlying_type<T>::type tmp;
Get(index, tmp);
val = static_cast<T>(tmp);
}
template<typename T>
void GetIDOnlyStructs(int index, std::optional<std::vector<T>> &out) const {
out.emplace();
std::string str;
Get(index, str);
for (const auto &id : nlohmann::json::parse(str))
out->emplace_back().ID = id.get<Snowflake>();
}
template<typename T, typename OutputIt>
void GetArray(int index, OutputIt first) const {
std::string str;
Get(index, str);
for (const auto &id : nlohmann::json::parse(str))
*first++ = id.get<T>();
}
bool IsNull(int index) const;
int Step();
bool Insert();
bool FetchOne();
int Reset();
sqlite3_stmt *obj();
private:
Database *m_db;
sqlite3_stmt *m_stmt;
};
Message GetMessageBound(std::unique_ptr<Statement> &stmt) const;
void SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction);
bool CreateTables();
bool CreateStatements();
bool m_ok = true;
std::filesystem::path m_db_path;
Database m_db;
#define STMT(x) mutable std::unique_ptr<Statement> m_stmt_##x
STMT(set_guild);
STMT(get_guild);
STMT(get_guild_ids);
STMT(clr_guild);
STMT(set_chan);
STMT(get_chan);
STMT(get_chan_ids);
STMT(clr_chan);
STMT(set_msg);
STMT(get_msg);
STMT(set_msg_ref);
STMT(get_last_msgs);
STMT(set_user);
STMT(get_user);
STMT(set_member);
STMT(get_member);
STMT(set_role);
STMT(get_role);
STMT(set_emoji);
STMT(get_emoji);
STMT(set_perm);
STMT(get_perm);
STMT(set_ban);
STMT(get_ban);
STMT(get_bans);
STMT(clr_ban);
STMT(set_interaction);
STMT(set_member_roles);
STMT(get_member_roles);
STMT(set_guild_emoji);
STMT(get_guild_emojis);
STMT(clr_guild_emoji);
STMT(set_guild_feature);
STMT(get_guild_features);
STMT(get_guild_chans);
STMT(set_thread);
STMT(get_threads);
STMT(get_active_threads);
STMT(get_messages_before);
STMT(get_pins);
STMT(set_emoji_role);
STMT(get_emoji_roles);
STMT(set_mention);
STMT(get_mentions);
STMT(set_attachment);
STMT(get_attachments);
STMT(set_recipient);
STMT(get_recipients);
STMT(clr_recipient);
STMT(add_reaction);
STMT(sub_reaction);
STMT(get_reactions);
#undef STMT
};

197
src/discord/user.cpp Normal file
View File

@@ -0,0 +1,197 @@
#include "user.hpp"
#include "abaddon.hpp"
bool UserData::IsDeleted() const {
return Discriminator == "0000";
}
bool UserData::HasAvatar() const {
return Avatar.size() > 0;
}
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 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 {
return Abaddon::Get().GetDiscordClient().GetMemberHoistedRole(guild_id, ID, with_color);
}
std::string UserData::GetMention() const {
return "<@" + std::to_string(ID) + ">";
}
std::string UserData::GetEscapedName() const {
return Glib::Markup::escape_text(Username);
}
std::string UserData::GetEscapedBoldName() const {
return "<b>" + Glib::Markup::escape_text(Username) + "</b>";
}
std::string UserData::GetEscapedString() const {
return Glib::Markup::escape_text(Username) + "#" + Discriminator;
}
void from_json(const nlohmann::json &j, UserData &m) {
JS_D("id", m.ID);
JS_D("username", m.Username);
JS_D("discriminator", m.Discriminator);
JS_N("avatar", m.Avatar);
JS_O("bot", m.IsBot);
JS_O("system", m.IsSystem);
JS_O("mfa_enabled", m.IsMFAEnabled);
JS_O("locale", m.Locale);
JS_O("verified", m.IsVerified);
JS_O("email", m.Email);
JS_O("flags", m.Flags);
JS_ON("premium_type", m.PremiumType);
JS_O("public_flags", m.PublicFlags);
JS_O("desktop", m.IsDesktop);
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) {
j["id"] = m.ID;
j["username"] = m.Username;
j["discriminator"] = m.Discriminator;
if (m.Avatar == "")
j["avatar"] = nullptr;
else
j["avatar"] = m.Avatar;
JS_IF("bot", m.IsBot);
JS_IF("system", m.IsSystem);
JS_IF("mfa_enabled", m.IsMFAEnabled);
JS_IF("locale", m.Locale);
JS_IF("verified", m.IsVerified);
JS_IF("email", m.Email);
JS_IF("flags", m.Flags);
JS_IF("premium_type", m.PremiumType);
JS_IF("public_flags", m.PublicFlags);
JS_IF("desktop", m.IsDesktop);
JS_IF("mobile", m.IsMobile);
JS_IF("nsfw_allowed", m.IsNSFWAllowed);
JS_IF("phone", m.Phone);
}
void UserData::update_from_json(const nlohmann::json &j) {
JS_RD("username", Username);
JS_RD("discriminator", Discriminator);
JS_RD("avatar", Avatar);
JS_RD("bot", IsBot);
JS_RD("system", IsSystem);
JS_RD("mfa_enabled", IsMFAEnabled);
JS_RD("locale", Locale);
JS_RD("verified", IsVerified);
JS_RD("email", Email);
JS_RD("flags", Flags);
JS_RD("premium_type", PremiumType);
JS_RD("public_flags", PublicFlags);
JS_RD("desktop", IsDesktop);
JS_RD("mobile", IsMobile);
JS_RD("nsfw_allowed", IsNSFWAllowed);
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";
case CertifiedModerator:
return "certifiedmoderator";
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";
case CertifiedModerator:
return "Discord Certified Moderator";
default:
return "";
}
}

82
src/discord/user.hpp Normal file
View File

@@ -0,0 +1,82 @@
#pragma once
#include "snowflake.hpp"
#include "json.hpp"
#include <string>
enum class EPremiumType {
None = 0,
NitroClassic = 1,
Nitro = 2,
};
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,
CertifiedModerator = 1 << 18,
MaxFlag_PlusOne,
MaxFlag = MaxFlag_PlusOne - 1,
};
static const char *GetFlagName(uint64_t flag);
static const char *GetFlagReadableName(uint64_t flag);
Snowflake ID;
std::string Username;
std::string Discriminator;
std::string Avatar; // null
std::optional<bool> IsBot;
std::optional<bool> IsSystem;
std::optional<bool> IsMFAEnabled;
std::optional<std::string> Locale;
std::optional<bool> IsVerified;
std::optional<std::string> Email; // null
std::optional<uint64_t> Flags;
std::optional<EPremiumType> PremiumType; // null
std::optional<uint64_t> PublicFlags;
// undocumented (opt)
std::optional<bool> IsDesktop;
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);
void update_from_json(const nlohmann::json &j);
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;
std::string GetEscapedBoldName() const;
std::string GetEscapedString() const;
template<bool with_at>
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;
}
};

View File

@@ -0,0 +1,40 @@
#include "usersettings.hpp"
void from_json(const nlohmann::json &j, UserSettingsGuildFoldersEntry &m) {
JS_N("color", m.Color);
JS_D("guild_ids", m.GuildIDs);
JS_N("id", m.ID);
JS_N("name", m.Name);
}
void from_json(const nlohmann::json &j, UserSettings &m) {
JS_D("timezone_offset", m.TimezoneOffset);
JS_D("theme", m.Theme);
JS_D("stream_notifications_enabled", m.AreStreamNotificationsEnabled);
JS_D("status", m.Status);
JS_D("show_current_game", m.ShouldShowCurrentGame);
// JS_D("restricted_guilds", m.RestrictedGuilds);
JS_D("render_reactions", m.ShouldRenderReactions);
JS_D("render_embeds", m.ShouldRenderEmbeds);
JS_D("native_phone_integration_enabled", m.IsNativePhoneIntegrationEnabled);
JS_D("message_display_compact", m.ShouldMessageDisplayCompact);
JS_D("locale", m.Locale);
JS_D("inline_embed_media", m.ShouldInlineEmbedMedia);
JS_D("inline_attachment_media", m.ShouldInlineAttachmentMedia);
JS_D("guild_positions", m.GuildPositions);
JS_D("guild_folders", m.GuildFolders);
JS_D("gif_auto_play", m.ShouldGIFAutoplay);
// JS_D("friend_source_flags", m.FriendSourceFlags);
JS_D("explicit_content_filter", m.ExplicitContentFilter);
JS_D("enable_tts_command", m.IsTTSCommandEnabled);
JS_D("disable_games_tab", m.ShouldDisableGamesTab);
JS_D("developer_mode", m.DeveloperMode);
JS_D("detect_platform_accounts", m.ShouldDetectPlatformAccounts);
JS_D("default_guilds_restricted", m.AreDefaultGuildsRestricted);
// JS_N("custom_status", m.CustomStatus);
JS_D("convert_emoticons", m.ShouldConvertEmoticons);
JS_D("contact_sync_enabled", m.IsContactSyncEnabled);
JS_D("animate_emoji", m.ShouldAnimateEmojis);
JS_D("allow_accessibility_detection", m.IsAccessibilityDetectionAllowed);
JS_D("afk_timeout", m.AFKTimeout);
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include "json.hpp"
#include "snowflake.hpp"
#include <string>
struct UserSettingsGuildFoldersEntry {
int Color = -1; // null
std::vector<Snowflake> GuildIDs;
Snowflake ID; // null (this can be a snowflake as a string or an int that isnt a snowflake lol)
std::string Name; // null
friend void from_json(const nlohmann::json &j, UserSettingsGuildFoldersEntry &m);
};
struct UserSettings {
int TimezoneOffset; //
std::string Theme; //
bool AreStreamNotificationsEnabled; //
std::string Status; //
bool ShouldShowCurrentGame; //
// std::vector<Unknown> RestrictedGuilds; //
bool ShouldRenderReactions; //
bool ShouldRenderEmbeds; //
bool IsNativePhoneIntegrationEnabled; //
bool ShouldMessageDisplayCompact; //
std::string Locale; //
bool ShouldInlineEmbedMedia; //
bool ShouldInlineAttachmentMedia; //
std::vector<Snowflake> GuildPositions; // deprecated?
std::vector<UserSettingsGuildFoldersEntry> GuildFolders; //
bool ShouldGIFAutoplay; //
// Unknown FriendSourceFlags; //
int ExplicitContentFilter; //
bool IsTTSCommandEnabled; //
bool ShouldDisableGamesTab; //
bool DeveloperMode; //
bool ShouldDetectPlatformAccounts; //
bool AreDefaultGuildsRestricted; //
// Unknown CustomStatus; // null
bool ShouldConvertEmoticons; //
bool IsContactSyncEnabled; //
bool ShouldAnimateEmojis; //
bool IsAccessibilityDetectionAllowed; //
int AFKTimeout;
friend void from_json(const nlohmann::json &j, UserSettings &m);
};

13
src/discord/webhook.cpp Normal file
View File

@@ -0,0 +1,13 @@
#include "webhook.hpp"
void from_json(const nlohmann::json &j, WebhookData &m) {
JS_D("id", m.ID);
JS_D("type", m.Type);
JS_O("guild_id", m.GuildID);
JS_D("channel_id", m.ChannelID);
JS_O("user", m.User);
JS_N("name", m.Name);
JS_N("avatar", m.Avatar);
JS_O("token", m.Token);
JS_N("application_id", m.ApplicationID);
}

24
src/discord/webhook.hpp Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <optional>
#include "json.hpp"
#include "snowflake.hpp"
#include "user.hpp"
enum class WebhookType {
Incoming = 1,
ChannelFollower = 2,
};
struct WebhookData {
Snowflake ID;
WebhookType Type;
std::optional<Snowflake> GuildID;
Snowflake ChannelID;
std::optional<UserData> User;
std::string Name; // null
std::string Avatar; // null
std::optional<std::string> Token;
Snowflake ApplicationID; // null
friend void from_json(const nlohmann::json &j, WebhookData &m);
};

66
src/discord/websocket.cpp Normal file
View File

@@ -0,0 +1,66 @@
#include "websocket.hpp"
#include <functional>
Websocket::Websocket() {}
void Websocket::StartConnection(std::string url) {
m_websocket.disableAutomaticReconnection();
m_websocket.setUrl(url);
m_websocket.setOnMessageCallback(std::bind(&Websocket::OnMessage, this, std::placeholders::_1));
m_websocket.setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent } }); // idk if this actually works
m_websocket.start();
}
void Websocket::SetUserAgent(std::string agent) {
m_agent = agent;
}
void Websocket::Stop() {
Stop(ix::WebSocketCloseConstants::kNormalClosureCode);
}
void Websocket::Stop(uint16_t code) {
m_websocket.stop(code);
}
bool Websocket::IsOpen() const {
auto state = m_websocket.getReadyState();
return state == ix::ReadyState::Open;
}
void Websocket::Send(const std::string &str) {
printf("sending %s\n", str.c_str());
m_websocket.sendText(str);
}
void Websocket::Send(const nlohmann::json &j) {
Send(j.dump());
}
void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) {
switch (msg->type) {
case ix::WebSocketMessageType::Open: {
m_signal_open.emit();
} break;
case ix::WebSocketMessageType::Close: {
m_signal_close.emit(msg->closeInfo.code);
} break;
case ix::WebSocketMessageType::Message: {
m_signal_message.emit(msg->str);
} break;
default:
break;
}
}
Websocket::type_signal_open Websocket::signal_open() {
return m_signal_open;
}
Websocket::type_signal_close Websocket::signal_close() {
return m_signal_close;
}
Websocket::type_signal_message Websocket::signal_message() {
return m_signal_message;
}

41
src/discord/websocket.hpp Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include <ixwebsocket/IXNetSystem.h>
#include <ixwebsocket/IXWebSocket.h>
#include <string>
#include <functional>
#include <nlohmann/json.hpp>
#include <sigc++/sigc++.h>
class Websocket {
public:
Websocket();
void StartConnection(std::string url);
void SetUserAgent(std::string agent);
void Send(const std::string &str);
void Send(const nlohmann::json &j);
void Stop();
void Stop(uint16_t code);
bool IsOpen() const;
private:
void OnMessage(const ix::WebSocketMessagePtr &msg);
ix::WebSocket m_websocket;
std::string m_agent;
public:
typedef sigc::signal<void> type_signal_open;
typedef sigc::signal<void, uint16_t> type_signal_close;
typedef sigc::signal<void, std::string> type_signal_message;
type_signal_open signal_open();
type_signal_close signal_close();
type_signal_message signal_message();
private:
type_signal_open m_signal_open;
type_signal_close m_signal_close;
type_signal_message m_signal_message;
};