Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 45 additions & 7 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1001,10 +1001,7 @@
"actions-retention-note": "Note: Moderation actions are retained for 1 - 12 months based on the configuration.",
"no-permission": "You don't have sufficient permissions to use this command.",
"panel-title": "User Panel: %u",
"panel-description": "Manage and view data for %u (%i). View a quick recap of their ping and moderation history, or delete all data stored for this user (Risky).",
"btn-history": "Ping history",
"btn-actions": "Actions history",
"btn-delete": "Delete all data (Risky)",
"panel-description": "Manage and view data for %u (%i). You can see the user's ping history, moderation actions, quick recap of both, and view data deletion options for this user.",
"list-protected-title": "Protected Users and Roles",
"list-protected-desc": "View all protected users and roles here. When someone pings one of these protected user(s)/role(s), a warning will be sent. Exceptions are when pinged by someone with a whitelisted role/as a whitelisted user or when it's sent in a whitelisted channel.",
"field-protected-users": "Protected Users",
Expand All @@ -1017,9 +1014,8 @@
"list-none": "None are configured.",
"modal-title": "Confirm data deletion for this user",
"modal-label": "Confirm data deletion by typing this phrase:",
"modal-phrase": "I understand that all data of this user will be deleted and that this action cannot be undone.",
"modal-phrase": "I understand that the data of this user will be deleted and that this action cannot be undone.",
"modal-failed": "The phrase you entered is incorrect. Data deletion cancelled.",
"modal-success-data-deletion": "All data for the user <@%u> (%u) has been deleted successfully",
"field-quick-history": "Quick history view (Last %w weeks)",
"field-quick-desc": "Pings history amount: %p\nModeration actions amount: %m",
"history-disabled": "History logging has been disabled by a bot-configurator.\nAre you (one of) the bot-configurators? You can enable history logging in the \"Data Storage\" tab in the 'ping-protection' module ^^",
Expand All @@ -1032,7 +1028,49 @@
"meme-grind": "Why are you even pinging yourself 5 times in a row? Anyways continue some more to possibly get the secret meme\n-# (good luck grinding, only a 1% chance of getting it and during testing I had it once after 83 pings)",
"label-jump": "Jump to Message",
"no-message-link": "This ping was blocked by AutoMod",
"list-entry-text": "%index. **Pinged %target** at %time\n%link"
"list-entry-text": "%index. **Pinged %target** at %time\n%link",
"punish-log-docs-title": "Troubleshooting",
"punish-log-docs-desc": "This issue is documented in the documentation - you can see how to fix this issue [in the documentation](https://docs.scnx.xyz/docs/custom-bot/modules/moderation/ping-protection/#troubleshooting). Please try the steps mentioned there before contacting support as it's very likely the steps mentioned will fix your issue ^^",
"log-fetch-mod-history-failed": "[Ping Protection] Failed to fetch moderation history for user %u: %e",
"log-warning-build-failed": "[Ping Protection] Failed to build the warning message: %e",
"log-warning-reply-failed": "[Ping Protection] Failed to send the warning message as a reply: %e",
"log-warning-send-failed": "[Ping Protection] Failed to send the fallback warning message in channel %c: %e",
"log-automod-channel-fetch-failed": "[Ping Protection] Failed to refresh the guild channel cache while syncing AutoMod: %e",
"log-automod-rule-delete-failed": "[Ping Protection] Failed to delete the native AutoMod rule: %e",
"log-automod-sync-failed": "[Ping Protection] AutoMod sync failed: %e",
"log-punish-log-send-failed": "[Ping Protection] Failed to send the punishment failure message: %e",
"log-modlog-create-failed": "[Ping Protection] Failed to store the moderation log for user %u: %e",
"log-ping-history-create-failed": "[Ping Protection] Failed to store ping history for user %u: %e",
"log-recent-mod-check-failed": "[Ping Protection] Failed to check recent moderation actions for user %u: %e",
"panel-ph": "Select an option",
"panel-opt-over": "Overview",
"panel-opt-hist": "Ping History",
"panel-opt-actions": "Moderation History",
"panel-opt-delete": "Data Deletion",
"panel-deletion-title": "Data Deletion: %u",
"panel-deletion-desc": "⚠️ DANGEROUS AREA ⚠️\nYou are now entering a dangerous zone. At this place, you are able to delete specific or all data for the selected user. These actions ***CANNOT BE UNDONE*** and should only be used if you are absolutely sure about what you are doing.\nIf you are unsure, click 'Go Back' from the dropdown now.\nUse the dropdown below to choose which data you want to delete or delete all data. Choose wisely and gracefully.",
"panel-deletion-placeholder": "Select a deletion option",
"panel-opt-back": "Go back",
"panel-opt-del-pings": "Ping History Deletion",
"panel-opt-del-actions": "Moderation History Deletion",
"panel-opt-del-all": "Delete All Data",
"panel-deletion-cooldown-active": "Data deletion is currently blocked for this user because of a recent %type deletion. Deletion will be available again at %time.",
"del-type-pings": "ping history",
"del-type-actions": "moderation history",
"del-type-all": "full data",
"del-type-unknown": "data",
"del-all-admin-only": "Only users with Administrator permissions can delete all stored data for a user.",
"err-del-cooldown": "Data deletion for this user is currently on cooldown because of a recent %time deletion. You can delete data again at %until.",
"del-all-title": "Confirm full data deletion",
"del-all-desc": "You are about to delete ALL data for this user. Reminder that this ***cannot be undone***. This is the last chance to back out. If you are sure, click the button below.\nThis action will automatically cancel in 30 seconds.",
"btn-conf-del": "Confirm deletion",
"btn-cancel": "Cancel",
"succ-del-canc": "Data deletion cancelled.",
"err-del-time": "⏳ Data deletion timed out and was cancelled. Please try again if you still want to delete data for this user.",
"succ-del-tgt": "The selected %type data was deleted successfully. Deletion for this user is now on cooldown until %until.",
"succ-del-all": "All stored Ping Protection data for this user was deleted successfully. Deletion for this user is now on cooldown until %until.",
"log-del-type": "[Ping Protection] Deleted %type data for user %target by %admin.",
"log-del-all": "[Ping Protection] Deleted all stored data for user %target by %admin."
},
"betterstatus": {
"command-description": "Change the bot's status",
Expand Down
115 changes: 31 additions & 84 deletions modules/ping-protection/commands/ping-protection.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
const {
fetchModHistory,
getPingCountInWindow,
generateHistoryResponse,
generateActionsResponse
const {
generateHistoryResponse,
generateActionsResponse,
generateUserPanel
} = require('../ping-protection');
const {localize} = require('../../../src/functions/localize');
const {truncate, safeSetFooter} = require('../../../src/functions/helpers');
const {
ActionRowBuilder,
ButtonBuilder,
EmbedBuilder,
ButtonStyle,
MessageFlags
} = require('discord.js');
const { localize } = require('../../../src/functions/localize');
const { truncate } = require('../../../src/functions/helpers');
const { EmbedBuilder, MessageFlags } = require('discord.js');

module.exports.run = async function (interaction) {
const group = interaction.options.getSubcommandGroup(false);
Expand All @@ -26,76 +19,30 @@ module.exports.run = async function (interaction) {

// Handles subcommands
module.exports.subcommands = {
'user': {
'history': async function (interaction) {
const user = interaction.options.getUser('user');
const payload = await generateHistoryResponse(interaction.client, user.id, 1);
await interaction.reply({
...payload,
flags: MessageFlags.Ephemeral
});
},
'actions-history': async function (interaction) {
const user = interaction.options.getUser('user');
const payload = await generateActionsResponse(interaction.client, user.id, 1);
await interaction.reply({
...payload,
flags: MessageFlags.Ephemeral
});
},
'panel': async function (interaction) {
const user = interaction.options.getUser('user');
const pingerId = user.id;
const storageConfig = interaction.client.configurations['ping-protection']['storage'];
const retentionWeeks = (storageConfig && storageConfig.pingHistoryRetention)
? storageConfig.pingHistoryRetention
: 12;
const timeframeDays = retentionWeeks * 7;

const pingCount = await getPingCountInWindow(interaction.client, pingerId, timeframeDays);
const modData = await fetchModHistory(interaction.client, pingerId, 1, 1000);

const row = new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId(`ping-protection_history_${user.id}`)
.setLabel(localize('ping-protection', 'btn-history'))
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId(`ping-protection_actions_${user.id}`)
.setLabel(localize('ping-protection', 'btn-actions'))
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId(`ping-protection_delete_${user.id}`)
.setLabel(localize('ping-protection', 'btn-delete'))
.setStyle(ButtonStyle.Danger)
);

const embed = new EmbedBuilder()
.setTitle(localize('ping-protection', 'panel-title', {u: user.tag}))
.setDescription(localize('ping-protection', 'panel-description', {
u: user.toString(),
i: user.id
}))
.setColor('Blue')
.setThumbnail(user.displayAvatarURL({dynamic: true}))
.addFields([{
name: localize('ping-protection', 'field-quick-history', {w: retentionWeeks}),
value: localize('ping-protection', 'field-quick-desc', {
p: pingCount,
m: modData.total
}),
inline: false
}]);

safeSetFooter(embed, interaction.client);
if (!interaction.client.strings.disableFooterTimestamp) embed.setTimestamp();

await interaction.reply({
embeds: [embed.toJSON()],
components: [row.toJSON()],
flags: MessageFlags.Ephemeral
});
}
'user': {
'history': async function (interaction) {
const user = interaction.options.getUser('user');
const payload = await generateHistoryResponse(interaction.client, user.id, 1);
await interaction.reply({ ...payload, flags: MessageFlags.Ephemeral });
},
'actions-history': async function (interaction) {
const user = interaction.options.getUser('user');
const payload = await generateActionsResponse(interaction.client, user.id, 1);
await interaction.reply({ ...payload, flags: MessageFlags.Ephemeral });
},
'panel': async function (interaction) {
const user = interaction.options.getUser('user');
const payload = await generateUserPanel(interaction.client, user);

await interaction.reply({
...payload,
flags: MessageFlags.Ephemeral
});
}
},
'list': {
'protected': async function (interaction) {
await listHandler(interaction, 'protected');
},
'list': {
'protected': async function (interaction) {
Expand Down
23 changes: 18 additions & 5 deletions modules/ping-protection/configs/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@
"description": "Pings in these channels are ignored.",
"type": "array",
"content": "channelID",
"default": []
"default": [],
"channelTypes": [
"GUILD_TEXT",
"GUILD_NEWS",
"GUILD_CATEGORY"
]
},
{
"name": "ignoredUsers",
Expand Down Expand Up @@ -115,16 +120,24 @@
{
"name": "enableAutomod",
"category": "automod",
"humanName": "Enable automod",
"description": "If enabled, the bot will utilise Discord's native AutoMod to block the message with a ping of a protected user/role.",
"humanName": {
"en": "Enable AutoMod"
},
"description": {
"en": "If enabled, the bot will utilise Discord's native AutoMod to block the message with a ping of a protected user/role. Warning: AutoMod does not support whitelisted categories due to limitations in Discord's AutoMod system - instead, it will still block the message but not log it in the history."
},
"type": "boolean",
"default": true
},
{
"name": "autoModLogChannel",
"category": "automod",
"humanName": "AutoMod Log Channel",
"description": "Channel where AutoMod alerts are sent.",
"humanName": {
"en": "AutoMod Log Channel"
},
"description": {
"en": "Channel where AutoMod alerts are sent. It is recommended to keep these in a private channel."
},
"type": "channelID",
"default": "",
"channelTypes": [
Expand Down
28 changes: 28 additions & 0 deletions modules/ping-protection/configs/moderation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,33 @@
"type": "integer",
"default": 10
},
{
"name": "enableRolePingThresholds",
"humanName": {
"en": "Enable role-based ping thresholds"
},
"description": {
"en": "If enabled, specific roles can have custom ping thresholds for this moderation action. This also allows specific roles to be exempted from this specific action."
},
"type": "boolean",
"default": false
},
{
"name": "rolePingThresholds",
"humanName": {
"en": "Role-based ping thresholds"
},
"description": {
"en": "Set custom ping thresholds per role for this moderation action. If a user has multiple configured roles, the value of their highest configured role is used. Setting a role to 0 exempts that role from this action - exempted roles also override any other role's threshold."
},
"type": "keyed",
"content": {
"key": "roleID",
"value": "integer"
},
"default": {},
"dependsOn": "enableRolePingThresholds"
},
{
"name": "useCustomTimeframe",
"humanName": "Use a custom timeframe",
Expand Down Expand Up @@ -60,6 +87,7 @@
"humanName": "Action log message",
"description": "The message that will be sent when a user is punished for pinging protected users/roles.",
"type": "string",
"dependsOn": "enableActionLogging",
"allowEmbed": true,
"params": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const {processPing} = require('../ping-protection');
const { processPing, isWhitelistedChannel } = require('../ping-protection');

// Handles auto mod actions
module.exports.run = async function (client, execution) {
Expand All @@ -16,6 +16,8 @@ module.exports.run = async function (client, execution) {
if (!originChannel && execution.channelId) {
originChannel = await execution.guild.channels.fetch(execution.channelId).catch(() => null);
}
if (isWhitelistedChannel(config, originChannel)) return;

const memberToPunish = await execution.guild.members.fetch(execution.userId).catch(() => null);

if (!isProtected && config.protectAllUsersWithProtectedRole) {
Expand Down
Loading
Loading