add set status for funsies

This commit is contained in:
ouwou
2020-11-01 02:53:37 -05:00
parent 18f4f7ce5e
commit 534bfccf23
13 changed files with 358 additions and 8 deletions

View File

@@ -7,6 +7,7 @@
#include "dialogs/editmessage.hpp"
#include "dialogs/joinguild.hpp"
#include "dialogs/confirm.hpp"
#include "dialogs/setstatus.hpp"
#include "abaddon.hpp"
#ifdef _WIN32
@@ -62,6 +63,7 @@ int Abaddon::StartGTK() {
m_main_window->signal_action_set_token().connect(sigc::mem_fun(*this, &Abaddon::ActionSetToken));
m_main_window->signal_action_reload_css().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadCSS));
m_main_window->signal_action_join_guild().connect(sigc::mem_fun(*this, &Abaddon::ActionJoinGuildDialog));
m_main_window->signal_action_set_status().connect(sigc::mem_fun(*this, &Abaddon::ActionSetStatus));
m_main_window->GetChannelList()->signal_action_channel_item_select().connect(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened));
m_main_window->GetChannelList()->signal_action_guild_move_up().connect(sigc::mem_fun(*this, &Abaddon::ActionMoveGuildUp));
@@ -372,6 +374,19 @@ void Abaddon::ActionBanMember(Snowflake user_id, Snowflake guild_id) {
m_discord.BanUser(user_id, guild_id);
}
void Abaddon::ActionSetStatus() {
SetStatusDialog dlg(*m_main_window);
const auto response = dlg.run();
if (response != Gtk::RESPONSE_OK || !m_discord.IsStarted()) return;
const auto status = dlg.GetStatusType();
const auto activity_type = dlg.GetActivityType();
const auto activity_name = dlg.GetActivityName();
Activity activity;
activity.Name = activity_name;
activity.Type = activity_type;
m_discord.UpdateStatus(status, false, activity);
}
void Abaddon::ActionReloadCSS() {
try {
Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_provider);

View File

@@ -44,6 +44,7 @@ public:
void ActionLeaveGuild(Snowflake id);
void ActionKickMember(Snowflake user_id, Snowflake guild_id);
void ActionBanMember(Snowflake user_id, Snowflake guild_id);
void ActionSetStatus();
void ActionReloadCSS();

61
dialogs/setstatus.cpp Normal file
View File

@@ -0,0 +1,61 @@
#include "setstatus.hpp"
SetStatusDialog::SetStatusDialog(Gtk::Window &parent)
: Gtk::Dialog("Set Status", parent, true)
, m_layout(Gtk::ORIENTATION_VERTICAL)
, m_bbox(Gtk::ORIENTATION_HORIZONTAL)
, m_bottom(Gtk::ORIENTATION_HORIZONTAL)
, m_ok("OK")
, m_cancel("Cancel") {
set_default_size(300, 50);
m_text.set_placeholder_text("Status text");
m_status_combo.append("online", "Online");
m_status_combo.append("dnd", "Do Not Disturb");
m_status_combo.append("idle", "Away");
m_status_combo.append("invisible", "Invisible");
m_status_combo.set_active_text("Online");
m_type_combo.append("0", "Playing");
m_type_combo.append("1", "Streaming");
m_type_combo.append("2", "Listening to");
m_type_combo.append("3", "Watching");
m_type_combo.append("4", "Custom");
m_type_combo.append("5", "Competing in");
m_type_combo.set_active_text("Custom");
m_ok.signal_clicked().connect([this]() {
response(Gtk::RESPONSE_OK);
});
m_cancel.signal_clicked().connect([this]() {
response(Gtk::RESPONSE_CANCEL);
});
m_bbox.pack_start(m_ok, Gtk::PACK_SHRINK);
m_bbox.pack_start(m_cancel, Gtk::PACK_SHRINK);
m_bbox.set_layout(Gtk::BUTTONBOX_END);
m_bottom.add(m_status_combo);
m_bottom.add(m_type_combo);
m_bottom.add(m_bbox);
m_layout.add(m_text);
m_layout.add(m_bottom);
get_content_area()->add(m_layout);
show_all_children();
}
ActivityType SetStatusDialog::GetActivityType() const {
const auto x = m_type_combo.get_active_id();
return static_cast<ActivityType>(std::stoul(x));
}
std::string SetStatusDialog::GetStatusType() const {
return m_status_combo.get_active_id();
}
std::string SetStatusDialog::GetActivityName() const {
return m_text.get_text();
}

22
dialogs/setstatus.hpp Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <gtkmm.h>
#include "../discord/objects.hpp"
class SetStatusDialog : public Gtk::Dialog {
public:
SetStatusDialog(Gtk::Window &parent);
ActivityType GetActivityType() const;
std::string GetStatusType() const;
std::string GetActivityName() const;
protected:
Gtk::Box m_layout;
Gtk::Box m_bottom;
Gtk::Entry m_text;
Gtk::ComboBoxText m_status_combo;
Gtk::ComboBoxText m_type_combo;
Gtk::Button m_ok;
Gtk::Button m_cancel;
Gtk::ButtonBox m_bbox;
};

101
discord/activity.cpp Normal file
View File

@@ -0,0 +1,101 @@
#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, Activity &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 Activity &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);
}

92
discord/activity.hpp Normal file
View File

@@ -0,0 +1,92 @@
#pragma once
#include <string>
#include <optional>
#include "../util.hpp"
#include "json.hpp"
#include "snowflake.hpp"
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<std::string> Start; // opt
std::optional<std::string> End; // opt
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 Activity {
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, Activity &m);
friend void to_json(nlohmann::json &j, const Activity &m);
};

View File

@@ -397,6 +397,15 @@ void DiscordClient::BanUser(Snowflake user_id, Snowflake guild_id) {
m_http.MakePUT("/guilds/" + std::to_string(guild_id) + "/bans/" + std::to_string(user_id), "{}", [](auto) {});
}
void DiscordClient::UpdateStatus(const std::string &status, bool is_afk, const Activity &obj) {
UpdateStatusMessage msg;
msg.Status = status;
msg.IsAFK = is_afk;
msg.Activities.push_back(obj);
m_websocket.Send(nlohmann::json(msg));
}
void DiscordClient::UpdateToken(std::string token) {
if (!IsStarted()) {
m_token = token;

View File

@@ -103,6 +103,7 @@ public:
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(const std::string &status, bool is_afk, const Activity &obj);
void UpdateToken(std::string token);

View File

@@ -37,7 +37,7 @@ inline void json_nullable(const ::nlohmann::json &j, const char *key, T &val) {
if (!at.is_null())
val = at.get<T::value_type>();
else
val = std::nullopt;
val = ::std::nullopt;
} else {
const auto &at = j.at(key);
if (!at.is_null())
@@ -53,9 +53,9 @@ inline void json_optional_nullable(const ::nlohmann::json &j, const char *key, T
if (!at.is_null())
val = at.get<T::value_type>();
else
val = std::nullopt;
val = ::std::nullopt;
} else {
val = std::nullopt;
val = ::std::nullopt;
}
} else {
if (j.contains(key)) {
@@ -74,7 +74,7 @@ inline void json_update_optional_nullable(const ::nlohmann::json &j, const char
if (!at.is_null())
val = at.get<T::value_type>();
else
val = std::nullopt;
val = ::std::nullopt;
}
} else {
if (j.contains(key)) {
@@ -109,32 +109,45 @@ inline void json_update_optional_nullable_default(const ::nlohmann::json &j, con
}
} // 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)

View File

@@ -83,6 +83,15 @@ void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m) {
j["d"]["members"] = m.Members;
}
void to_json(nlohmann::json &j, const UpdateStatusMessage &m) {
j["op"] = GatewayOp::UpdateStatus;
j["d"] = nlohmann::json::object();
j["d"]["activities"] = m.Activities;
j["d"]["status"] = m.Status;
j["d"]["afk"] = m.IsAFK;
j["d"]["since"] = nullptr;
}
void from_json(const nlohmann::json &j, ReadyEventData &m) {
JS_D("v", m.GatewayVersion);
JS_D("user", m.User);

View File

@@ -14,6 +14,7 @@
#include "invite.hpp"
#include "permissions.hpp"
#include "emoji.hpp"
#include "activity.hpp"
// most stuff below should just be objects that get processed and thrown away immediately
@@ -21,6 +22,7 @@ enum class GatewayOp : int {
Event = 0,
Heartbeat = 1,
Identify = 2,
UpdateStatus = 3,
Hello = 10,
HeartbeatAck = 11,
LazyLoadRequest = 14,
@@ -134,6 +136,14 @@ struct LazyLoadRequestMessage {
friend void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m);
};
struct UpdateStatusMessage {
std::vector<Activity> Activities; // null (but never sent as such)
std::string Status;
bool IsAFK;
friend void to_json(nlohmann::json &j, const UpdateStatusMessage &m);
};
struct ReadyEventData {
int GatewayVersion; //
User User; //

View File

@@ -17,10 +17,13 @@ MainWindow::MainWindow()
m_menu_discord_set_token.set_label("Set Token");
m_menu_discord_join_guild.set_label("Join Guild");
m_menu_discord_join_guild.set_sensitive(false);
m_menu_discord_set_status.set_label("Set Status");
m_menu_discord_set_status.set_sensitive(false);
m_menu_discord_sub.append(m_menu_discord_connect);
m_menu_discord_sub.append(m_menu_discord_disconnect);
m_menu_discord_sub.append(m_menu_discord_set_token);
m_menu_discord_sub.append(m_menu_discord_join_guild);
m_menu_discord_sub.append(m_menu_discord_set_status);
m_menu_discord.set_submenu(m_menu_discord_sub);
m_menu_file.set_label("File");
@@ -31,19 +34,19 @@ MainWindow::MainWindow()
m_menu_bar.append(m_menu_file);
m_menu_bar.append(m_menu_discord);
m_menu_discord_connect.signal_activate().connect([&] {
m_menu_discord_connect.signal_activate().connect([this] {
m_signal_action_connect.emit();
});
m_menu_discord_disconnect.signal_activate().connect([&] {
m_menu_discord_disconnect.signal_activate().connect([this] {
m_signal_action_disconnect.emit();
});
m_menu_discord_set_token.signal_activate().connect([&] {
m_menu_discord_set_token.signal_activate().connect([this] {
m_signal_action_set_token.emit();
});
m_menu_discord_join_guild.signal_activate().connect([&] {
m_menu_discord_join_guild.signal_activate().connect([this] {
m_signal_action_join_guild.emit();
});
@@ -51,6 +54,10 @@ MainWindow::MainWindow()
m_signal_action_reload_css.emit();
});
m_menu_discord_set_status.signal_activate().connect([this] {
m_signal_action_set_status.emit();
});
m_content_box.set_hexpand(true);
m_content_box.set_vexpand(true);
@@ -97,6 +104,7 @@ void MainWindow::UpdateComponents() {
m_menu_discord_disconnect.set_sensitive(discord_active);
m_menu_discord_join_guild.set_sensitive(discord_active);
m_menu_discord_set_token.set_sensitive(!discord_active);
m_menu_discord_set_status.set_sensitive(discord_active);
if (!discord_active) {
m_channel_list.Clear();
@@ -226,3 +234,7 @@ MainWindow::type_signal_action_reload_css MainWindow::signal_action_reload_css()
MainWindow::type_signal_action_join_guild MainWindow::signal_action_join_guild() {
return m_signal_action_join_guild;
}
MainWindow::type_signal_action_set_status MainWindow::signal_action_set_status() {
return m_signal_action_set_status;
}

View File

@@ -37,12 +37,14 @@ public:
typedef sigc::signal<void> type_signal_action_set_token;
typedef sigc::signal<void> type_signal_action_reload_css;
typedef sigc::signal<void> type_signal_action_join_guild;
typedef sigc::signal<void> type_signal_action_set_status;
type_signal_action_connect signal_action_connect();
type_signal_action_disconnect signal_action_disconnect();
type_signal_action_set_token signal_action_set_token();
type_signal_action_reload_css signal_action_reload_css();
type_signal_action_join_guild signal_action_join_guild();
type_signal_action_set_status signal_action_set_status();
protected:
type_signal_action_connect m_signal_action_connect;
@@ -50,6 +52,7 @@ protected:
type_signal_action_set_token m_signal_action_set_token;
type_signal_action_reload_css m_signal_action_reload_css;
type_signal_action_join_guild m_signal_action_join_guild;
type_signal_action_set_status m_signal_action_set_status;
protected:
Gtk::Box m_main_box;
@@ -68,6 +71,7 @@ protected:
Gtk::MenuItem m_menu_discord_disconnect;
Gtk::MenuItem m_menu_discord_set_token;
Gtk::MenuItem m_menu_discord_join_guild;
Gtk::MenuItem m_menu_discord_set_status;
Gtk::MenuItem m_menu_file;
Gtk::Menu m_menu_file_sub;