Skip to content

Commit c60b20d

Browse files
committed
Member listeners and automod rule fixes
1 parent 0a68d5a commit c60b20d

File tree

10 files changed

+321
-29
lines changed

10 files changed

+321
-29
lines changed

config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"vc_chat": 854429524657176601
2828
},
2929
"log_channel_ids": {
30+
"welcome": 854429524477476864,
3031
"suggestion_list": 854429524798996483,
3132
"tickets": 854429524798996480,
3233
"staff_news": 854429525147385895,

src/command_modules/moderation.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ dpp::task<> moderation::inviteinfo(const dpp::slashcommand_t &event) {
175175
if (invite.expires_at == 0) {
176176
embed.add_field("Expires", "Never", false);
177177
} else {
178-
embed.add_field("Expires", std::format("<t:{}>", invite.expires_at), false);
178+
embed.add_field("Expires", std::format("<t:{}:R>", invite.expires_at), false);
179179
}
180180
co_await thinking;
181181
event.edit_original_response(dpp::message(embed));

src/listeners/automod_rules.cpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ void automod_rules::add_rule_description_fields(dpp::embed& embed, const dpp::au
130130
}
131131
}
132132

133-
dpp::task<> automod_rules::on_automod_rule_add(const dpp::automod_rule_create_t &event, const nlohmann::json& config) {
134-
util::AUTOMOD_RULE_CACHE.push(event.created);
133+
dpp::task<> automod_rules::on_automod_rule_add(const dpp::automod_rule_create_t &event, const nlohmann::json& config, std::vector<dpp::automod_rule>& automod_rules) {
134+
automod_rules.push_back(event.created);
135135
dpp::confirmation_callback_t user_conf = co_await event.owner->co_user_get_cached(event.created.creator_id);
136136
dpp::user_identified user;
137137
if (user_conf.is_error()) {
@@ -147,22 +147,26 @@ dpp::task<> automod_rules::on_automod_rule_add(const dpp::automod_rule_create_t
147147
event.owner->message_create(dpp::message(config["log_channel_ids"]["discord_updates"], embed));
148148
}
149149

150-
void automod_rules::on_automod_rule_remove(const dpp::automod_rule_delete_t &event, const nlohmann::json& config) {
150+
void automod_rules::on_automod_rule_remove(const dpp::automod_rule_delete_t &event, const nlohmann::json& config, std::vector<dpp::automod_rule>& automod_rules) {
151151
dpp::embed embed = dpp::embed().set_color(dpp::colors::red).set_title("Automod Rule Deleted")
152152
.add_field("Rule Name", event.deleted.name, true);
153153
add_rule_description_fields(embed, event.deleted);
154154
event.owner->message_create(dpp::message(config["log_channel_ids"]["discord_updates"], embed));
155+
// Delete rule from cache if possible
156+
if (const auto it = std::ranges::find(automod_rules, event.deleted); it != automod_rules.end()) {
157+
automod_rules.erase(it);
158+
}
155159
}
156160

157-
void automod_rules::on_automod_rule_edit(const dpp::automod_rule_update_t &event, const nlohmann::json& config) {
161+
void automod_rules::on_automod_rule_edit(const dpp::automod_rule_update_t &event, const nlohmann::json& config, std::vector<dpp::automod_rule>& automod_rules) {
158162
dpp::embed embed = dpp::embed().set_color(0x00A0A0).set_title("Automod Rule Edited")
159-
.add_field("Rule Name", event.updated.name, true);
163+
.add_field("Rule Name", event.updated.name, false);
160164
// Find rule in cache
161-
auto old_rule = util::AUTOMOD_RULE_CACHE.begin();
162-
while (old_rule != util::AUTOMOD_RULE_CACHE.end() && old_rule->id != event.updated.id) ++old_rule;
165+
auto old_rule = automod_rules.begin();
166+
while (old_rule != automod_rules.end() && old_rule->id != event.updated.id) ++old_rule;
163167
// If rule cannot be found, add edited version to cache and exit
164-
if (old_rule == util::AUTOMOD_RULE_CACHE.end()) {
165-
util::AUTOMOD_RULE_CACHE.push(event.updated);
168+
if (old_rule == automod_rules.end()) {
169+
automod_rules.push_back(event.updated);
166170
embed.set_footer(dpp::embed_footer().set_text("Limited info available (old rule not in cache)"));
167171
event.owner->message_create(dpp::message(config["log_channel_ids"]["discord_updates"], embed));
168172
return;
@@ -403,8 +407,8 @@ void automod_rules::on_automod_rule_edit(const dpp::automod_rule_update_t &event
403407
if (new_actions != old_actions) {
404408
old_actions.pop_back();
405409
new_actions.pop_back();
406-
embed.add_field("Old list of actions to take", old_actions, false);
407-
embed.add_field("New list of actions to take", new_actions, false);
410+
embed.add_field("Old list of actions to take", old_actions, true);
411+
embed.add_field("New list of actions to take", new_actions, true);
408412
}
409413

410414
if (event.updated.enabled != old_rule->enabled) {

src/listeners/automod_rules.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
namespace automod_rules {
1919
void add_rule_description_fields(dpp::embed& embed, const dpp::automod_rule& rule);
20-
dpp::task<> on_automod_rule_add(const dpp::automod_rule_create_t &event, const nlohmann::json& config);
21-
void on_automod_rule_remove(const dpp::automod_rule_delete_t &event, const nlohmann::json& config);
22-
void on_automod_rule_edit(const dpp::automod_rule_update_t &event, const nlohmann::json& config);
20+
dpp::task<> on_automod_rule_add(const dpp::automod_rule_create_t &event, const nlohmann::json& config, std::vector<dpp::automod_rule>& automod_rules);
21+
void on_automod_rule_remove(const dpp::automod_rule_delete_t &event, const nlohmann::json& config, std::vector<dpp::automod_rule>& automod_rules);
22+
void on_automod_rule_edit(const dpp::automod_rule_update_t &event, const nlohmann::json& config, std::vector<dpp::automod_rule>& automod_rules);
2323
}

src/listeners/guild.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* guild: Handlers for events related to the guild
2+
* Copyright 2025 Ben Westover <[email protected]>
3+
*
4+
* This program is free software: you can redistribute it and/or modify it
5+
* under the terms of the GNU General Public License as published by the Free
6+
* Software Foundation, either version 3 of the License, or (at your option)
7+
* any later version. This program is distributed in the hope that it will be
8+
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
10+
* Public License for more details.
11+
*
12+
* You should have received a copy of the GNU General Public License along with
13+
* this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
#include "guild.h"
16+
#include "../util.h"
17+
18+
void guild::on_invite_created(const dpp::invite_create_t &event, const nlohmann::json& config, std::vector<dpp::invite>& invites) {
19+
invites.push_back(event.created_invite);
20+
21+
dpp::embed embed = dpp::embed().set_color(dpp::colors::green).set_thumbnail(event.created_invite.inviter.get_avatar_url())
22+
.set_title("Invite Created")
23+
.add_field("Invite Creator", event.created_invite.inviter.username, true)
24+
.add_field("User ID", event.created_invite.inviter.id.str(), true)
25+
.add_field("Invite Code", event.created_invite.code, false)
26+
.add_field("Invite Channel", event.created_invite.destination_channel.get_mention(), true);
27+
if (event.created_invite.expires_at == 0) {
28+
embed.add_field("Expires", "Never", true);
29+
} else {
30+
embed.add_field("Expires", std::format("<t:{}:R>", event.created_invite.expires_at), true);
31+
}
32+
event.owner->message_create(dpp::message(config["log_channel_ids"]["invites"], embed));
33+
}
34+
35+
void guild::on_invite_deleted(const dpp::invite_delete_t &event, const nlohmann::json& config, std::vector<dpp::invite>& invites) {
36+
37+
dpp::embed embed = dpp::embed().set_color(dpp::colors::red).set_title("Invite Deleted");
38+
// Find invite in cache
39+
auto invite = invites.begin();
40+
while (invite != invites.end() && invite->code != event.deleted_invite.code) ++invite;
41+
if (invite == invites.end()) {
42+
embed.add_field("Invite Code", event.deleted_invite.code, false);
43+
embed.set_footer(dpp::embed_footer().set_text("Invite not found in cache; limited info available."));
44+
} else {
45+
embed.add_field("Invite Creator", invite->inviter.username, true)
46+
.add_field("User ID", invite->inviter.id.str(), true)
47+
.add_field("Invite Code", invite->code, false)
48+
.add_field("Invite Channel", invite->destination_channel.get_mention(), true)
49+
.add_field("Uses", std::to_string(invite->uses), true);
50+
// Delete invite from cache
51+
invites.erase(invite);
52+
}
53+
event.owner->message_create(dpp::message(config["log_channel_ids"]["invites"], embed));
54+
}

src/listeners/guild.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/* guild: Handlers for events related to the guild
2+
* Copyright 2025 Ben Westover <[email protected]>
3+
*
4+
* This program is free software: you can redistribute it and/or modify it
5+
* under the terms of the GNU General Public License as published by the Free
6+
* Software Foundation, either version 3 of the License, or (at your option)
7+
* any later version. This program is distributed in the hope that it will be
8+
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
10+
* Public License for more details.
11+
*
12+
* You should have received a copy of the GNU General Public License along with
13+
* this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
#pragma once
16+
#include <dpp/dpp.h>
17+
18+
namespace guild {
19+
void on_invite_created(const dpp::invite_create_t &event, const nlohmann::json& config, std::vector<dpp::invite>& invites);
20+
void on_invite_deleted(const dpp::invite_delete_t &event, const nlohmann::json& config, std::vector<dpp::invite>& invites);
21+
}

src/listeners/members.cpp

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/* members: Handlers for events involving server members
2+
* Copyright 2025 Ben Westover <[email protected]>
3+
*
4+
* This program is free software: you can redistribute it and/or modify it
5+
* under the terms of the GNU General Public License as published by the Free
6+
* Software Foundation, either version 3 of the License, or (at your option)
7+
* any later version. This program is distributed in the hope that it will be
8+
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
10+
* Public License for more details.
11+
*
12+
* You should have received a copy of the GNU General Public License along with
13+
* this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
#include "members.h"
16+
#include "../util.h"
17+
18+
dpp::task<dpp::invite> members::findinvite(dpp::cluster* bot, const dpp::snowflake guild, std::vector<dpp::invite>& invites) {
19+
dpp::invite invite_used = dpp::invite();
20+
invite_used.code = "unknown";
21+
22+
// Get current invite list
23+
dpp::confirmation_callback_t invites_conf = co_await bot->co_guild_get_invites(guild);
24+
if (invites_conf.is_error()) {
25+
co_return invite_used;
26+
}
27+
// Look for any invite with more uses than the cache has
28+
for (size_t i = 0; i < invites.size(); i++) {
29+
for (const dpp::invite& invite : std::get<dpp::invite_map>(invites_conf.value) | std::views::values) {
30+
if (invite.code == invites[i].code && invite.uses > invites[i].uses) {
31+
// Update invite in cache
32+
invites[i] = invite;
33+
34+
invite_used = invite;
35+
co_return invite_used;
36+
}
37+
}
38+
}
39+
co_return invite_used;
40+
}
41+
42+
dpp::task<> members::on_kick(const dpp::guild_audit_log_entry_create_t& event, const nlohmann::json& config) {
43+
dpp::confirmation_callback_t user_conf = co_await event.owner->co_user_get_cached(event.entry.target_id);
44+
dpp::confirmation_callback_t mod_conf = co_await event.owner->co_user_get_cached(event.entry.user_id);
45+
46+
dpp::embed embed = dpp::embed().set_color(dpp::colors::red).set_title("Manual Kick");
47+
if (!user_conf.is_error()) {
48+
dpp::user_identified user = std::get<dpp::user_identified>(user_conf.value);
49+
embed.set_thumbnail(user.get_avatar_url()).add_field("User kicked", user.username, true);
50+
}
51+
embed.add_field("User ID", event.entry.target_id.str(), true);
52+
53+
if (mod_conf.is_error()) {
54+
embed.add_field("Moderator ID", event.entry.user_id.str(), false);
55+
} else {
56+
dpp::user_identified mod = std::get<dpp::user_identified>(mod_conf.value);
57+
embed.add_field("Kicked by", mod.global_name, false);
58+
}
59+
60+
if (!event.entry.reason.empty()) {
61+
embed.add_field("Reason", event.entry.reason, false);
62+
}
63+
event.owner->message_create(dpp::message(config["log_channel_ids"]["mod_log"], embed));
64+
}
65+
66+
dpp::task<> members::on_ban(const dpp::guild_audit_log_entry_create_t& event, const nlohmann::json& config) {
67+
dpp::confirmation_callback_t user_conf = co_await event.owner->co_user_get_cached(event.entry.target_id);
68+
dpp::confirmation_callback_t mod_conf = co_await event.owner->co_user_get_cached(event.entry.user_id);
69+
70+
dpp::embed embed = dpp::embed().set_color(dpp::colors::red).set_title("Manual Ban");
71+
if (!user_conf.is_error()) {
72+
dpp::user_identified user = std::get<dpp::user_identified>(user_conf.value);
73+
embed.set_thumbnail(user.get_avatar_url()).add_field("User banned", user.username, true);
74+
}
75+
embed.add_field("User ID", event.entry.target_id.str(), true);
76+
77+
if (mod_conf.is_error()) {
78+
embed.add_field("Moderator ID", event.entry.user_id.str(), false);
79+
} else {
80+
dpp::user_identified mod = std::get<dpp::user_identified>(mod_conf.value);
81+
embed.add_field("Banned by", mod.global_name, false);
82+
}
83+
84+
if (!event.entry.reason.empty()) {
85+
embed.add_field("Reason", event.entry.reason, false);
86+
}
87+
event.owner->message_create(dpp::message(config["log_channel_ids"]["mod_log"], embed));
88+
}
89+
90+
dpp::task<> members::on_unban(const dpp::guild_audit_log_entry_create_t& event, const nlohmann::json& config) {
91+
dpp::confirmation_callback_t user_conf = co_await event.owner->co_user_get_cached(event.entry.target_id);
92+
dpp::confirmation_callback_t mod_conf = co_await event.owner->co_user_get_cached(event.entry.user_id);
93+
94+
dpp::embed embed = dpp::embed().set_color(dpp::colors::green).set_title("Ban Manually Removed");
95+
if (!user_conf.is_error()) {
96+
dpp::user_identified user = std::get<dpp::user_identified>(user_conf.value);
97+
embed.set_thumbnail(user.get_avatar_url()).add_field("User unbanned", user.username, true);
98+
}
99+
embed.add_field("User ID", event.entry.target_id.str(), true);
100+
101+
if (mod_conf.is_error()) {
102+
embed.add_field("Moderator ID", event.entry.user_id.str(), false);
103+
} else {
104+
dpp::user_identified mod = std::get<dpp::user_identified>(mod_conf.value);
105+
embed.add_field("Ban removed by", mod.global_name, false);
106+
}
107+
108+
if (!event.entry.reason.empty()) {
109+
embed.add_field("Reason", event.entry.reason, false);
110+
}
111+
event.owner->message_create(dpp::message(config["log_channel_ids"]["mod_log"], embed));
112+
}
113+
114+
dpp::task<> members::on_join(const dpp::guild_member_add_t& event, const nlohmann::json& config, std::vector<dpp::invite>& invites) {
115+
dpp::embed welcome_embed = dpp::embed().set_color(dpp::colors::green).set_title("New Member")
116+
.set_thumbnail(event.added.get_user()->get_avatar_url())
117+
.set_description(event.added.get_user()->global_name + " has joined Tech Support Central!");
118+
event.owner->message_create(dpp::message(config["log_channel_ids"]["welcome"], welcome_embed));
119+
120+
dpp::embed log_embed = dpp::embed().set_color(dpp::colors::green).set_title("Member Joined")
121+
.set_thumbnail(event.added.get_user()->get_avatar_url())
122+
.set_description(event.added.get_user()->username)
123+
.add_field("User ID", event.added.user_id.str(), false)
124+
.add_field("Account Created:", std::format("<t:{}>",
125+
static_cast<time_t>(event.added.get_user()->get_creation_time())), false);
126+
dpp::invite invite_used = co_await findinvite(event.owner, event.adding_guild.id, invites);
127+
if (invite_used.code != "unknown") {
128+
log_embed.add_field("Invite code", invite_used.code, false);
129+
if (invite_used.code == "2vwUBmhM8U") {
130+
log_embed.add_field("Invite creator", "top.gg", true);
131+
} else {
132+
log_embed.add_field("Invite creator", invite_used.inviter.username, true);
133+
}
134+
log_embed.add_field("Invite uses", std::to_string(invite_used.uses), true);
135+
}
136+
event.owner->message_create(dpp::message(config["log_channel_ids"]["member_log"], log_embed));
137+
}
138+
139+
void members::on_leave(const dpp::guild_member_remove_t& event, const nlohmann::json& config) {
140+
dpp::embed embed = dpp::embed().set_color(dpp::colors::red).set_title("Member Left")
141+
.set_thumbnail(event.removed.get_avatar_url())
142+
.set_description(event.removed.username + " has left Tech Support Central.")
143+
.set_footer(dpp::embed_footer().set_text(std::string("User ID: ") + event.removed.id.str()));
144+
event.owner->message_create(dpp::message(config["log_channel_ids"]["member_log"], embed));
145+
}

src/listeners/members.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* members: Handlers for events involving server members
2+
* Copyright 2025 Ben Westover <[email protected]>
3+
*
4+
* This program is free software: you can redistribute it and/or modify it
5+
* under the terms of the GNU General Public License as published by the Free
6+
* Software Foundation, either version 3 of the License, or (at your option)
7+
* any later version. This program is distributed in the hope that it will be
8+
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
10+
* Public License for more details.
11+
*
12+
* You should have received a copy of the GNU General Public License along with
13+
* this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
#pragma once
16+
#include <dpp/dpp.h>
17+
18+
namespace members {
19+
/**
20+
* Find the most recent invite used.
21+
* @param bot Bot cluster to use to query guild invites
22+
* @param guild Guild to search invites of
23+
* @param invites List of invites to this guild (before the most recent invite use)
24+
* @return The most recently used invite, or an empty invite with code "unknown"
25+
*/
26+
dpp::task<dpp::invite> findinvite(dpp::cluster* bot, dpp::snowflake guild, std::vector<dpp::invite>& invites);
27+
28+
dpp::task<> on_kick(const dpp::guild_audit_log_entry_create_t& event, const nlohmann::json& config);
29+
dpp::task<> on_ban(const dpp::guild_audit_log_entry_create_t& event, const nlohmann::json& config);
30+
dpp::task<> on_unban(const dpp::guild_audit_log_entry_create_t& event, const nlohmann::json& config);
31+
dpp::task<> on_join(const dpp::guild_member_add_t& event, const nlohmann::json& config, std::vector<dpp::invite>& invites);
32+
void on_leave(const dpp::guild_member_remove_t& event, const nlohmann::json& config);
33+
}

0 commit comments

Comments
 (0)