handle rate limited channels

This commit is contained in:
ouwou 2021-04-13 04:36:27 -04:00
parent 140782c395
commit 422eed92fc
9 changed files with 181 additions and 6 deletions

View File

@ -44,8 +44,9 @@ bool ChatInput::ProcessKeyPress(GdkEventKey *event) {
auto text = buf->get_text();
// sometimes a message thats just newlines can sneak in if you hold down enter
if (text.size() > 0 && !std::all_of(text.begin(), text.end(), [](gunichar c) -> bool { return c == gunichar('\n'); })) {
buf->set_text("");
m_signal_submit.emit(text);
const bool accepted = m_signal_submit.emit(text);
if (accepted)
buf->set_text("");
return true;
}
}

View File

@ -16,7 +16,7 @@ private:
Gtk::TextView m_textview;
public:
typedef sigc::signal<void, Glib::ustring> type_signal_submit;
typedef sigc::signal<bool, Glib::ustring> type_signal_submit;
typedef sigc::signal<void> type_signal_escape;
type_signal_submit signal_submit();

View File

@ -2,6 +2,7 @@
#include "chatmessage.hpp"
#include "../abaddon.hpp"
#include "chatinputindicator.hpp"
#include "ratelimitindicator.hpp"
#include "chatinput.hpp"
constexpr static uint64_t SnowflakeSplitDifference = 600;
@ -14,7 +15,16 @@ ChatWindow::ChatWindow() {
m_scroll = Gtk::manage(new Gtk::ScrolledWindow);
m_input = Gtk::manage(new ChatInput);
m_input_indicator = Gtk::manage(new ChatInputIndicator);
m_rate_limit_indicator = Gtk::manage(new RateLimitIndicator);
m_meta = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
m_rate_limit_indicator->set_margin_end(5);
m_rate_limit_indicator->set_hexpand(true);
m_rate_limit_indicator->set_halign(Gtk::ALIGN_END);
m_rate_limit_indicator->set_valign(Gtk::ALIGN_END);
m_rate_limit_indicator->show();
m_input_indicator->set_halign(Gtk::ALIGN_START);
m_input_indicator->set_valign(Gtk::ALIGN_END);
m_input_indicator->show();
@ -88,11 +98,17 @@ ChatWindow::ChatWindow() {
m_completer.show();
m_meta->set_hexpand(true);
m_meta->set_halign(Gtk::ALIGN_FILL);
m_meta->show();
m_meta->add(*m_input_indicator);
m_meta->add(*m_rate_limit_indicator);
m_scroll->add(*m_list);
m_main->add(*m_scroll);
m_main->add(m_completer);
m_main->add(*m_input);
m_main->add(*m_input_indicator);
m_main->add(*m_meta);
m_main->show();
}
@ -125,6 +141,7 @@ void ChatWindow::SetMessages(const std::set<Snowflake> &msgs) {
void ChatWindow::SetActiveChannel(Snowflake id) {
m_active_channel = id;
m_input_indicator->SetActiveChannel(id);
m_rate_limit_indicator->SetActiveChannel(id);
if (m_is_replying)
StopReplying();
}
@ -179,11 +196,16 @@ Snowflake ChatWindow::GetActiveChannel() const {
return m_active_channel;
}
void ChatWindow::OnInputSubmit(const Glib::ustring &text) {
bool ChatWindow::OnInputSubmit(const Glib::ustring &text) {
if (!m_rate_limit_indicator->CanSpeak())
return false;
if (m_active_channel.IsValid())
m_signal_action_chat_submit.emit(text, m_active_channel, m_replying_to); // m_replying_to is checked for invalid in the handler
if (m_is_replying)
StopReplying();
return true;
}
bool ChatWindow::OnKeyPressEvent(GdkEventKey *e) {

View File

@ -9,6 +9,7 @@ class ChatMessageHeader;
class ChatMessageItemContainer;
class ChatInput;
class ChatInputIndicator;
class RateLimitIndicator;
class ChatWindow {
public:
ChatWindow();
@ -43,7 +44,7 @@ protected:
Snowflake m_active_channel;
void OnInputSubmit(const Glib::ustring &text);
bool OnInputSubmit(const Glib::ustring &text);
bool OnKeyPressEvent(GdkEventKey *e);
void OnScrollEdgeOvershot(Gtk::PositionType pos);
@ -63,6 +64,8 @@ protected:
Completer m_completer;
ChatInputIndicator *m_input_indicator;
RateLimitIndicator *m_rate_limit_indicator;
Gtk::Box *m_meta;
public:
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_delete;

View File

@ -0,0 +1,110 @@
#include "ratelimitindicator.hpp"
#include "../abaddon.hpp"
#include <filesystem>
RateLimitIndicator::RateLimitIndicator()
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL) {
m_label.set_text("");
m_label.set_ellipsize(Pango::ELLIPSIZE_START);
m_label.set_valign(Gtk::ALIGN_END);
get_style_context()->add_class("ratelimit-indicator");
m_img.set_margin_start(7);
add(m_label);
add(m_img);
m_label.show();
if (!std::filesystem::exists("./res/clock.png")) return;
try {
const auto pixbuf = Gdk::Pixbuf::create_from_file("./res/clock.png");
int w, h;
GetImageDimensions(pixbuf->get_width(), pixbuf->get_height(), w, h, 20, 10);
m_img.property_pixbuf() = pixbuf->scale_simple(w, h, Gdk::INTERP_BILINEAR);
} catch (...) {}
Abaddon::Get().GetDiscordClient().signal_message_create().connect(sigc::mem_fun(*this, &RateLimitIndicator::OnMessageCreate));
}
void RateLimitIndicator::SetActiveChannel(Snowflake id) {
m_active_channel = id;
const auto channel = *Abaddon::Get().GetDiscordClient().GetChannel(m_active_channel);
if (channel.RateLimitPerUser.has_value())
m_rate_limit = *channel.RateLimitPerUser;
else
m_rate_limit = 0;
UpdateIndicator();
}
bool RateLimitIndicator::CanSpeak() const {
const auto rate_limit = GetRateLimit();
if (rate_limit == 0) return true;
const auto it = m_times.find(m_active_channel);
if (it == m_times.end())
return true;
const auto now = std::chrono::steady_clock::now();
const auto sec_diff = std::chrono::duration_cast<std::chrono::seconds>(now - it->second).count();
return sec_diff >= GetRateLimit();
}
int RateLimitIndicator::GetTimeLeft() const {
if (CanSpeak()) return 0;
auto it = m_times.find(m_active_channel);
if (it == m_times.end()) return 0;
const auto now = std::chrono::steady_clock::now();
const auto sec_diff = std::chrono::duration_cast<std::chrono::seconds>(now - it->second).count();
if (sec_diff >= GetRateLimit())
return 0;
else
return GetRateLimit() - sec_diff;
}
int RateLimitIndicator::GetRateLimit() const {
return m_rate_limit;
}
bool RateLimitIndicator::UpdateIndicator() {
if (const auto rate_limit = GetRateLimit(); rate_limit != 0) {
m_img.show();
auto &discord = Abaddon::Get().GetDiscordClient();
if (discord.HasAnyChannelPermission(discord.GetUserData().ID, m_active_channel, Permission::MANAGE_MESSAGES | Permission::MANAGE_CHANNELS)) {
m_label.set_text("You may bypass slowmode.");
set_has_tooltip(false);
} else {
const auto time_left = GetTimeLeft();
if (time_left > 0)
m_label.set_text(std::to_string(time_left) + "s");
else
m_label.set_text("");
set_tooltip_text("Slowmode is enabled. Members can send one message every " + std::to_string(rate_limit) + " seconds.");
}
} else {
m_img.hide();
m_label.set_text("");
set_has_tooltip(false);
}
if (m_connection)
m_connection.disconnect();
m_connection = Glib::signal_timeout().connect_seconds(sigc::mem_fun(*this, &RateLimitIndicator::UpdateIndicator), 1);
return false;
}
void RateLimitIndicator::OnMessageCreate(const Message &message) {
auto &discord = Abaddon::Get().GetDiscordClient();
if (message.Author.ID != discord.GetUserData().ID) return;
const bool can_bypass = discord.HasAnyChannelPermission(discord.GetUserData().ID, m_active_channel, Permission::MANAGE_MESSAGES | Permission::MANAGE_CHANNELS);
if (GetRateLimit() > 0 && !can_bypass) {
m_times[message.ChannelID] = std::chrono::steady_clock::now();
UpdateIndicator();
}
}

View File

@ -0,0 +1,29 @@
#pragma once
#include <gtkmm.h>
#include <unordered_map>
#include <chrono>
#include "../discord/message.hpp"
class RateLimitIndicator : public Gtk::Box {
public:
RateLimitIndicator();
void SetActiveChannel(Snowflake id);
// even tho this probably isnt the right place for this im gonna do it anyway to reduce coad
bool CanSpeak() const;
private:
int GetTimeLeft() const;
int GetRateLimit() const;
bool UpdateIndicator();
void OnMessageCreate(const Message &message);
Gtk::Image m_img;
Gtk::Label m_label;
sigc::connection m_connection;
int m_rate_limit;
Snowflake m_active_channel;
std::unordered_map<Snowflake, std::chrono::time_point<std::chrono::steady_clock>> m_times;
};

View File

@ -261,6 +261,14 @@ bool DiscordClient::HasGuildPermission(Snowflake user_id, Snowflake guild_id, Pe
return (base & perm) == perm;
}
bool DiscordClient::HasAnyChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const {
const auto channel = m_store.GetChannel(channel_id);
if (!channel.has_value()) return false;
const auto base = ComputePermissions(user_id, *channel->GuildID);
const auto overwrites = ComputeOverwrites(base, user_id, channel_id);
return (overwrites & perm) != Permission::NONE;
}
bool DiscordClient::HasChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const {
const auto channel = m_store.GetChannel(channel_id);
if (!channel.has_value()) return false;

View File

@ -94,6 +94,8 @@ public:
std::unordered_set<Snowflake> GetChannelsInGuild(Snowflake 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;

BIN
res/clock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB