mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-03-15 05:41:51 +00:00
Merge branch '240811_application_commands_merge_2' into next
This commit is contained in:
commit
43b8017985
279 changed files with 6192 additions and 3044 deletions
|
@ -1,6 +1,6 @@
|
|||
import { ApiPermissions } from "@zeppelinbot/shared/apiPermissions.js";
|
||||
import express, { Request, Response } from "express";
|
||||
import jsYaml from "js-yaml";
|
||||
import { YAMLException } from "js-yaml";
|
||||
import moment from "moment-timezone";
|
||||
import { Queue } from "../../Queue.js";
|
||||
import { validateGuildConfig } from "../../configValidator.js";
|
||||
|
@ -15,8 +15,6 @@ import { ObjectAliasError } from "../../utils/validateNoObjectAliases.js";
|
|||
import { hasGuildPermission, requireGuildPermission } from "../permissions.js";
|
||||
import { clientError, ok, serverError, unauthorized } from "../responses.js";
|
||||
|
||||
const YAMLException = jsYaml.YAMLException;
|
||||
|
||||
const apiPermissionAssignments = new ApiPermissionAssignments();
|
||||
const auditLog = new ApiAuditLog();
|
||||
|
||||
|
|
|
@ -28,10 +28,10 @@ app.use(multer().none());
|
|||
|
||||
const rootRouter = express.Router();
|
||||
|
||||
initAuth(app);
|
||||
initGuildsAPI(app);
|
||||
initArchives(app);
|
||||
initDocs(app);
|
||||
initAuth(rootRouter);
|
||||
initGuildsAPI(rootRouter);
|
||||
initArchives(rootRouter);
|
||||
initDocs(rootRouter);
|
||||
|
||||
// Default route
|
||||
rootRouter.get("/", (req, res) => {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { ConfigValidationError, GuildPluginBlueprint, PluginConfigManager } from "knub";
|
||||
import moment from "moment-timezone";
|
||||
import { BaseConfig, ConfigValidationError, GuildPluginBlueprint, PluginConfigManager } from "knub";
|
||||
import { ZodError } from "zod";
|
||||
import { availableGuildPlugins } from "./plugins/availablePlugins.js";
|
||||
import { ZeppelinGuildConfig, zZeppelinGuildConfig } from "./types.js";
|
||||
import { zZeppelinGuildConfig } from "./types.js";
|
||||
import { formatZodIssue } from "./utils/formatZodIssue.js";
|
||||
|
||||
const pluginNameToPlugin = new Map<string, GuildPluginBlueprint<any, any>>();
|
||||
|
@ -16,14 +15,7 @@ export async function validateGuildConfig(config: any): Promise<string | null> {
|
|||
return validationResult.error.issues.map(formatZodIssue).join("\n");
|
||||
}
|
||||
|
||||
const guildConfig = config as ZeppelinGuildConfig;
|
||||
|
||||
if (guildConfig.timezone) {
|
||||
const validTimezones = moment.tz.names();
|
||||
if (!validTimezones.includes(guildConfig.timezone)) {
|
||||
return `Invalid timezone: ${guildConfig.timezone}`;
|
||||
}
|
||||
}
|
||||
const guildConfig = config as BaseConfig;
|
||||
|
||||
if (guildConfig.plugins) {
|
||||
for (const [pluginName, pluginOptions] of Object.entries(guildConfig.plugins)) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { In, InsertResult, Repository } from "typeorm";
|
||||
import { FindOptionsWhere } from "typeorm/find-options/FindOptionsWhere";
|
||||
import { Queue } from "../Queue.js";
|
||||
import { chunkArray } from "../utils.js";
|
||||
import { BaseGuildRepository } from "./BaseGuildRepository.js";
|
||||
|
@ -73,34 +74,69 @@ export class GuildCases extends BaseGuildRepository {
|
|||
});
|
||||
}
|
||||
|
||||
async getByUserId(userId: string): Promise<Case[]> {
|
||||
async getByUserId(
|
||||
userId: string,
|
||||
filters: Omit<FindOptionsWhere<Case>, "guild_id" | "user_id"> = {},
|
||||
): Promise<Case[]> {
|
||||
return this.cases.find({
|
||||
relations: this.getRelations(),
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
user_id: userId,
|
||||
...filters,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getRecentByUserId(userId: string, count: number, skip = 0): Promise<Case[]> {
|
||||
return this.cases.find({
|
||||
relations: this.getRelations(),
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
user_id: userId,
|
||||
},
|
||||
skip,
|
||||
take: count,
|
||||
order: {
|
||||
case_number: "DESC",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getTotalCasesByModId(modId: string): Promise<number> {
|
||||
async getTotalCasesByModId(
|
||||
modId: string,
|
||||
filters: Omit<FindOptionsWhere<Case>, "guild_id" | "mod_id" | "is_hidden"> = {},
|
||||
): Promise<number> {
|
||||
return this.cases.count({
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
mod_id: modId,
|
||||
is_hidden: false,
|
||||
...filters,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getRecentByModId(modId: string, count: number, skip = 0): Promise<Case[]> {
|
||||
return this.cases.find({
|
||||
relations: this.getRelations(),
|
||||
where: {
|
||||
async getRecentByModId(
|
||||
modId: string,
|
||||
count: number,
|
||||
skip = 0,
|
||||
filters: Omit<FindOptionsWhere<Case>, "guild_id" | "mod_id"> = {},
|
||||
): Promise<Case[]> {
|
||||
const where: FindOptionsWhere<Case> = {
|
||||
guild_id: this.guildId,
|
||||
mod_id: modId,
|
||||
is_hidden: false,
|
||||
},
|
||||
...filters,
|
||||
};
|
||||
|
||||
if (where.is_hidden === true) {
|
||||
delete where.is_hidden;
|
||||
}
|
||||
|
||||
return this.cases.find({
|
||||
relations: this.getRelations(),
|
||||
where,
|
||||
skip,
|
||||
take: count,
|
||||
order: {
|
||||
|
|
|
@ -44,15 +44,7 @@ import { availableGlobalPlugins, availableGuildPlugins } from "./plugins/availab
|
|||
import { setProfiler } from "./profiler.js";
|
||||
import { logRateLimit } from "./rateLimitStats.js";
|
||||
import { startUptimeCounter } from "./uptime.js";
|
||||
import {
|
||||
MINUTES,
|
||||
SECONDS,
|
||||
errorMessage,
|
||||
isDiscordAPIError,
|
||||
isDiscordHTTPError,
|
||||
sleep,
|
||||
successMessage,
|
||||
} from "./utils.js";
|
||||
import { MINUTES, SECONDS, errorMessage, isDiscordAPIError, isDiscordHTTPError, sleep, successMessage } from "./utils.js";
|
||||
import { DecayingCounter } from "./utils/DecayingCounter.js";
|
||||
import { enableProfiling } from "./utils/easyProfiler.js";
|
||||
import { loadYamlSafely } from "./utils/loadYamlSafely.js";
|
||||
|
@ -324,9 +316,27 @@ connect().then(async () => {
|
|||
if (row) {
|
||||
try {
|
||||
const loaded = loadYamlSafely(row.config);
|
||||
|
||||
if (loaded.success_emoji || loaded.error_emoji) {
|
||||
const deprecatedKeys = [] as string[];
|
||||
const exampleConfig = `plugins:\n common:\n config:\n success_emoji: "👍"\n error_emoji: "👎"`;
|
||||
|
||||
if (loaded.success_emoji) {
|
||||
deprecatedKeys.push("success_emoji");
|
||||
}
|
||||
|
||||
if (loaded.error_emoji) {
|
||||
deprecatedKeys.push("error_emoji");
|
||||
}
|
||||
|
||||
logger.warn(`Deprecated config properties found in "${key}": ${deprecatedKeys.join(", ")}`);
|
||||
logger.warn(`You can now configure those emojis in the "common" plugin config\n${exampleConfig}`);
|
||||
}
|
||||
|
||||
// Remove deprecated properties some may still have in their config
|
||||
delete loaded.success_emoji;
|
||||
delete loaded.error_emoji;
|
||||
|
||||
return loaded;
|
||||
} catch (err) {
|
||||
logger.error(`Error while loading config "${key}": ${err.message}`);
|
||||
|
|
|
@ -3,18 +3,18 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
ChatInputCommandInteraction,
|
||||
GuildMember,
|
||||
InteractionReplyOptions,
|
||||
Message,
|
||||
MessageCreateOptions,
|
||||
MessageMentionOptions,
|
||||
PermissionsBitField,
|
||||
TextBasedChannel,
|
||||
User,
|
||||
} from "discord.js";
|
||||
import { AnyPluginData, BasePluginData, CommandContext, ExtendedMatchParams, GuildPluginData, helpers } from "knub";
|
||||
import { logger } from "./logger.js";
|
||||
import { isStaff } from "./staff.js";
|
||||
import { TZeppelinKnub } from "./types.js";
|
||||
import { errorMessage, successMessage } from "./utils.js";
|
||||
import { Tail } from "./utils/typeUtils.js";
|
||||
|
||||
const { getMemberLevel } = helpers;
|
||||
|
@ -49,46 +49,57 @@ export async function hasPermission(
|
|||
return helpers.hasPermission(config, permission);
|
||||
}
|
||||
|
||||
export async function sendSuccessMessage(
|
||||
pluginData: AnyPluginData<any>,
|
||||
channel: TextBasedChannel,
|
||||
body: string,
|
||||
allowedMentions?: MessageMentionOptions,
|
||||
): Promise<Message | undefined> {
|
||||
const emoji = pluginData.fullConfig.success_emoji || undefined;
|
||||
const formattedBody = successMessage(body, emoji);
|
||||
const content: MessageCreateOptions = allowedMentions
|
||||
? { content: formattedBody, allowedMentions }
|
||||
: { content: formattedBody };
|
||||
|
||||
return channel
|
||||
.send({ ...content }) // Force line break
|
||||
.catch((err) => {
|
||||
const channelInfo = "guild" in channel ? `${channel.id} (${channel.guild.id})` : channel.id;
|
||||
logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`);
|
||||
return undefined;
|
||||
});
|
||||
export function isContextInteraction(
|
||||
context: TextBasedChannel | Message | User | ChatInputCommandInteraction,
|
||||
): context is ChatInputCommandInteraction {
|
||||
return "commandId" in context && !!context.commandId;
|
||||
}
|
||||
|
||||
export async function sendErrorMessage(
|
||||
pluginData: AnyPluginData<any>,
|
||||
channel: TextBasedChannel,
|
||||
body: string,
|
||||
allowedMentions?: MessageMentionOptions,
|
||||
): Promise<Message | undefined> {
|
||||
const emoji = pluginData.fullConfig.error_emoji || undefined;
|
||||
const formattedBody = errorMessage(body, emoji);
|
||||
const content: MessageCreateOptions = allowedMentions
|
||||
? { content: formattedBody, allowedMentions }
|
||||
: { content: formattedBody };
|
||||
export function isContextMessage(
|
||||
context: TextBasedChannel | Message | User | ChatInputCommandInteraction,
|
||||
): context is Message {
|
||||
return "content" in context || "embeds" in context;
|
||||
}
|
||||
|
||||
return channel
|
||||
.send({ ...content }) // Force line break
|
||||
.catch((err) => {
|
||||
const channelInfo = "guild" in channel ? `${channel.id} (${channel.guild.id})` : channel.id;
|
||||
logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`);
|
||||
return undefined;
|
||||
});
|
||||
export async function getContextChannel(
|
||||
context: TextBasedChannel | Message | User | ChatInputCommandInteraction,
|
||||
): Promise<TextBasedChannel> {
|
||||
if (isContextInteraction(context)) {
|
||||
// context is ChatInputCommandInteraction
|
||||
return context.channel!;
|
||||
} else if ("username" in context) {
|
||||
// context is User
|
||||
return await (context as User).createDM();
|
||||
} else if ("send" in context) {
|
||||
// context is TextBaseChannel
|
||||
return context as TextBasedChannel;
|
||||
} else {
|
||||
// context is Message
|
||||
return context.channel;
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendContextResponse(
|
||||
context: TextBasedChannel | Message | User | ChatInputCommandInteraction,
|
||||
response: string | Omit<MessageCreateOptions, "flags"> | InteractionReplyOptions,
|
||||
): Promise<Message> {
|
||||
if (isContextInteraction(context)) {
|
||||
const options = { ...(typeof response === "string" ? { content: response } : response), fetchReply: true };
|
||||
|
||||
return (
|
||||
context.replied
|
||||
? context.followUp(options)
|
||||
: context.deferred
|
||||
? context.editReply(options)
|
||||
: context.reply(options)
|
||||
) as Promise<Message>;
|
||||
}
|
||||
|
||||
if (typeof response !== "string" && "ephemeral" in response) {
|
||||
delete response.ephemeral;
|
||||
}
|
||||
|
||||
return (await getContextChannel(context)).send(response as string | Omit<MessageCreateOptions, "flags">);
|
||||
}
|
||||
|
||||
export function getBaseUrl(pluginData: AnyPluginData<any>) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { PluginOptions, guildPlugin } from "knub";
|
||||
import { GuildAutoReactions } from "../../data/GuildAutoReactions.js";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin.js";
|
||||
import { DisableAutoReactionsCmd } from "./commands/DisableAutoReactionsCmd.js";
|
||||
import { NewAutoReactionsCmd } from "./commands/NewAutoReactionsCmd.js";
|
||||
|
@ -50,4 +51,8 @@ export const AutoReactionsPlugin = guildPlugin<AutoReactionsPluginType>()({
|
|||
state.autoReactions = GuildAutoReactions.getGuildInstance(guild.id);
|
||||
state.cache = new Map();
|
||||
},
|
||||
|
||||
beforeStart(pluginData) {
|
||||
pluginData.state.common = pluginData.getPlugin(CommonPlugin);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { autoReactionsCmd } from "../types.js";
|
||||
|
||||
export const DisableAutoReactionsCmd = autoReactionsCmd({
|
||||
|
@ -14,12 +13,12 @@ export const DisableAutoReactionsCmd = autoReactionsCmd({
|
|||
async run({ message: msg, args, pluginData }) {
|
||||
const autoReaction = await pluginData.state.autoReactions.getForChannel(args.channelId);
|
||||
if (!autoReaction) {
|
||||
sendErrorMessage(pluginData, msg.channel, `Auto-reactions aren't enabled in <#${args.channelId}>`);
|
||||
void pluginData.state.common.sendErrorMessage(msg, `Auto-reactions aren't enabled in <#${args.channelId}>`);
|
||||
return;
|
||||
}
|
||||
|
||||
await pluginData.state.autoReactions.removeFromChannel(args.channelId);
|
||||
pluginData.state.cache.delete(args.channelId);
|
||||
sendSuccessMessage(pluginData, msg.channel, `Auto-reactions disabled in <#${args.channelId}>`);
|
||||
void pluginData.state.common.sendSuccessMessage(msg, `Auto-reactions disabled in <#${args.channelId}>`);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { PermissionsBitField } from "discord.js";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { canUseEmoji, customEmojiRegex, isEmoji } from "../../../utils.js";
|
||||
import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions.js";
|
||||
import { missingPermissionError } from "../../../utils/missingPermissionError.js";
|
||||
|
@ -25,9 +24,8 @@ export const NewAutoReactionsCmd = autoReactionsCmd({
|
|||
const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!;
|
||||
const missingPermissions = getMissingChannelPermissions(me, args.channel, requiredPermissions);
|
||||
if (missingPermissions) {
|
||||
sendErrorMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
pluginData.state.common.sendErrorMessage(
|
||||
msg,
|
||||
`Cannot set auto-reactions for that channel. ${missingPermissionError(missingPermissions)}`,
|
||||
);
|
||||
return;
|
||||
|
@ -35,7 +33,7 @@ export const NewAutoReactionsCmd = autoReactionsCmd({
|
|||
|
||||
for (const reaction of args.reactions) {
|
||||
if (!isEmoji(reaction)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "One or more of the specified reactions were invalid!");
|
||||
void pluginData.state.common.sendErrorMessage(msg, "One or more of the specified reactions were invalid!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -45,7 +43,10 @@ export const NewAutoReactionsCmd = autoReactionsCmd({
|
|||
if (customEmojiMatch) {
|
||||
// Custom emoji
|
||||
if (!canUseEmoji(pluginData.client, customEmojiMatch[2])) {
|
||||
sendErrorMessage(pluginData, msg.channel, "I can only use regular emojis and custom emojis from this server");
|
||||
pluginData.state.common.sendErrorMessage(
|
||||
msg,
|
||||
"I can only use regular emojis and custom emojis from this server",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -60,6 +61,6 @@ export const NewAutoReactionsCmd = autoReactionsCmd({
|
|||
|
||||
await pluginData.state.autoReactions.set(args.channel.id, finalReactions);
|
||||
pluginData.state.cache.delete(args.channel.id);
|
||||
sendSuccessMessage(pluginData, msg.channel, `Auto-reactions set for <#${args.channel.id}>`);
|
||||
void pluginData.state.common.sendSuccessMessage(msg, `Auto-reactions set for <#${args.channel.id}>`);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub";
|
||||
import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub";
|
||||
import z from "zod";
|
||||
import { GuildAutoReactions } from "../../data/GuildAutoReactions.js";
|
||||
import { GuildLogs } from "../../data/GuildLogs.js";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages.js";
|
||||
import { AutoReaction } from "../../data/entities/AutoReaction.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
|
||||
export const zAutoReactionsConfig = z.strictObject({
|
||||
can_manage: z.boolean(),
|
||||
|
@ -16,6 +17,7 @@ export interface AutoReactionsPluginType extends BasePluginType {
|
|||
savedMessages: GuildSavedMessages;
|
||||
autoReactions: GuildAutoReactions;
|
||||
cache: Map<string, AutoReaction | null>;
|
||||
common: pluginUtils.PluginPublicInterface<typeof CommonPlugin>;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners.js";
|
|||
import { MINUTES, SECONDS } from "../../utils.js";
|
||||
import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap.js";
|
||||
import { unregisterEventListenersFromMap } from "../../utils/unregisterEventListenersFromMap.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
import { CountersPlugin } from "../Counters/CountersPlugin.js";
|
||||
import { InternalPosterPlugin } from "../InternalPoster/InternalPosterPlugin.js";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin.js";
|
||||
|
@ -117,6 +118,10 @@ export const AutomodPlugin = guildPlugin<AutomodPluginType>()({
|
|||
state.cachedAntiraidLevel = await state.antiraidLevels.get();
|
||||
},
|
||||
|
||||
beforeStart(pluginData) {
|
||||
pluginData.state.common = pluginData.getPlugin(CommonPlugin);
|
||||
},
|
||||
|
||||
async afterLoad(pluginData) {
|
||||
const { state } = pluginData;
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ export const BanAction = automodAction({
|
|||
await modActions.banUserId(
|
||||
userId,
|
||||
reason,
|
||||
reason,
|
||||
{
|
||||
contactMethods,
|
||||
caseArgs,
|
||||
|
|
|
@ -33,7 +33,7 @@ export const KickAction = automodAction({
|
|||
const modActions = pluginData.getPlugin(ModActionsPlugin);
|
||||
for (const member of membersToKick) {
|
||||
if (!member) continue;
|
||||
await modActions.kickMember(member, reason, { contactMethods, caseArgs, isAutomodAction: true });
|
||||
await modActions.kickMember(member, reason, reason, { contactMethods, caseArgs, isAutomodAction: true });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -57,6 +57,7 @@ export const MuteAction = automodAction({
|
|||
userId,
|
||||
duration,
|
||||
reason,
|
||||
reason,
|
||||
{ contactMethods, caseArgs, isAutomodAction: true },
|
||||
rolesToRemove,
|
||||
rolesToRestore,
|
||||
|
|
|
@ -33,7 +33,7 @@ export const WarnAction = automodAction({
|
|||
const modActions = pluginData.getPlugin(ModActionsPlugin);
|
||||
for (const member of membersToWarn) {
|
||||
if (!member) continue;
|
||||
await modActions.warnMember(member, reason, { contactMethods, caseArgs, isAutomodAction: true });
|
||||
await modActions.warnMember(member, reason, reason, { contactMethods, caseArgs, isAutomodAction: true });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { guildPluginMessageCommand } from "knub";
|
||||
import { sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { setAntiraidLevel } from "../functions/setAntiraidLevel.js";
|
||||
import { AutomodPluginType } from "../types.js";
|
||||
|
||||
|
@ -9,6 +8,6 @@ export const AntiraidClearCmd = guildPluginMessageCommand<AutomodPluginType>()({
|
|||
|
||||
async run({ pluginData, message }) {
|
||||
await setAntiraidLevel(pluginData, null, message.author);
|
||||
sendSuccessMessage(pluginData, message.channel, "Anti-raid turned **off**");
|
||||
void pluginData.state.common.sendSuccessMessage(message, "Anti-raid turned **off**");
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { guildPluginMessageCommand } from "knub";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { setAntiraidLevel } from "../functions/setAntiraidLevel.js";
|
||||
import { AutomodPluginType } from "../types.js";
|
||||
|
||||
|
@ -15,11 +14,11 @@ export const SetAntiraidCmd = guildPluginMessageCommand<AutomodPluginType>()({
|
|||
async run({ pluginData, message, args }) {
|
||||
const config = pluginData.config.get();
|
||||
if (!config.antiraid_levels.includes(args.level)) {
|
||||
sendErrorMessage(pluginData, message.channel, "Unknown anti-raid level");
|
||||
pluginData.state.common.sendErrorMessage(message, "Unknown anti-raid level");
|
||||
return;
|
||||
}
|
||||
|
||||
await setAntiraidLevel(pluginData, args.level, message.author);
|
||||
sendSuccessMessage(pluginData, message.channel, `Anti-raid level set to **${args.level}**`);
|
||||
pluginData.state.common.sendSuccessMessage(message, `Anti-raid level set to **${args.level}**`);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { guildPluginEventListener } from "knub";
|
||||
import diff from "lodash/difference.js";
|
||||
import isEqual from "lodash/isEqual.js";
|
||||
import diff from "lodash.difference";
|
||||
import isEqual from "lodash.isequal";
|
||||
import { runAutomod } from "../functions/runAutomod.js";
|
||||
import { AutomodContext, AutomodPluginType } from "../types.js";
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { GuildMember, GuildTextBasedChannel, PartialGuildMember, ThreadChannel, User } from "discord.js";
|
||||
import { BasePluginType, CooldownManager } from "knub";
|
||||
import { BasePluginType, CooldownManager, pluginUtils } from "knub";
|
||||
import z from "zod";
|
||||
import { Queue } from "../../Queue.js";
|
||||
import { RegExpRunner } from "../../RegExpRunner.js";
|
||||
|
@ -9,6 +9,7 @@ import { GuildLogs } from "../../data/GuildLogs.js";
|
|||
import { GuildSavedMessages } from "../../data/GuildSavedMessages.js";
|
||||
import { SavedMessage } from "../../data/entities/SavedMessage.js";
|
||||
import { entries, zBoundedRecord, zDelayString } from "../../utils.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
import { CounterEvents } from "../Counters/types.js";
|
||||
import { ModActionType, ModActionsEvents } from "../ModActions/types.js";
|
||||
import { MutesEvents } from "../Mutes/types.js";
|
||||
|
@ -140,6 +141,8 @@ export interface AutomodPluginType extends BasePluginType {
|
|||
|
||||
modActionsListeners: Map<keyof ModActionsEvents, any>;
|
||||
mutesListeners: Map<keyof MutesEvents, any>;
|
||||
|
||||
common: pluginUtils.PluginPublicInterface<typeof CommonPlugin>;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import { AllowedGuilds } from "../../data/AllowedGuilds.js";
|
|||
import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments.js";
|
||||
import { Configs } from "../../data/Configs.js";
|
||||
import { GuildArchives } from "../../data/GuildArchives.js";
|
||||
import { sendSuccessMessage } from "../../pluginUtils.js";
|
||||
import { getActiveReload, resetActiveReload } from "./activeReload.js";
|
||||
import { AddDashboardUserCmd } from "./commands/AddDashboardUserCmd.js";
|
||||
import { AddServerFromInviteCmd } from "./commands/AddServerFromInviteCmd.js";
|
||||
|
@ -77,7 +76,7 @@ export const BotControlPlugin = globalPlugin<BotControlPluginType>()({
|
|||
if (guild) {
|
||||
const channel = guild.channels.cache.get(channelId as Snowflake);
|
||||
if (channel instanceof TextChannel) {
|
||||
sendSuccessMessage(pluginData, channel, "Global plugins reloaded!");
|
||||
void channel.send("Global plugins reloaded!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ApiPermissions } from "@zeppelinbot/shared/apiPermissions.js";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { isStaffPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { isStaffPreFilter } from "../../../pluginUtils.js";
|
||||
import { renderUsername } from "../../../utils.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
||||
|
@ -19,7 +19,7 @@ export const AddDashboardUserCmd = botControlCmd({
|
|||
async run({ pluginData, message: msg, args }) {
|
||||
const guild = await pluginData.state.allowedGuilds.find(args.guildId);
|
||||
if (!guild) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin");
|
||||
void msg.channel.send("Server is not using Zeppelin");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -36,10 +36,7 @@ export const AddDashboardUserCmd = botControlCmd({
|
|||
}
|
||||
|
||||
const userNameList = args.users.map((user) => `<@!${user.id}> (**${renderUsername(user)}**, \`${user.id}\`)`);
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`The following users were given dashboard access for **${guild.name}**:\n\n${userNameList}`,
|
||||
);
|
||||
|
||||
msg.channel.send(`The following users were given dashboard access for **${guild.name}**:\n\n${userNameList}`);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { ApiPermissions } from "@zeppelinbot/shared/apiPermissions.js";
|
||||
import moment from "moment-timezone";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { DBDateFormat, isGuildInvite, resolveInvite } from "../../../utils.js";
|
||||
import { isEligible } from "../functions/isEligible.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
@ -18,19 +17,19 @@ export const AddServerFromInviteCmd = botControlCmd({
|
|||
async run({ pluginData, message: msg, args }) {
|
||||
const invite = await resolveInvite(pluginData.client, args.inviteCode, true);
|
||||
if (!invite || !isGuildInvite(invite)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Could not resolve invite"); // :D
|
||||
void msg.channel.send("Could not resolve invite"); // :D
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = await pluginData.state.allowedGuilds.find(invite.guild.id);
|
||||
if (existing) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Server is already allowed!");
|
||||
void msg.channel.send("Server is already allowed!");
|
||||
return;
|
||||
}
|
||||
|
||||
const { result, explanation } = await isEligible(pluginData, args.user, invite);
|
||||
if (!result) {
|
||||
sendErrorMessage(pluginData, msg.channel, `Could not add server because it's not eligible: ${explanation}`);
|
||||
msg.channel.send(`Could not add server because it's not eligible: ${explanation}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -51,6 +50,6 @@ export const AddServerFromInviteCmd = botControlCmd({
|
|||
);
|
||||
}
|
||||
|
||||
sendSuccessMessage(pluginData, msg.channel, "Server was eligible and is now allowed to use Zeppelin!");
|
||||
msg.channel.send("Server was eligible and is now allowed to use Zeppelin!");
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ApiPermissions } from "@zeppelinbot/shared/apiPermissions.js";
|
||||
import moment from "moment-timezone";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { isStaffPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { isStaffPreFilter } from "../../../pluginUtils.js";
|
||||
import { DBDateFormat, isSnowflake } from "../../../utils.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
||||
|
@ -20,17 +20,17 @@ export const AllowServerCmd = botControlCmd({
|
|||
async run({ pluginData, message: msg, args }) {
|
||||
const existing = await pluginData.state.allowedGuilds.find(args.guildId);
|
||||
if (existing) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Server is already allowed!");
|
||||
void msg.channel.send("Server is already allowed!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSnowflake(args.guildId)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Invalid server ID!");
|
||||
void msg.channel.send("Invalid server ID!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.userId && !isSnowflake(args.userId)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Invalid user ID!");
|
||||
void msg.channel.send("Invalid user ID!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,6 @@ export const AllowServerCmd = botControlCmd({
|
|||
);
|
||||
}
|
||||
|
||||
sendSuccessMessage(pluginData, msg.channel, "Server is now allowed to use Zeppelin!");
|
||||
void msg.channel.send("Server is now allowed to use Zeppelin!");
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { isStaffPreFilter, sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { isStaffPreFilter } from "../../../pluginUtils.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
||||
export const ChannelToServerCmd = botControlCmd({
|
||||
|
@ -16,7 +16,7 @@ export const ChannelToServerCmd = botControlCmd({
|
|||
async run({ pluginData, message: msg, args }) {
|
||||
const channel = pluginData.client.channels.cache.get(args.channelId);
|
||||
if (!channel) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Channel not found in cache!");
|
||||
void msg.channel.send("Channel not found in cache!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { isStaffPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { isStaffPreFilter } from "../../../pluginUtils.js";
|
||||
import { noop } from "../../../utils.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
||||
|
@ -18,7 +18,7 @@ export const DisallowServerCmd = botControlCmd({
|
|||
async run({ pluginData, message: msg, args }) {
|
||||
const existing = await pluginData.state.allowedGuilds.find(args.guildId);
|
||||
if (!existing) {
|
||||
sendErrorMessage(pluginData, msg.channel, "That server is not allowed in the first place!");
|
||||
void msg.channel.send("That server is not allowed in the first place!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,6 @@ export const DisallowServerCmd = botControlCmd({
|
|||
.get(args.guildId as Snowflake)
|
||||
?.leave()
|
||||
.catch(noop);
|
||||
sendSuccessMessage(pluginData, msg.channel, "Server removed!");
|
||||
void msg.channel.send("Server removed!");
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { isGuildInvite, resolveInvite } from "../../../utils.js";
|
||||
import { isEligible } from "../functions/isEligible.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
@ -16,17 +15,17 @@ export const EligibleCmd = botControlCmd({
|
|||
async run({ pluginData, message: msg, args }) {
|
||||
const invite = await resolveInvite(pluginData.client, args.inviteCode, true);
|
||||
if (!invite || !isGuildInvite(invite)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Could not resolve invite");
|
||||
void msg.channel.send("Could not resolve invite");
|
||||
return;
|
||||
}
|
||||
|
||||
const { result, explanation } = await isEligible(pluginData, args.user, invite);
|
||||
|
||||
if (result) {
|
||||
sendSuccessMessage(pluginData, msg.channel, `Server is eligible: ${explanation}`);
|
||||
void msg.channel.send(`Server is eligible: ${explanation}`);
|
||||
return;
|
||||
}
|
||||
|
||||
sendErrorMessage(pluginData, msg.channel, `Server is **NOT** eligible: ${explanation}`);
|
||||
void msg.channel.send(`Server is **NOT** eligible: ${explanation}`);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { isStaffPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { isStaffPreFilter } from "../../../pluginUtils.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
||||
export const LeaveServerCmd = botControlCmd({
|
||||
|
@ -16,7 +16,7 @@ export const LeaveServerCmd = botControlCmd({
|
|||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
if (!pluginData.client.guilds.cache.has(args.guildId as Snowflake)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "I am not in that guild");
|
||||
void msg.channel.send("I am not in that guild");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -26,10 +26,10 @@ export const LeaveServerCmd = botControlCmd({
|
|||
try {
|
||||
await pluginData.client.guilds.cache.get(args.guildId as Snowflake)?.leave();
|
||||
} catch (e) {
|
||||
sendErrorMessage(pluginData, msg.channel, `Failed to leave guild: ${e.message}`);
|
||||
void msg.channel.send(`Failed to leave guild: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
sendSuccessMessage(pluginData, msg.channel, `Left guild **${guildName}**`);
|
||||
void msg.channel.send(`Left guild **${guildName}**`);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { AllowedGuild } from "../../../data/entities/AllowedGuild.js";
|
||||
import { ApiPermissionAssignment } from "../../../data/entities/ApiPermissionAssignment.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { renderUsername, resolveUser } from "../../../utils.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
||||
|
@ -16,7 +15,7 @@ export const ListDashboardPermsCmd = botControlCmd({
|
|||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
if (!args.user && !args.guildId) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Must specify at least guildId, user, or both.");
|
||||
void msg.channel.send("Must specify at least guildId, user, or both.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -24,7 +23,7 @@ export const ListDashboardPermsCmd = botControlCmd({
|
|||
if (args.guildId) {
|
||||
guild = await pluginData.state.allowedGuilds.find(args.guildId);
|
||||
if (!guild) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin");
|
||||
void msg.channel.send("Server is not using Zeppelin");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +32,7 @@ export const ListDashboardPermsCmd = botControlCmd({
|
|||
if (args.user) {
|
||||
existingUserAssignment = await pluginData.state.apiPermissionAssignments.getByUserId(args.user.id);
|
||||
if (existingUserAssignment.length === 0) {
|
||||
sendErrorMessage(pluginData, msg.channel, "The user has no assigned permissions.");
|
||||
void msg.channel.send("The user has no assigned permissions.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -54,11 +53,7 @@ export const ListDashboardPermsCmd = botControlCmd({
|
|||
}
|
||||
|
||||
if (finalMessage === "") {
|
||||
sendErrorMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`The user ${userInfo} has no assigned permissions on the specified server.`,
|
||||
);
|
||||
msg.channel.send(`The user ${userInfo} has no assigned permissions on the specified server.`);
|
||||
return;
|
||||
}
|
||||
// Else display all users that have permissions on the specified guild
|
||||
|
@ -67,7 +62,7 @@ export const ListDashboardPermsCmd = botControlCmd({
|
|||
|
||||
const existingGuildAssignment = await pluginData.state.apiPermissionAssignments.getByGuildId(guild.id);
|
||||
if (existingGuildAssignment.length === 0) {
|
||||
sendErrorMessage(pluginData, msg.channel, `The server ${guildInfo} has no assigned permissions.`);
|
||||
msg.channel.send(`The server ${guildInfo} has no assigned permissions.`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -80,6 +75,9 @@ export const ListDashboardPermsCmd = botControlCmd({
|
|||
}
|
||||
}
|
||||
|
||||
await sendSuccessMessage(pluginData, msg.channel, finalMessage.trim(), {});
|
||||
await msg.channel.send({
|
||||
content: finalMessage.trim(),
|
||||
allowedMentions: {},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { renderUsername, resolveUser } from "../../../utils.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
||||
|
@ -14,7 +13,7 @@ export const ListDashboardUsersCmd = botControlCmd({
|
|||
async run({ pluginData, message: msg, args }) {
|
||||
const guild = await pluginData.state.allowedGuilds.find(args.guildId);
|
||||
if (!guild) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin");
|
||||
void msg.channel.send("Server is not using Zeppelin");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -30,11 +29,9 @@ export const ListDashboardUsersCmd = botControlCmd({
|
|||
`<@!${user.id}> (**${renderUsername(user)}**, \`${user.id}\`): ${permission.permissions.join(", ")}`,
|
||||
);
|
||||
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`The following users have dashboard access for **${guild.name}**:\n\n${userNameList.join("\n")}`,
|
||||
{},
|
||||
);
|
||||
msg.channel.send({
|
||||
content: `The following users have dashboard access for **${guild.name}**:\n\n${userNameList.join("\n")}`,
|
||||
allowedMentions: {},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import moment from "moment-timezone";
|
||||
import { GuildArchives } from "../../../data/GuildArchives.js";
|
||||
import { getBaseUrl, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { getBaseUrl } from "../../../pluginUtils.js";
|
||||
import { getRateLimitStats } from "../../../rateLimitStats.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
||||
|
@ -13,7 +13,7 @@ export const RateLimitPerformanceCmd = botControlCmd({
|
|||
async run({ pluginData, message: msg }) {
|
||||
const logItems = getRateLimitStats();
|
||||
if (logItems.length === 0) {
|
||||
sendSuccessMessage(pluginData, msg.channel, `No rate limits hit`);
|
||||
void msg.channel.send(`No rate limits hit`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { isStaffPreFilter, sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { isStaffPreFilter } from "../../../pluginUtils.js";
|
||||
import { getActiveReload, setActiveReload } from "../activeReload.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
||||
|
@ -14,7 +14,7 @@ export const ReloadGlobalPluginsCmd = botControlCmd({
|
|||
|
||||
const guildId = "guild" in message.channel ? message.channel.guild.id : null;
|
||||
if (!guildId) {
|
||||
sendErrorMessage(pluginData, message.channel, "This command can only be used in a server");
|
||||
void message.channel.send("This command can only be used in a server");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { isStaffPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { isStaffPreFilter } from "../../../pluginUtils.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
||||
export const ReloadServerCmd = botControlCmd({
|
||||
|
@ -16,18 +16,18 @@ export const ReloadServerCmd = botControlCmd({
|
|||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
if (!pluginData.client.guilds.cache.has(args.guildId as Snowflake)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "I am not in that guild");
|
||||
void msg.channel.send("I am not in that guild");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await pluginData.getKnubInstance().reloadGuild(args.guildId);
|
||||
} catch (e) {
|
||||
sendErrorMessage(pluginData, msg.channel, `Failed to reload guild: ${e.message}`);
|
||||
void msg.channel.send(`Failed to reload guild: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const guild = await pluginData.client.guilds.fetch(args.guildId as Snowflake);
|
||||
sendSuccessMessage(pluginData, msg.channel, `Reloaded guild **${guild?.name || "???"}**`);
|
||||
void msg.channel.send(`Reloaded guild **${guild?.name || "???"}**`);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { isStaffPreFilter, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { isStaffPreFilter } from "../../../pluginUtils.js";
|
||||
import { renderUsername } from "../../../utils.js";
|
||||
import { botControlCmd } from "../types.js";
|
||||
|
||||
|
@ -18,7 +18,7 @@ export const RemoveDashboardUserCmd = botControlCmd({
|
|||
async run({ pluginData, message: msg, args }) {
|
||||
const guild = await pluginData.state.allowedGuilds.find(args.guildId);
|
||||
if (!guild) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Server is not using Zeppelin");
|
||||
void msg.channel.send("Server is not using Zeppelin");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -35,10 +35,7 @@ export const RemoveDashboardUserCmd = botControlCmd({
|
|||
}
|
||||
|
||||
const userNameList = args.users.map((user) => `<@!${user.id}> (**${renderUsername(user)}**, \`${user.id}\`)`);
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`The following users were removed from the dashboard for **${guild.name}**:\n\n${userNameList}`,
|
||||
);
|
||||
|
||||
msg.channel.send(`The following users were removed from the dashboard for **${guild.name}**:\n\n${userNameList}`);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { FindOptionsWhere } from "typeorm/find-options/FindOptionsWhere";
|
||||
import { Case } from "../../../data/entities/Case.js";
|
||||
import { CasesPluginType } from "../types.js";
|
||||
|
||||
|
@ -7,6 +8,7 @@ export function getRecentCasesByMod(
|
|||
modId: string,
|
||||
count: number,
|
||||
skip = 0,
|
||||
filters: Omit<FindOptionsWhere<Case>, "guild_id" | "mod_id" | "is_hidden"> = {},
|
||||
): Promise<Case[]> {
|
||||
return pluginData.state.cases.getRecentByModId(modId, count, skip);
|
||||
return pluginData.state.cases.getRecentByModId(modId, count, skip, filters);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { FindOptionsWhere } from "typeorm/find-options/FindOptionsWhere";
|
||||
import { Case } from "../../../data/entities/Case.js";
|
||||
import { CasesPluginType } from "../types.js";
|
||||
|
||||
export function getTotalCasesByMod(pluginData: GuildPluginData<CasesPluginType>, modId: string): Promise<number> {
|
||||
return pluginData.state.cases.getTotalCasesByModId(modId);
|
||||
export function getTotalCasesByMod(
|
||||
pluginData: GuildPluginData<CasesPluginType>,
|
||||
modId: string,
|
||||
filters: Omit<FindOptionsWhere<Case>, "guild_id" | "mod_id" | "is_hidden"> = {},
|
||||
): Promise<number> {
|
||||
return pluginData.state.cases.getTotalCasesByModId(modId, filters);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
import { Invite } from "discord.js";
|
||||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import { GuildPluginData } from "knub";
|
||||
import cloneDeep from "lodash/cloneDeep.js";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import { allowTimeout } from "../../../RegExpRunner.js";
|
||||
import { ZalgoRegex } from "../../../data/Zalgo.js";
|
||||
import { ISavedMessageEmbedData, SavedMessage } from "../../../data/entities/SavedMessage.js";
|
||||
import {
|
||||
getInviteCodesInString,
|
||||
getUrlsInString,
|
||||
isGuildInvite,
|
||||
resolveInvite,
|
||||
resolveMember,
|
||||
} from "../../../utils.js";
|
||||
import { getInviteCodesInString, getUrlsInString, isGuildInvite, resolveInvite, resolveMember } from "../../../utils.js";
|
||||
import { CensorPluginType } from "../types.js";
|
||||
import { censorMessage } from "./censorMessage.js";
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { guildPlugin } from "knub";
|
||||
import z from "zod";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin.js";
|
||||
import { ArchiveChannelCmd } from "./commands/ArchiveChannelCmd.js";
|
||||
import { ChannelArchiverPluginType } from "./types.js";
|
||||
|
@ -14,4 +15,8 @@ export const ChannelArchiverPlugin = guildPlugin<ChannelArchiverPluginType>()({
|
|||
messageCommands: [
|
||||
ArchiveChannelCmd,
|
||||
],
|
||||
|
||||
beforeStart(pluginData) {
|
||||
pluginData.state.common = pluginData.getPlugin(CommonPlugin);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import moment from "moment-timezone";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { isOwner, sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { isOwner } from "../../../pluginUtils.js";
|
||||
import { SECONDS, confirm, noop, renderUsername } from "../../../utils.js";
|
||||
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin.js";
|
||||
import { rehostAttachment } from "../rehostAttachment.js";
|
||||
|
@ -32,12 +32,12 @@ export const ArchiveChannelCmd = channelArchiverCmd({
|
|||
|
||||
async run({ message: msg, args, pluginData }) {
|
||||
if (!args["attachment-channel"]) {
|
||||
const confirmed = await confirm(msg.channel, msg.author.id, {
|
||||
const confirmed = await confirm(msg, msg.author.id, {
|
||||
content:
|
||||
"No `-attachment-channel` specified. Continue? Attachments will not be available in the log if their message is deleted.",
|
||||
});
|
||||
if (!confirmed) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Canceled");
|
||||
void pluginData.state.common.sendErrorMessage(msg, "Canceled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { BasePluginType, guildPluginMessageCommand } from "knub";
|
||||
import { BasePluginType, guildPluginMessageCommand, pluginUtils } from "knub";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
|
||||
export interface ChannelArchiverPluginType extends BasePluginType {}
|
||||
export interface ChannelArchiverPluginType extends BasePluginType {
|
||||
state: {
|
||||
common: pluginUtils.PluginPublicInterface<typeof CommonPlugin>;
|
||||
};
|
||||
}
|
||||
|
||||
export const channelArchiverCmd = guildPluginMessageCommand<ChannelArchiverPluginType>();
|
||||
|
|
153
backend/src/plugins/Common/CommonPlugin.ts
Normal file
153
backend/src/plugins/Common/CommonPlugin.ts
Normal file
|
@ -0,0 +1,153 @@
|
|||
import {
|
||||
Attachment,
|
||||
ChatInputCommandInteraction,
|
||||
Message,
|
||||
MessageCreateOptions,
|
||||
MessageMentionOptions,
|
||||
ModalSubmitInteraction,
|
||||
TextBasedChannel,
|
||||
User,
|
||||
} from "discord.js";
|
||||
import { PluginOptions, guildPlugin } from "knub";
|
||||
import { logger } from "../../logger.js";
|
||||
import { isContextInteraction, sendContextResponse } from "../../pluginUtils.js";
|
||||
import { errorMessage, successMessage } from "../../utils.js";
|
||||
import { getErrorEmoji, getSuccessEmoji } from "./functions/getEmoji.js";
|
||||
import { CommonPluginType, zCommonConfig } from "./types.js";
|
||||
|
||||
const defaultOptions: PluginOptions<CommonPluginType> = {
|
||||
config: {
|
||||
success_emoji: "✅",
|
||||
error_emoji: "❌",
|
||||
attachment_storing_channel: null,
|
||||
},
|
||||
};
|
||||
|
||||
export const CommonPlugin = guildPlugin<CommonPluginType>()({
|
||||
name: "common",
|
||||
dependencies: () => [],
|
||||
configParser: (input) => zCommonConfig.parse(input),
|
||||
defaultOptions,
|
||||
public(pluginData) {
|
||||
return {
|
||||
getSuccessEmoji,
|
||||
getErrorEmoji,
|
||||
|
||||
sendSuccessMessage: async (
|
||||
context: TextBasedChannel | Message | User | ChatInputCommandInteraction,
|
||||
body: string,
|
||||
allowedMentions?: MessageMentionOptions,
|
||||
responseInteraction?: ModalSubmitInteraction,
|
||||
ephemeral = true,
|
||||
): Promise<Message | undefined> => {
|
||||
const emoji = getSuccessEmoji(pluginData);
|
||||
const formattedBody = successMessage(body, emoji);
|
||||
const content: MessageCreateOptions = allowedMentions
|
||||
? { content: formattedBody, allowedMentions }
|
||||
: { content: formattedBody };
|
||||
|
||||
if (responseInteraction) {
|
||||
await responseInteraction
|
||||
.editReply({ content: formattedBody, embeds: [], components: [] })
|
||||
.catch((err) => logger.error(`Interaction reply failed: ${err}`));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isContextInteraction(context)) {
|
||||
// noinspection TypeScriptValidateJSTypes
|
||||
return sendContextResponse(context, { ...content }) // Force line break
|
||||
.catch((err) => {
|
||||
const channelInfo =
|
||||
"guild" in context && context.guild ? `${context.id} (${context.guild.id})` : context.id;
|
||||
|
||||
logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`);
|
||||
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
const replyMethod = context.replied || context.deferred ? "editReply" : "reply";
|
||||
|
||||
return context[replyMethod]({
|
||||
content: formattedBody,
|
||||
embeds: [],
|
||||
components: [],
|
||||
fetchReply: true,
|
||||
ephemeral,
|
||||
}).catch((err) => {
|
||||
logger.error(`Context reply failed: ${err}`);
|
||||
|
||||
return undefined;
|
||||
}) as Promise<Message>;
|
||||
},
|
||||
|
||||
sendErrorMessage: async (
|
||||
context: TextBasedChannel | Message | User | ChatInputCommandInteraction,
|
||||
body: string,
|
||||
allowedMentions?: MessageMentionOptions,
|
||||
responseInteraction?: ModalSubmitInteraction,
|
||||
ephemeral = true,
|
||||
): Promise<Message | undefined> => {
|
||||
const emoji = getErrorEmoji(pluginData);
|
||||
const formattedBody = errorMessage(body, emoji);
|
||||
const content: MessageCreateOptions = allowedMentions
|
||||
? { content: formattedBody, allowedMentions }
|
||||
: { content: formattedBody };
|
||||
|
||||
if (responseInteraction) {
|
||||
await responseInteraction
|
||||
.editReply({ content: formattedBody, embeds: [], components: [] })
|
||||
.catch((err) => logger.error(`Interaction reply failed: ${err}`));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isContextInteraction(context)) {
|
||||
// noinspection TypeScriptValidateJSTypes
|
||||
return sendContextResponse(context, { ...content }) // Force line break
|
||||
.catch((err) => {
|
||||
const channelInfo =
|
||||
"guild" in context && context.guild ? `${context.id} (${context.guild.id})` : context.id;
|
||||
|
||||
logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`);
|
||||
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
const replyMethod = context.replied || context.deferred ? "editReply" : "reply";
|
||||
|
||||
return context[replyMethod]({
|
||||
content: formattedBody,
|
||||
embeds: [],
|
||||
components: [],
|
||||
fetchReply: true,
|
||||
ephemeral,
|
||||
}).catch((err) => {
|
||||
logger.error(`Context reply failed: ${err}`);
|
||||
|
||||
return undefined;
|
||||
}) as Promise<Message>;
|
||||
},
|
||||
|
||||
storeAttachmentsAsMessage: async (attachments: Attachment[], backupChannel?: TextBasedChannel | null) => {
|
||||
const attachmentChannelId = pluginData.config.get().attachment_storing_channel;
|
||||
const channel = attachmentChannelId
|
||||
? (pluginData.guild.channels.cache.get(attachmentChannelId) as TextBasedChannel) ?? backupChannel
|
||||
: backupChannel;
|
||||
|
||||
if (!channel) {
|
||||
throw new Error(
|
||||
"Cannot store attachments: no attachment storing channel configured, and no backup channel passed",
|
||||
);
|
||||
}
|
||||
|
||||
return channel!.send({
|
||||
content: `Storing ${attachments.length} attachment${attachments.length === 1 ? "" : "s"}`,
|
||||
files: attachments.map((a) => a.url),
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
9
backend/src/plugins/Common/docs.ts
Normal file
9
backend/src/plugins/Common/docs.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { ZeppelinPluginDocs } from "../../types.js";
|
||||
import { zCommonConfig } from "./types.js";
|
||||
|
||||
export const commonPluginDocs: ZeppelinPluginDocs = {
|
||||
type: "internal",
|
||||
configSchema: zCommonConfig,
|
||||
|
||||
prettyName: "Common",
|
||||
};
|
10
backend/src/plugins/Common/functions/getEmoji.ts
Normal file
10
backend/src/plugins/Common/functions/getEmoji.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { CommonPluginType } from "../types.js";
|
||||
|
||||
export function getSuccessEmoji(pluginData: GuildPluginData<CommonPluginType>) {
|
||||
return pluginData.config.get().success_emoji ?? "✅";
|
||||
}
|
||||
|
||||
export function getErrorEmoji(pluginData: GuildPluginData<CommonPluginType>) {
|
||||
return pluginData.config.get().error_emoji ?? "❌";
|
||||
}
|
12
backend/src/plugins/Common/types.ts
Normal file
12
backend/src/plugins/Common/types.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { BasePluginType } from "knub";
|
||||
import z from "zod";
|
||||
|
||||
export const zCommonConfig = z.strictObject({
|
||||
success_emoji: z.string(),
|
||||
error_emoji: z.string(),
|
||||
attachment_storing_channel: z.nullable(z.string()),
|
||||
});
|
||||
|
||||
export interface CommonPluginType extends BasePluginType {
|
||||
config: z.output<typeof zCommonConfig>;
|
||||
}
|
|
@ -1,30 +1,31 @@
|
|||
import { PluginOptions, guildPlugin } from "knub";
|
||||
import { GuildContextMenuLinks } from "../../data/GuildContextMenuLinks.js";
|
||||
import { GuildCases } from "../../data/GuildCases.js";
|
||||
import { CasesPlugin } from "../Cases/CasesPlugin.js";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin.js";
|
||||
import { ModActionsPlugin } from "../ModActions/ModActionsPlugin.js";
|
||||
import { MutesPlugin } from "../Mutes/MutesPlugin.js";
|
||||
import { UtilityPlugin } from "../Utility/UtilityPlugin.js";
|
||||
import { ContextClickedEvt } from "./events/ContextClickedEvt.js";
|
||||
import { BanCmd } from "./commands/BanUserCtxCmd.js";
|
||||
import { CleanCmd } from "./commands/CleanMessageCtxCmd.js";
|
||||
import { ModMenuCmd } from "./commands/ModMenuUserCtxCmd.js";
|
||||
import { MuteCmd } from "./commands/MuteUserCtxCmd.js";
|
||||
import { NoteCmd } from "./commands/NoteUserCtxCmd.js";
|
||||
import { WarnCmd } from "./commands/WarnUserCtxCmd.js";
|
||||
import { ContextMenuPluginType, zContextMenusConfig } from "./types.js";
|
||||
import { loadAllCommands } from "./utils/loadAllCommands.js";
|
||||
|
||||
const defaultOptions: PluginOptions<ContextMenuPluginType> = {
|
||||
config: {
|
||||
can_use: false,
|
||||
|
||||
user_muteindef: false,
|
||||
user_mute1d: false,
|
||||
user_mute1h: false,
|
||||
user_info: false,
|
||||
|
||||
message_clean10: false,
|
||||
message_clean25: false,
|
||||
message_clean50: false,
|
||||
can_open_mod_menu: false,
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
level: ">=50",
|
||||
config: {
|
||||
can_use: true,
|
||||
|
||||
can_open_mod_menu: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -33,22 +34,15 @@ const defaultOptions: PluginOptions<ContextMenuPluginType> = {
|
|||
export const ContextMenuPlugin = guildPlugin<ContextMenuPluginType>()({
|
||||
name: "context_menu",
|
||||
|
||||
dependencies: () => [MutesPlugin, LogsPlugin, UtilityPlugin],
|
||||
dependencies: () => [CasesPlugin, MutesPlugin, ModActionsPlugin, LogsPlugin, UtilityPlugin],
|
||||
configParser: (input) => zContextMenusConfig.parse(input),
|
||||
defaultOptions,
|
||||
|
||||
// prettier-ignore
|
||||
events: [
|
||||
ContextClickedEvt,
|
||||
],
|
||||
contextMenuCommands: [ModMenuCmd, NoteCmd, WarnCmd, MuteCmd, BanCmd, CleanCmd],
|
||||
|
||||
beforeLoad(pluginData) {
|
||||
const { state, guild } = pluginData;
|
||||
|
||||
state.contextMenuLinks = new GuildContextMenuLinks(guild.id);
|
||||
},
|
||||
|
||||
afterLoad(pluginData) {
|
||||
loadAllCommands(pluginData);
|
||||
state.cases = GuildCases.getGuildInstance(guild.id);
|
||||
},
|
||||
});
|
||||
|
|
116
backend/src/plugins/ContextMenus/actions/ban.ts
Normal file
116
backend/src/plugins/ContextMenus/actions/ban.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonInteraction,
|
||||
ContextMenuCommandInteraction,
|
||||
ModalBuilder,
|
||||
ModalSubmitInteraction,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
} from "discord.js";
|
||||
import humanizeDuration from "humanize-duration";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { logger } from "../../../logger.js";
|
||||
import { canActOn } from "../../../pluginUtils.js";
|
||||
import { convertDelayStringToMS, renderUserUsername } from "../../../utils.js";
|
||||
import { CaseArgs } from "../../Cases/types.js";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin.js";
|
||||
import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd.js";
|
||||
import { ContextMenuPluginType, ModMenuActionType } from "../types.js";
|
||||
import { updateAction } from "./update.js";
|
||||
|
||||
async function banAction(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
duration: string | undefined,
|
||||
reason: string | undefined,
|
||||
evidence: string | undefined,
|
||||
target: string,
|
||||
interaction: ButtonInteraction | ContextMenuCommandInteraction,
|
||||
submitInteraction: ModalSubmitInteraction,
|
||||
) {
|
||||
const interactionToReply = interaction.isButton() ? interaction : submitInteraction;
|
||||
const executingMember = await pluginData.guild.members.fetch(interaction.user.id);
|
||||
const userCfg = await pluginData.config.getMatchingConfig({
|
||||
channelId: interaction.channelId,
|
||||
member: executingMember,
|
||||
});
|
||||
|
||||
const modactions = pluginData.getPlugin(ModActionsPlugin);
|
||||
if (!userCfg.can_use || !(await modactions.hasBanPermission(executingMember, interaction.channelId))) {
|
||||
await interactionToReply.editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
const targetMember = await pluginData.guild.members.fetch(target);
|
||||
if (!canActOn(pluginData, executingMember, targetMember)) {
|
||||
await interactionToReply.editReply({ content: "Cannot ban: insufficient permissions", embeds: [], components: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
const caseArgs: Partial<CaseArgs> = {
|
||||
modId: executingMember.id,
|
||||
};
|
||||
|
||||
const durationMs = duration ? convertDelayStringToMS(duration)! : undefined;
|
||||
const result = await modactions.banUserId(target, reason, reason, { caseArgs }, durationMs);
|
||||
if (result.status === "failed") {
|
||||
await interactionToReply.editReply({ content: "Error: Failed to ban user", embeds: [], components: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
const userName = renderUserUsername(targetMember.user);
|
||||
const messageResultText = result.notifyResult.text ? ` (${result.notifyResult.text})` : "";
|
||||
const banMessage = `Banned **${userName}** ${
|
||||
durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely"
|
||||
} (Case #${result.case.case_number})${messageResultText}`;
|
||||
|
||||
if (evidence) {
|
||||
await updateAction(pluginData, executingMember, result.case, evidence);
|
||||
}
|
||||
|
||||
await interactionToReply.editReply({ content: banMessage, embeds: [], components: [] });
|
||||
}
|
||||
|
||||
export async function launchBanActionModal(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
interaction: ButtonInteraction | ContextMenuCommandInteraction,
|
||||
target: string,
|
||||
) {
|
||||
const modalId = `${ModMenuActionType.BAN}:${interaction.id}`;
|
||||
const modal = new ModalBuilder().setCustomId(modalId).setTitle("Ban");
|
||||
const durationIn = new TextInputBuilder()
|
||||
.setCustomId("duration")
|
||||
.setLabel("Duration (Optional)")
|
||||
.setRequired(false)
|
||||
.setStyle(TextInputStyle.Short);
|
||||
const reasonIn = new TextInputBuilder()
|
||||
.setCustomId("reason")
|
||||
.setLabel("Reason (Optional)")
|
||||
.setRequired(false)
|
||||
.setStyle(TextInputStyle.Paragraph);
|
||||
const evidenceIn = new TextInputBuilder()
|
||||
.setCustomId("evidence")
|
||||
.setLabel("Evidence (Optional)")
|
||||
.setRequired(false)
|
||||
.setStyle(TextInputStyle.Paragraph);
|
||||
const durationRow = new ActionRowBuilder<TextInputBuilder>().addComponents(durationIn);
|
||||
const reasonRow = new ActionRowBuilder<TextInputBuilder>().addComponents(reasonIn);
|
||||
const evidenceRow = new ActionRowBuilder<TextInputBuilder>().addComponents(evidenceIn);
|
||||
modal.addComponents(durationRow, reasonRow, evidenceRow);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
await interaction
|
||||
.awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId })
|
||||
.then(async (submitted) => {
|
||||
if (interaction.isButton()) {
|
||||
await submitted.deferUpdate().catch((err) => logger.error(`Ban interaction defer failed: ${err}`));
|
||||
} else if (interaction.isContextMenuCommand()) {
|
||||
await submitted.deferReply({ ephemeral: true });
|
||||
}
|
||||
|
||||
const duration = submitted.fields.getTextInputValue("duration");
|
||||
const reason = submitted.fields.getTextInputValue("reason");
|
||||
const evidence = submitted.fields.getTextInputValue("evidence");
|
||||
|
||||
await banAction(pluginData, duration, reason, evidence, target, interaction, submitted);
|
||||
});
|
||||
}
|
|
@ -1,16 +1,26 @@
|
|||
import { ContextMenuCommandInteraction, TextChannel } from "discord.js";
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
Message,
|
||||
MessageContextMenuCommandInteraction,
|
||||
ModalBuilder,
|
||||
ModalSubmitInteraction,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
} from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError.js";
|
||||
import { logger } from "../../../logger.js";
|
||||
import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin.js";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin.js";
|
||||
import { ContextMenuPluginType } from "../types.js";
|
||||
import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd.js";
|
||||
import { ContextMenuPluginType, ModMenuActionType } from "../types.js";
|
||||
|
||||
export async function cleanAction(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
amount: number,
|
||||
interaction: ContextMenuCommandInteraction,
|
||||
target: string,
|
||||
targetMessage: Message,
|
||||
targetChannel: string,
|
||||
interaction: ModalSubmitInteraction,
|
||||
) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const executingMember = await pluginData.guild.members.fetch(interaction.user.id);
|
||||
const userCfg = await pluginData.config.getMatchingConfig({
|
||||
channelId: interaction.channelId,
|
||||
|
@ -18,33 +28,54 @@ export async function cleanAction(
|
|||
});
|
||||
const utility = pluginData.getPlugin(UtilityPlugin);
|
||||
|
||||
if (!userCfg.can_use || !(await utility.hasPermission(executingMember, interaction.channelId, "can_clean"))) {
|
||||
await interaction.followUp({ content: "Cannot clean: insufficient permissions" });
|
||||
if (!userCfg.can_use || !(await utility.hasPermission(executingMember, targetChannel, "can_clean"))) {
|
||||
await interaction
|
||||
.editReply({ content: "Cannot clean: insufficient permissions", embeds: [], components: [] })
|
||||
.catch((err) => logger.error(`Clean interaction reply failed: ${err}`));
|
||||
return;
|
||||
}
|
||||
|
||||
const targetMessage = interaction.channel
|
||||
? await interaction.channel.messages.fetch(interaction.targetId)
|
||||
: await (pluginData.guild.channels.resolve(interaction.channelId) as TextChannel).messages.fetch(
|
||||
interaction.targetId,
|
||||
);
|
||||
await interaction
|
||||
.editReply({
|
||||
content: `Cleaning ${amount} messages from ${target}...`,
|
||||
embeds: [],
|
||||
components: [],
|
||||
})
|
||||
.catch((err) => logger.error(`Clean interaction reply failed: ${err}`));
|
||||
|
||||
const targetUserOnly = false;
|
||||
const deletePins = false;
|
||||
const user = undefined;
|
||||
|
||||
try {
|
||||
await interaction.followUp(`Cleaning... Amount: ${amount}, User Only: ${targetUserOnly}, Pins: ${deletePins}`);
|
||||
utility.clean({ count: amount, user, channel: targetMessage.channel.id, "delete-pins": deletePins }, targetMessage);
|
||||
} catch (e) {
|
||||
await interaction.followUp({ ephemeral: true, content: "Plugin error, please check your BOT_ALERTs" });
|
||||
|
||||
if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) {
|
||||
pluginData.getPlugin(LogsPlugin).logBotAlert({
|
||||
body: `Failed to clean in <#${interaction.channelId}> in ContextMenu action \`clean\`:_ ${e}`,
|
||||
});
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
await utility.clean({ count: amount, channel: targetChannel, "response-interaction": interaction }, targetMessage);
|
||||
}
|
||||
|
||||
export async function launchCleanActionModal(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
interaction: MessageContextMenuCommandInteraction,
|
||||
target: string,
|
||||
) {
|
||||
const modalId = `${ModMenuActionType.CLEAN}:${interaction.id}`;
|
||||
const modal = new ModalBuilder().setCustomId(modalId).setTitle("Clean");
|
||||
const amountIn = new TextInputBuilder().setCustomId("amount").setLabel("Amount").setStyle(TextInputStyle.Short);
|
||||
const amountRow = new ActionRowBuilder<TextInputBuilder>().addComponents(amountIn);
|
||||
modal.addComponents(amountRow);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
await interaction
|
||||
.awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId })
|
||||
.then(async (submitted) => {
|
||||
await submitted.deferReply({ ephemeral: true });
|
||||
|
||||
const amount = submitted.fields.getTextInputValue("amount");
|
||||
if (isNaN(Number(amount))) {
|
||||
interaction.editReply({ content: `Error: Amount '${amount}' is invalid`, embeds: [], components: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
await cleanAction(
|
||||
pluginData,
|
||||
Number(amount),
|
||||
target,
|
||||
interaction.targetMessage,
|
||||
interaction.channelId,
|
||||
submitted,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,21 +1,36 @@
|
|||
import { ContextMenuCommandInteraction } from "discord.js";
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonInteraction,
|
||||
ContextMenuCommandInteraction,
|
||||
ModalBuilder,
|
||||
ModalSubmitInteraction,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
} from "discord.js";
|
||||
import humanizeDuration from "humanize-duration";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError.js";
|
||||
import { logger } from "../../../logger.js";
|
||||
import { canActOn } from "../../../pluginUtils.js";
|
||||
import { convertDelayStringToMS } from "../../../utils.js";
|
||||
import { CaseArgs } from "../../Cases/types.js";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin.js";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin.js";
|
||||
import { MutesPlugin } from "../../Mutes/MutesPlugin.js";
|
||||
import { ContextMenuPluginType } from "../types.js";
|
||||
import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd.js";
|
||||
import { ContextMenuPluginType, ModMenuActionType } from "../types.js";
|
||||
import { updateAction } from "./update.js";
|
||||
|
||||
export async function muteAction(
|
||||
async function muteAction(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
duration: string | undefined,
|
||||
interaction: ContextMenuCommandInteraction,
|
||||
reason: string | undefined,
|
||||
evidence: string | undefined,
|
||||
target: string,
|
||||
interaction: ButtonInteraction | ContextMenuCommandInteraction,
|
||||
submitInteraction: ModalSubmitInteraction,
|
||||
) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const interactionToReply = interaction.isButton() ? interaction : submitInteraction;
|
||||
const executingMember = await pluginData.guild.members.fetch(interaction.user.id);
|
||||
const userCfg = await pluginData.config.getMatchingConfig({
|
||||
channelId: interaction.channelId,
|
||||
|
@ -24,43 +39,100 @@ export async function muteAction(
|
|||
|
||||
const modactions = pluginData.getPlugin(ModActionsPlugin);
|
||||
if (!userCfg.can_use || !(await modactions.hasMutePermission(executingMember, interaction.channelId))) {
|
||||
await interaction.followUp({ content: "Cannot mute: insufficient permissions" });
|
||||
await interactionToReply.editReply({
|
||||
content: "Cannot mute: insufficient permissions",
|
||||
embeds: [],
|
||||
components: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const durationMs = duration ? convertDelayStringToMS(duration)! : undefined;
|
||||
const mutes = pluginData.getPlugin(MutesPlugin);
|
||||
const userId = interaction.targetId;
|
||||
const targetMember = await pluginData.guild.members.fetch(interaction.targetId);
|
||||
|
||||
const targetMember = await pluginData.guild.members.fetch(target);
|
||||
if (!canActOn(pluginData, executingMember, targetMember)) {
|
||||
await interaction.followUp({ ephemeral: true, content: "Cannot mute: insufficient permissions" });
|
||||
await interactionToReply.editReply({
|
||||
content: "Cannot mute: insufficient permissions",
|
||||
embeds: [],
|
||||
components: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const caseArgs: Partial<CaseArgs> = {
|
||||
modId: executingMember.id,
|
||||
};
|
||||
const mutes = pluginData.getPlugin(MutesPlugin);
|
||||
const durationMs = duration ? convertDelayStringToMS(duration)! : undefined;
|
||||
|
||||
try {
|
||||
const result = await mutes.muteUser(userId, durationMs, "Context Menu Action", { caseArgs });
|
||||
|
||||
const result = await mutes.muteUser(target, durationMs, reason, reason, { caseArgs });
|
||||
const messageResultText = result.notifyResult.text ? ` (${result.notifyResult.text})` : "";
|
||||
const muteMessage = `Muted **${result.case!.user_name}** ${
|
||||
durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely"
|
||||
} (Case #${result.case!.case_number}) (user notified via ${
|
||||
result.notifyResult.method ?? "dm"
|
||||
})\nPlease update the new case with the \`update\` command`;
|
||||
} (Case #${result.case!.case_number})${messageResultText}`;
|
||||
|
||||
await interaction.followUp({ ephemeral: true, content: muteMessage });
|
||||
if (evidence) {
|
||||
await updateAction(pluginData, executingMember, result.case!, evidence);
|
||||
}
|
||||
|
||||
await interactionToReply.editReply({ content: muteMessage, embeds: [], components: [] });
|
||||
} catch (e) {
|
||||
await interaction.followUp({ ephemeral: true, content: "Plugin error, please check your BOT_ALERTs" });
|
||||
await interactionToReply.editReply({
|
||||
content: "Plugin error, please check your BOT_ALERTs",
|
||||
embeds: [],
|
||||
components: [],
|
||||
});
|
||||
|
||||
if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) {
|
||||
pluginData.getPlugin(LogsPlugin).logBotAlert({
|
||||
body: `Failed to mute <@!${userId}> in ContextMenu action \`mute\` because a mute role has not been specified in server config`,
|
||||
body: `Failed to mute <@!${target}> in ContextMenu action \`mute\` because a mute role has not been specified in server config`,
|
||||
});
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function launchMuteActionModal(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
interaction: ButtonInteraction | ContextMenuCommandInteraction,
|
||||
target: string,
|
||||
) {
|
||||
const modalId = `${ModMenuActionType.MUTE}:${interaction.id}`;
|
||||
const modal = new ModalBuilder().setCustomId(modalId).setTitle("Mute");
|
||||
const durationIn = new TextInputBuilder()
|
||||
.setCustomId("duration")
|
||||
.setLabel("Duration (Optional)")
|
||||
.setRequired(false)
|
||||
.setStyle(TextInputStyle.Short);
|
||||
const reasonIn = new TextInputBuilder()
|
||||
.setCustomId("reason")
|
||||
.setLabel("Reason (Optional)")
|
||||
.setRequired(false)
|
||||
.setStyle(TextInputStyle.Paragraph);
|
||||
const evidenceIn = new TextInputBuilder()
|
||||
.setCustomId("evidence")
|
||||
.setLabel("Evidence (Optional)")
|
||||
.setRequired(false)
|
||||
.setStyle(TextInputStyle.Paragraph);
|
||||
const durationRow = new ActionRowBuilder<TextInputBuilder>().addComponents(durationIn);
|
||||
const reasonRow = new ActionRowBuilder<TextInputBuilder>().addComponents(reasonIn);
|
||||
const evidenceRow = new ActionRowBuilder<TextInputBuilder>().addComponents(evidenceIn);
|
||||
modal.addComponents(durationRow, reasonRow, evidenceRow);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
await interaction
|
||||
.awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId })
|
||||
.then(async (submitted) => {
|
||||
if (interaction.isButton()) {
|
||||
await submitted.deferUpdate().catch((err) => logger.error(`Mute interaction defer failed: ${err}`));
|
||||
} else if (interaction.isContextMenuCommand()) {
|
||||
await submitted.deferReply({ ephemeral: true });
|
||||
}
|
||||
|
||||
const duration = submitted.fields.getTextInputValue("duration");
|
||||
const reason = submitted.fields.getTextInputValue("reason");
|
||||
const evidence = submitted.fields.getTextInputValue("evidence");
|
||||
|
||||
await muteAction(pluginData, duration, reason, evidence, target, interaction, submitted);
|
||||
});
|
||||
}
|
||||
|
|
103
backend/src/plugins/ContextMenus/actions/note.ts
Normal file
103
backend/src/plugins/ContextMenus/actions/note.ts
Normal file
|
@ -0,0 +1,103 @@
|
|||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonInteraction,
|
||||
ContextMenuCommandInteraction,
|
||||
ModalBuilder,
|
||||
ModalSubmitInteraction,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
} from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { CaseTypes } from "../../../data/CaseTypes.js";
|
||||
import { logger } from "../../../logger.js";
|
||||
import { canActOn } from "../../../pluginUtils.js";
|
||||
import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin.js";
|
||||
import { renderUserUsername } from "../../../utils.js";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin.js";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin.js";
|
||||
import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd.js";
|
||||
import { ContextMenuPluginType, ModMenuActionType } from "../types.js";
|
||||
|
||||
async function noteAction(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
reason: string,
|
||||
target: string,
|
||||
interaction: ButtonInteraction | ContextMenuCommandInteraction,
|
||||
submitInteraction: ModalSubmitInteraction,
|
||||
) {
|
||||
const interactionToReply = interaction.isButton() ? interaction : submitInteraction;
|
||||
const executingMember = await pluginData.guild.members.fetch(interaction.user.id);
|
||||
const userCfg = await pluginData.config.getMatchingConfig({
|
||||
channelId: interaction.channelId,
|
||||
member: executingMember,
|
||||
});
|
||||
|
||||
const modactions = pluginData.getPlugin(ModActionsPlugin);
|
||||
if (!userCfg.can_use || !(await modactions.hasNotePermission(executingMember, interaction.channelId))) {
|
||||
await interactionToReply.editReply({
|
||||
content: "Cannot note: insufficient permissions",
|
||||
embeds: [],
|
||||
components: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const targetMember = await pluginData.guild.members.fetch(target);
|
||||
if (!canActOn(pluginData, executingMember, targetMember)) {
|
||||
await interactionToReply.editReply({
|
||||
content: "Cannot note: insufficient permissions",
|
||||
embeds: [],
|
||||
components: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
userId: target,
|
||||
modId: executingMember.id,
|
||||
type: CaseTypes.Note,
|
||||
reason,
|
||||
});
|
||||
|
||||
pluginData.getPlugin(LogsPlugin).logMemberNote({
|
||||
mod: interaction.user,
|
||||
user: targetMember.user,
|
||||
caseNumber: createdCase.case_number,
|
||||
reason,
|
||||
});
|
||||
|
||||
const userName = renderUserUsername(targetMember.user);
|
||||
await interactionToReply.editReply({
|
||||
content: `Note added on **${userName}** (Case #${createdCase.case_number})`,
|
||||
embeds: [],
|
||||
components: [],
|
||||
});
|
||||
}
|
||||
|
||||
export async function launchNoteActionModal(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
interaction: ButtonInteraction | ContextMenuCommandInteraction,
|
||||
target: string,
|
||||
) {
|
||||
const modalId = `${ModMenuActionType.NOTE}:${interaction.id}`;
|
||||
const modal = new ModalBuilder().setCustomId(modalId).setTitle("Note");
|
||||
const reasonIn = new TextInputBuilder().setCustomId("reason").setLabel("Note").setStyle(TextInputStyle.Paragraph);
|
||||
const reasonRow = new ActionRowBuilder<TextInputBuilder>().addComponents(reasonIn);
|
||||
modal.addComponents(reasonRow);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
await interaction
|
||||
.awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId })
|
||||
.then(async (submitted) => {
|
||||
if (interaction.isButton()) {
|
||||
await submitted.deferUpdate().catch((err) => logger.error(`Note interaction defer failed: ${err}`));
|
||||
} else if (interaction.isContextMenuCommand()) {
|
||||
await submitted.deferReply({ ephemeral: true });
|
||||
}
|
||||
|
||||
const reason = submitted.fields.getTextInputValue("reason");
|
||||
|
||||
await noteAction(pluginData, reason, target, interaction, submitted);
|
||||
});
|
||||
}
|
28
backend/src/plugins/ContextMenus/actions/update.ts
Normal file
28
backend/src/plugins/ContextMenus/actions/update.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { GuildMember } from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { CaseTypes } from "../../../data/CaseTypes.js";
|
||||
import { Case } from "../../../data/entities/Case.js";
|
||||
import { CasesPlugin } from "../../Cases/CasesPlugin.js";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin.js";
|
||||
import { ContextMenuPluginType } from "../types.js";
|
||||
|
||||
export async function updateAction(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
executingMember: GuildMember,
|
||||
theCase: Case,
|
||||
value: string,
|
||||
) {
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
await casesPlugin.createCaseNote({
|
||||
caseId: theCase.case_number,
|
||||
modId: executingMember.id,
|
||||
body: value,
|
||||
});
|
||||
|
||||
void pluginData.getPlugin(LogsPlugin).logCaseUpdate({
|
||||
mod: executingMember.user,
|
||||
caseNumber: theCase.case_number,
|
||||
caseType: CaseTypes[theCase.type],
|
||||
note: value,
|
||||
});
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { ContextMenuCommandInteraction } from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin.js";
|
||||
import { ContextMenuPluginType } from "../types.js";
|
||||
|
||||
export async function userInfoAction(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
interaction: ContextMenuCommandInteraction,
|
||||
) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const executingMember = await pluginData.guild.members.fetch(interaction.user.id);
|
||||
const userCfg = await pluginData.config.getMatchingConfig({
|
||||
channelId: interaction.channelId,
|
||||
member: executingMember,
|
||||
});
|
||||
const utility = pluginData.getPlugin(UtilityPlugin);
|
||||
|
||||
if (userCfg.can_use && (await utility.hasPermission(executingMember, interaction.channelId, "can_userinfo"))) {
|
||||
const embed = await utility.userInfo(interaction.targetId);
|
||||
if (!embed) {
|
||||
await interaction.followUp({ content: "Cannot info: internal error" });
|
||||
return;
|
||||
}
|
||||
await interaction.followUp({ embeds: [embed] });
|
||||
} else {
|
||||
await interaction.followUp({ content: "Cannot info: insufficient permissions" });
|
||||
}
|
||||
}
|
108
backend/src/plugins/ContextMenus/actions/warn.ts
Normal file
108
backend/src/plugins/ContextMenus/actions/warn.ts
Normal file
|
@ -0,0 +1,108 @@
|
|||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonInteraction,
|
||||
ContextMenuCommandInteraction,
|
||||
ModalBuilder,
|
||||
ModalSubmitInteraction,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
} from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { logger } from "../../../logger.js";
|
||||
import { canActOn } from "../../../pluginUtils.js";
|
||||
import { renderUserUsername } from "../../../utils.js";
|
||||
import { CaseArgs } from "../../Cases/types.js";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin.js";
|
||||
import { MODAL_TIMEOUT } from "../commands/ModMenuUserCtxCmd.js";
|
||||
import { ContextMenuPluginType, ModMenuActionType } from "../types.js";
|
||||
import { updateAction } from "./update.js";
|
||||
|
||||
async function warnAction(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
reason: string,
|
||||
evidence: string | undefined,
|
||||
target: string,
|
||||
interaction: ButtonInteraction | ContextMenuCommandInteraction,
|
||||
submitInteraction: ModalSubmitInteraction,
|
||||
) {
|
||||
const interactionToReply = interaction.isButton() ? interaction : submitInteraction;
|
||||
const executingMember = await pluginData.guild.members.fetch(interaction.user.id);
|
||||
const userCfg = await pluginData.config.getMatchingConfig({
|
||||
channelId: interaction.channelId,
|
||||
member: executingMember,
|
||||
});
|
||||
|
||||
const modactions = pluginData.getPlugin(ModActionsPlugin);
|
||||
if (!userCfg.can_use || !(await modactions.hasWarnPermission(executingMember, interaction.channelId))) {
|
||||
await interactionToReply.editReply({
|
||||
content: "Cannot warn: insufficient permissions",
|
||||
embeds: [],
|
||||
components: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const targetMember = await pluginData.guild.members.fetch(target);
|
||||
if (!canActOn(pluginData, executingMember, targetMember)) {
|
||||
await interactionToReply.editReply({
|
||||
content: "Cannot warn: insufficient permissions",
|
||||
embeds: [],
|
||||
components: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const caseArgs: Partial<CaseArgs> = {
|
||||
modId: executingMember.id,
|
||||
};
|
||||
|
||||
const result = await modactions.warnMember(targetMember, reason, reason, { caseArgs });
|
||||
if (result.status === "failed") {
|
||||
await interactionToReply.editReply({ content: "Error: Failed to warn user", embeds: [], components: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
const userName = renderUserUsername(targetMember.user);
|
||||
const messageResultText = result.notifyResult.text ? ` (${result.notifyResult.text})` : "";
|
||||
const muteMessage = `Warned **${userName}** (Case #${result.case.case_number})${messageResultText}`;
|
||||
|
||||
if (evidence) {
|
||||
await updateAction(pluginData, executingMember, result.case, evidence);
|
||||
}
|
||||
|
||||
await interactionToReply.editReply({ content: muteMessage, embeds: [], components: [] });
|
||||
}
|
||||
|
||||
export async function launchWarnActionModal(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
interaction: ButtonInteraction | ContextMenuCommandInteraction,
|
||||
target: string,
|
||||
) {
|
||||
const modalId = `${ModMenuActionType.WARN}:${interaction.id}`;
|
||||
const modal = new ModalBuilder().setCustomId(modalId).setTitle("Warn");
|
||||
const reasonIn = new TextInputBuilder().setCustomId("reason").setLabel("Reason").setStyle(TextInputStyle.Paragraph);
|
||||
const evidenceIn = new TextInputBuilder()
|
||||
.setCustomId("evidence")
|
||||
.setLabel("Evidence (Optional)")
|
||||
.setRequired(false)
|
||||
.setStyle(TextInputStyle.Paragraph);
|
||||
const reasonRow = new ActionRowBuilder<TextInputBuilder>().addComponents(reasonIn);
|
||||
const evidenceRow = new ActionRowBuilder<TextInputBuilder>().addComponents(evidenceIn);
|
||||
modal.addComponents(reasonRow, evidenceRow);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
await interaction
|
||||
.awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId })
|
||||
.then(async (submitted) => {
|
||||
if (interaction.isButton()) {
|
||||
await submitted.deferUpdate().catch((err) => logger.error(`Warn interaction defer failed: ${err}`));
|
||||
} else if (interaction.isContextMenuCommand()) {
|
||||
await submitted.deferReply({ ephemeral: true });
|
||||
}
|
||||
|
||||
const reason = submitted.fields.getTextInputValue("reason");
|
||||
const evidence = submitted.fields.getTextInputValue("evidence");
|
||||
|
||||
await warnAction(pluginData, reason, evidence, target, interaction, submitted);
|
||||
});
|
||||
}
|
11
backend/src/plugins/ContextMenus/commands/BanUserCtxCmd.ts
Normal file
11
backend/src/plugins/ContextMenus/commands/BanUserCtxCmd.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { PermissionFlagsBits } from "discord.js";
|
||||
import { guildPluginUserContextMenuCommand } from "knub";
|
||||
import { launchBanActionModal } from "../actions/ban.js";
|
||||
|
||||
export const BanCmd = guildPluginUserContextMenuCommand({
|
||||
name: "Ban",
|
||||
defaultMemberPermissions: PermissionFlagsBits.BanMembers.toString(),
|
||||
async run({ pluginData, interaction }) {
|
||||
await launchBanActionModal(pluginData, interaction, interaction.targetId);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import { PermissionFlagsBits } from "discord.js";
|
||||
import { guildPluginMessageContextMenuCommand } from "knub";
|
||||
import { launchCleanActionModal } from "../actions/clean.js";
|
||||
|
||||
export const CleanCmd = guildPluginMessageContextMenuCommand({
|
||||
name: "Clean",
|
||||
defaultMemberPermissions: PermissionFlagsBits.ManageMessages.toString(),
|
||||
async run({ pluginData, interaction }) {
|
||||
await launchCleanActionModal(pluginData, interaction, interaction.targetId);
|
||||
},
|
||||
});
|
328
backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts
Normal file
328
backend/src/plugins/ContextMenus/commands/ModMenuUserCtxCmd.ts
Normal file
|
@ -0,0 +1,328 @@
|
|||
import {
|
||||
APIEmbed,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonInteraction,
|
||||
ButtonStyle,
|
||||
ContextMenuCommandInteraction,
|
||||
GuildMember,
|
||||
PermissionFlagsBits,
|
||||
User,
|
||||
} from "discord.js";
|
||||
import { GuildPluginData, guildPluginUserContextMenuCommand } from "knub";
|
||||
import { Case } from "../../../data/entities/Case.js";
|
||||
import { logger } from "../../../logger.js";
|
||||
import { SECONDS, UnknownUser, emptyEmbedValue, renderUserUsername, resolveUser, trimLines } from "../../../utils.js";
|
||||
import { asyncMap } from "../../../utils/async.js";
|
||||
import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields.js";
|
||||
import { getGuildPrefix } from "../../../utils/getGuildPrefix.js";
|
||||
import { CasesPlugin } from "../../Cases/CasesPlugin.js";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin.js";
|
||||
import { getUserInfoEmbed } from "../../Utility/functions/getUserInfoEmbed.js";
|
||||
import { launchBanActionModal } from "../actions/ban.js";
|
||||
import { launchMuteActionModal } from "../actions/mute.js";
|
||||
import { launchNoteActionModal } from "../actions/note.js";
|
||||
import { launchWarnActionModal } from "../actions/warn.js";
|
||||
import {
|
||||
ContextMenuPluginType,
|
||||
LoadModMenuPageFn,
|
||||
ModMenuActionOpts,
|
||||
ModMenuActionType,
|
||||
ModMenuNavigationType,
|
||||
} from "../types.js";
|
||||
|
||||
export const MODAL_TIMEOUT = 60 * SECONDS;
|
||||
const MOD_MENU_TIMEOUT = 60 * SECONDS;
|
||||
const CASES_PER_PAGE = 10;
|
||||
|
||||
export const ModMenuCmd = guildPluginUserContextMenuCommand({
|
||||
name: "Mod Menu",
|
||||
defaultMemberPermissions: PermissionFlagsBits.ViewAuditLog.toString(),
|
||||
async run({ pluginData, interaction }) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
// Run permission checks for executing user.
|
||||
const executingMember = await pluginData.guild.members.fetch(interaction.user.id);
|
||||
const userCfg = await pluginData.config.getMatchingConfig({
|
||||
channelId: interaction.channelId,
|
||||
member: executingMember,
|
||||
});
|
||||
if (!userCfg.can_use || !userCfg.can_open_mod_menu) {
|
||||
await interaction.followUp({ content: "Error: Insufficient Permissions" });
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await resolveUser(pluginData.client, interaction.targetId);
|
||||
if (!user.id) {
|
||||
await interaction.followUp("Error: User not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Load cases and display mod menu
|
||||
const cases: Case[] = await pluginData.state.cases.with("notes").getByUserId(user.id);
|
||||
const userName =
|
||||
user instanceof UnknownUser && cases.length ? cases[cases.length - 1].user_name : renderUserUsername(user);
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const totalCases = cases.length;
|
||||
const totalPages: number = Math.max(Math.ceil(totalCases / CASES_PER_PAGE), 1);
|
||||
const prefix = getGuildPrefix(pluginData);
|
||||
const infoEmbed = await getUserInfoEmbed(pluginData, user.id, false);
|
||||
displayModMenu(
|
||||
pluginData,
|
||||
interaction,
|
||||
totalPages,
|
||||
async (page) => {
|
||||
const pageCases: Case[] = await pluginData.state.cases
|
||||
.with("notes")
|
||||
.getRecentByUserId(user.id, CASES_PER_PAGE, (page - 1) * CASES_PER_PAGE);
|
||||
const lines = await asyncMap(pageCases, (c) => casesPlugin.getCaseSummary(c, true, interaction.targetId));
|
||||
|
||||
const firstCaseNum = (page - 1) * CASES_PER_PAGE + 1;
|
||||
const lastCaseNum = Math.min(page * CASES_PER_PAGE, totalCases);
|
||||
const title =
|
||||
lines.length == 0
|
||||
? `${userName}`
|
||||
: `Most recent cases for ${userName} | ${firstCaseNum}-${lastCaseNum} of ${totalCases}`;
|
||||
|
||||
const embed = {
|
||||
author: {
|
||||
name: title,
|
||||
icon_url: user instanceof User ? user.displayAvatarURL() : undefined,
|
||||
},
|
||||
fields: [
|
||||
...getChunkedEmbedFields(
|
||||
emptyEmbedValue,
|
||||
lines.length == 0 ? `No cases found for **${userName}**` : lines.join("\n"),
|
||||
),
|
||||
{
|
||||
name: emptyEmbedValue,
|
||||
value: trimLines(
|
||||
lines.length == 0 ? "" : `Use \`${prefix}case <num>\` to see more information about an individual case`,
|
||||
),
|
||||
},
|
||||
],
|
||||
footer: { text: `Page ${page}/${totalPages}` },
|
||||
} satisfies APIEmbed;
|
||||
|
||||
return embed;
|
||||
},
|
||||
infoEmbed,
|
||||
executingMember,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
async function displayModMenu(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
interaction: ContextMenuCommandInteraction,
|
||||
totalPages: number,
|
||||
loadPage: LoadModMenuPageFn,
|
||||
infoEmbed: APIEmbed | null,
|
||||
executingMember: GuildMember,
|
||||
) {
|
||||
if (interaction.deferred == false) {
|
||||
await interaction.deferReply().catch((err) => logger.error(`Mod menu interaction defer failed: ${err}`));
|
||||
}
|
||||
|
||||
const firstButton = new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setEmoji("⏪")
|
||||
.setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.FIRST }))
|
||||
.setDisabled(true);
|
||||
const prevButton = new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setEmoji("⬅")
|
||||
.setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.PREV }))
|
||||
.setDisabled(true);
|
||||
const infoButton = new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setLabel("Info")
|
||||
.setEmoji("ℹ")
|
||||
.setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.INFO }))
|
||||
.setDisabled(infoEmbed != null ? false : true);
|
||||
const nextButton = new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setEmoji("➡")
|
||||
.setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.NEXT }))
|
||||
.setDisabled(totalPages > 1 ? false : true);
|
||||
const lastButton = new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setEmoji("⏩")
|
||||
.setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.LAST }))
|
||||
.setDisabled(totalPages > 1 ? false : true);
|
||||
const navigationButtons = [firstButton, prevButton, infoButton, nextButton, lastButton] satisfies ButtonBuilder[];
|
||||
|
||||
const modactions = pluginData.getPlugin(ModActionsPlugin);
|
||||
const moderationButtons = [
|
||||
new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setLabel("Note")
|
||||
.setEmoji("📝")
|
||||
.setDisabled(!(await modactions.hasNotePermission(executingMember, interaction.channelId)))
|
||||
.setCustomId(serializeCustomId({ action: ModMenuActionType.NOTE, target: interaction.targetId })),
|
||||
new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setLabel("Warn")
|
||||
.setEmoji("⚠️")
|
||||
.setDisabled(!(await modactions.hasWarnPermission(executingMember, interaction.channelId)))
|
||||
.setCustomId(serializeCustomId({ action: ModMenuActionType.WARN, target: interaction.targetId })),
|
||||
new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setLabel("Mute")
|
||||
.setEmoji("🔇")
|
||||
.setDisabled(!(await modactions.hasMutePermission(executingMember, interaction.channelId)))
|
||||
.setCustomId(serializeCustomId({ action: ModMenuActionType.MUTE, target: interaction.targetId })),
|
||||
new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setLabel("Ban")
|
||||
.setEmoji("🚫")
|
||||
.setDisabled(!(await modactions.hasBanPermission(executingMember, interaction.channelId)))
|
||||
.setCustomId(serializeCustomId({ action: ModMenuActionType.BAN, target: interaction.targetId })),
|
||||
] satisfies ButtonBuilder[];
|
||||
|
||||
const navigationRow = new ActionRowBuilder<ButtonBuilder>().addComponents(navigationButtons);
|
||||
const moderationRow = new ActionRowBuilder<ButtonBuilder>().addComponents(moderationButtons);
|
||||
|
||||
let page = 1;
|
||||
await interaction
|
||||
.editReply({
|
||||
embeds: [await loadPage(page)],
|
||||
components: [navigationRow, moderationRow],
|
||||
})
|
||||
.then(async (currentPage) => {
|
||||
const collector = await currentPage.createMessageComponentCollector({
|
||||
time: MOD_MENU_TIMEOUT,
|
||||
});
|
||||
|
||||
collector.on("collect", async (i) => {
|
||||
const opts = deserializeCustomId(i.customId);
|
||||
if (opts.action == ModMenuActionType.PAGE) {
|
||||
await i.deferUpdate().catch((err) => logger.error(`Mod menu defer failed: ${err}`));
|
||||
}
|
||||
|
||||
// Update displayed embed if any navigation buttons were used
|
||||
if (opts.action == ModMenuActionType.PAGE && opts.target == ModMenuNavigationType.INFO && infoEmbed != null) {
|
||||
infoButton
|
||||
.setLabel("Cases")
|
||||
.setEmoji("📋")
|
||||
.setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.CASES }));
|
||||
firstButton.setDisabled(true);
|
||||
prevButton.setDisabled(true);
|
||||
nextButton.setDisabled(true);
|
||||
lastButton.setDisabled(true);
|
||||
|
||||
await i
|
||||
.editReply({
|
||||
embeds: [infoEmbed],
|
||||
components: [navigationRow, moderationRow],
|
||||
})
|
||||
.catch((err) => logger.error(`Mod menu info view failed: ${err}`));
|
||||
} else if (opts.action == ModMenuActionType.PAGE && opts.target == ModMenuNavigationType.CASES) {
|
||||
infoButton
|
||||
.setLabel("Info")
|
||||
.setEmoji("ℹ")
|
||||
.setCustomId(serializeCustomId({ action: ModMenuActionType.PAGE, target: ModMenuNavigationType.INFO }));
|
||||
updateNavButtonState(firstButton, prevButton, nextButton, lastButton, page, totalPages);
|
||||
|
||||
await i
|
||||
.editReply({
|
||||
embeds: [await loadPage(page)],
|
||||
components: [navigationRow, moderationRow],
|
||||
})
|
||||
.catch((err) => logger.error(`Mod menu cases view failed: ${err}`));
|
||||
} else if (opts.action == ModMenuActionType.PAGE) {
|
||||
let pageDelta = 0;
|
||||
switch (opts.target) {
|
||||
case ModMenuNavigationType.PREV:
|
||||
pageDelta = -1;
|
||||
break;
|
||||
case ModMenuNavigationType.NEXT:
|
||||
pageDelta = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
let newPage = 1;
|
||||
if (opts.target == ModMenuNavigationType.PREV || opts.target == ModMenuNavigationType.NEXT) {
|
||||
newPage = Math.max(Math.min(page + pageDelta, totalPages), 1);
|
||||
} else if (opts.target == ModMenuNavigationType.FIRST) {
|
||||
newPage = 1;
|
||||
} else if (opts.target == ModMenuNavigationType.LAST) {
|
||||
newPage = totalPages;
|
||||
}
|
||||
|
||||
if (newPage != page) {
|
||||
updateNavButtonState(firstButton, prevButton, nextButton, lastButton, newPage, totalPages);
|
||||
|
||||
await i
|
||||
.editReply({
|
||||
embeds: [await loadPage(newPage)],
|
||||
components: [navigationRow, moderationRow],
|
||||
})
|
||||
.catch((err) => logger.error(`Mod menu navigation failed: ${err}`));
|
||||
|
||||
page = newPage;
|
||||
}
|
||||
} else if (opts.action == ModMenuActionType.NOTE) {
|
||||
await launchNoteActionModal(pluginData, i as ButtonInteraction, opts.target);
|
||||
} else if (opts.action == ModMenuActionType.WARN) {
|
||||
await launchWarnActionModal(pluginData, i as ButtonInteraction, opts.target);
|
||||
} else if (opts.action == ModMenuActionType.MUTE) {
|
||||
await launchMuteActionModal(pluginData, i as ButtonInteraction, opts.target);
|
||||
} else if (opts.action == ModMenuActionType.BAN) {
|
||||
await launchBanActionModal(pluginData, i as ButtonInteraction, opts.target);
|
||||
}
|
||||
|
||||
collector.resetTimer();
|
||||
});
|
||||
|
||||
// Remove components on timeout.
|
||||
collector.on("end", async (_, reason) => {
|
||||
if (reason !== "messageDelete") {
|
||||
await interaction
|
||||
.editReply({
|
||||
components: [],
|
||||
})
|
||||
.catch((err) => logger.error(`Mod menu timeout failed: ${err}`));
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((err) => logger.error(`Mod menu setup failed: ${err}`));
|
||||
}
|
||||
|
||||
function serializeCustomId(opts: ModMenuActionOpts) {
|
||||
return `${opts.action}:${opts.target}`;
|
||||
}
|
||||
|
||||
function deserializeCustomId(customId: string): ModMenuActionOpts {
|
||||
const opts: ModMenuActionOpts = {
|
||||
action: customId.split(":")[0] as ModMenuActionType,
|
||||
target: customId.split(":")[1],
|
||||
};
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
function updateNavButtonState(
|
||||
firstButton: ButtonBuilder,
|
||||
prevButton: ButtonBuilder,
|
||||
nextButton: ButtonBuilder,
|
||||
lastButton: ButtonBuilder,
|
||||
currentPage: number,
|
||||
totalPages: number,
|
||||
) {
|
||||
if (currentPage > 1) {
|
||||
firstButton.setDisabled(false);
|
||||
prevButton.setDisabled(false);
|
||||
} else {
|
||||
firstButton.setDisabled(true);
|
||||
prevButton.setDisabled(true);
|
||||
}
|
||||
|
||||
if (currentPage == totalPages) {
|
||||
nextButton.setDisabled(true);
|
||||
lastButton.setDisabled(true);
|
||||
} else {
|
||||
nextButton.setDisabled(false);
|
||||
lastButton.setDisabled(false);
|
||||
}
|
||||
}
|
11
backend/src/plugins/ContextMenus/commands/MuteUserCtxCmd.ts
Normal file
11
backend/src/plugins/ContextMenus/commands/MuteUserCtxCmd.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { PermissionFlagsBits } from "discord.js";
|
||||
import { guildPluginUserContextMenuCommand } from "knub";
|
||||
import { launchMuteActionModal } from "../actions/mute.js";
|
||||
|
||||
export const MuteCmd = guildPluginUserContextMenuCommand({
|
||||
name: "Mute",
|
||||
defaultMemberPermissions: PermissionFlagsBits.ModerateMembers.toString(),
|
||||
async run({ pluginData, interaction }) {
|
||||
await launchMuteActionModal(pluginData, interaction, interaction.targetId);
|
||||
},
|
||||
});
|
11
backend/src/plugins/ContextMenus/commands/NoteUserCtxCmd.ts
Normal file
11
backend/src/plugins/ContextMenus/commands/NoteUserCtxCmd.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { PermissionFlagsBits } from "discord.js";
|
||||
import { guildPluginUserContextMenuCommand } from "knub";
|
||||
import { launchNoteActionModal } from "../actions/note.js";
|
||||
|
||||
export const NoteCmd = guildPluginUserContextMenuCommand({
|
||||
name: "Note",
|
||||
defaultMemberPermissions: PermissionFlagsBits.ManageMessages.toString(),
|
||||
async run({ pluginData, interaction }) {
|
||||
await launchNoteActionModal(pluginData, interaction, interaction.targetId);
|
||||
},
|
||||
});
|
11
backend/src/plugins/ContextMenus/commands/WarnUserCtxCmd.ts
Normal file
11
backend/src/plugins/ContextMenus/commands/WarnUserCtxCmd.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { PermissionFlagsBits } from "discord.js";
|
||||
import { guildPluginUserContextMenuCommand } from "knub";
|
||||
import { launchWarnActionModal } from "../actions/warn.js";
|
||||
|
||||
export const WarnCmd = guildPluginUserContextMenuCommand({
|
||||
name: "Warn",
|
||||
defaultMemberPermissions: PermissionFlagsBits.ManageMessages.toString(),
|
||||
async run({ pluginData, interaction }) {
|
||||
await launchWarnActionModal(pluginData, interaction, interaction.targetId);
|
||||
},
|
||||
});
|
|
@ -1,12 +0,0 @@
|
|||
import { contextMenuEvt } from "../types.js";
|
||||
import { routeContextAction } from "../utils/contextRouter.js";
|
||||
|
||||
export const ContextClickedEvt = contextMenuEvt({
|
||||
event: "interactionCreate",
|
||||
|
||||
async listener(meta) {
|
||||
if (!meta.args.interaction.isContextMenuCommand()) return;
|
||||
const inter = meta.args.interaction;
|
||||
await routeContextAction(meta.pluginData, inter);
|
||||
},
|
||||
});
|
|
@ -1,23 +1,41 @@
|
|||
import { BasePluginType, guildPluginEventListener } from "knub";
|
||||
import { APIEmbed, Awaitable } from "discord.js";
|
||||
import { BasePluginType } from "knub";
|
||||
import z from "zod";
|
||||
import { GuildContextMenuLinks } from "../../data/GuildContextMenuLinks.js";
|
||||
import { GuildCases } from "../../data/GuildCases.js";
|
||||
|
||||
export const zContextMenusConfig = z.strictObject({
|
||||
can_use: z.boolean(),
|
||||
user_muteindef: z.boolean(),
|
||||
user_mute1d: z.boolean(),
|
||||
user_mute1h: z.boolean(),
|
||||
user_info: z.boolean(),
|
||||
message_clean10: z.boolean(),
|
||||
message_clean25: z.boolean(),
|
||||
message_clean50: z.boolean(),
|
||||
can_open_mod_menu: z.boolean(),
|
||||
});
|
||||
|
||||
export interface ContextMenuPluginType extends BasePluginType {
|
||||
config: z.infer<typeof zContextMenusConfig>;
|
||||
state: {
|
||||
contextMenuLinks: GuildContextMenuLinks;
|
||||
cases: GuildCases;
|
||||
};
|
||||
}
|
||||
|
||||
export const contextMenuEvt = guildPluginEventListener<ContextMenuPluginType>();
|
||||
export const enum ModMenuActionType {
|
||||
PAGE = "page",
|
||||
NOTE = "note",
|
||||
WARN = "warn",
|
||||
CLEAN = "clean",
|
||||
MUTE = "mute",
|
||||
BAN = "ban",
|
||||
}
|
||||
|
||||
export const enum ModMenuNavigationType {
|
||||
FIRST = "first",
|
||||
PREV = "prev",
|
||||
NEXT = "next",
|
||||
LAST = "last",
|
||||
INFO = "info",
|
||||
CASES = "cases",
|
||||
}
|
||||
|
||||
export interface ModMenuActionOpts {
|
||||
action: ModMenuActionType;
|
||||
target: string;
|
||||
}
|
||||
|
||||
export type LoadModMenuPageFn = (page: number) => Awaitable<APIEmbed>;
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import { ContextMenuCommandInteraction } from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { ContextMenuPluginType } from "../types.js";
|
||||
import { hardcodedActions } from "./hardcodedContextOptions.js";
|
||||
|
||||
export async function routeContextAction(
|
||||
pluginData: GuildPluginData<ContextMenuPluginType>,
|
||||
interaction: ContextMenuCommandInteraction,
|
||||
) {
|
||||
const contextLink = await pluginData.state.contextMenuLinks.get(interaction.commandId);
|
||||
if (!contextLink) return;
|
||||
hardcodedActions[contextLink.action_name](pluginData, interaction);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import { cleanAction } from "../actions/clean.js";
|
||||
import { muteAction } from "../actions/mute.js";
|
||||
import { userInfoAction } from "../actions/userInfo.js";
|
||||
|
||||
export const hardcodedContext: Record<string, string> = {
|
||||
user_muteindef: "Mute Indefinitely",
|
||||
user_mute1d: "Mute for 1 day",
|
||||
user_mute1h: "Mute for 1 hour",
|
||||
user_info: "Get Info",
|
||||
message_clean10: "Clean 10 messages",
|
||||
message_clean25: "Clean 25 messages",
|
||||
message_clean50: "Clean 50 messages",
|
||||
};
|
||||
|
||||
export const hardcodedActions = {
|
||||
user_muteindef: (pluginData, interaction) => muteAction(pluginData, undefined, interaction),
|
||||
user_mute1d: (pluginData, interaction) => muteAction(pluginData, "1d", interaction),
|
||||
user_mute1h: (pluginData, interaction) => muteAction(pluginData, "1h", interaction),
|
||||
user_info: (pluginData, interaction) => userInfoAction(pluginData, interaction),
|
||||
message_clean10: (pluginData, interaction) => cleanAction(pluginData, 10, interaction),
|
||||
message_clean25: (pluginData, interaction) => cleanAction(pluginData, 25, interaction),
|
||||
message_clean50: (pluginData, interaction) => cleanAction(pluginData, 50, interaction),
|
||||
};
|
|
@ -1,39 +0,0 @@
|
|||
import { ApplicationCommandData, ApplicationCommandType } from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { LogsPlugin } from "../../../plugins/Logs/LogsPlugin.js";
|
||||
import { ContextMenuPluginType } from "../types.js";
|
||||
import { hardcodedContext } from "./hardcodedContextOptions.js";
|
||||
|
||||
export async function loadAllCommands(pluginData: GuildPluginData<ContextMenuPluginType>) {
|
||||
const comms = await pluginData.client.application!.commands;
|
||||
const cfg = pluginData.config.get();
|
||||
const newCommands: ApplicationCommandData[] = [];
|
||||
const addedNames: string[] = [];
|
||||
|
||||
for (const [name, label] of Object.entries(hardcodedContext)) {
|
||||
if (!cfg[name]) continue;
|
||||
|
||||
const type = name.startsWith("user") ? ApplicationCommandType.User : ApplicationCommandType.Message;
|
||||
const data: ApplicationCommandData = {
|
||||
type,
|
||||
name: label,
|
||||
};
|
||||
|
||||
addedNames.push(name);
|
||||
newCommands.push(data);
|
||||
}
|
||||
|
||||
const setCommands = await comms.set(newCommands, pluginData.guild.id).catch((e) => {
|
||||
pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Unable to overwrite context menus: ${e}` });
|
||||
return undefined;
|
||||
});
|
||||
if (!setCommands) return;
|
||||
|
||||
const setCommandsArray = [...setCommands.values()];
|
||||
await pluginData.state.contextMenuLinks.deleteAll();
|
||||
|
||||
for (let i = 0; i < setCommandsArray.length; i++) {
|
||||
const command = setCommandsArray[i];
|
||||
pluginData.state.contextMenuLinks.create(command.id, addedNames[i]);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import { GuildCounters } from "../../data/GuildCounters.js";
|
|||
import { CounterTrigger, parseCounterConditionString } from "../../data/entities/CounterTrigger.js";
|
||||
import { makePublicFn } from "../../pluginUtils.js";
|
||||
import { MINUTES, convertDelayStringToMS, values } from "../../utils.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
import { AddCounterCmd } from "./commands/AddCounterCmd.js";
|
||||
import { CountersListCmd } from "./commands/CountersListCmd.js";
|
||||
import { ResetAllCounterValuesCmd } from "./commands/ResetAllCounterValuesCmd.js";
|
||||
|
@ -127,6 +128,10 @@ export const CountersPlugin = guildPlugin<CountersPluginType>()({
|
|||
await state.counters.markUnusedTriggersToBeDeleted(activeTriggerIds);
|
||||
},
|
||||
|
||||
beforeStart(pluginData) {
|
||||
pluginData.state.common = pluginData.getPlugin(CommonPlugin);
|
||||
},
|
||||
|
||||
async afterLoad(pluginData) {
|
||||
const { state } = pluginData;
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Snowflake, TextChannel } from "discord.js";
|
|||
import { guildPluginMessageCommand } from "knub";
|
||||
import { waitForReply } from "knub/helpers";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { UnknownUser, resolveUser } from "../../../utils.js";
|
||||
import { changeCounterValue } from "../functions/changeCounterValue.js";
|
||||
import { CountersPluginType } from "../types.js";
|
||||
|
@ -45,22 +44,22 @@ export const AddCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
const counter = config.counters[args.counterName];
|
||||
const counterId = pluginData.state.counterIds[args.counterName];
|
||||
if (!counter || !counterId) {
|
||||
sendErrorMessage(pluginData, message.channel, `Unknown counter: ${args.counterName}`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `Unknown counter: ${args.counterName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (counter.can_edit === false) {
|
||||
sendErrorMessage(pluginData, message.channel, `Missing permissions to edit this counter's value`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `Missing permissions to edit this counter's value`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.channel && !counter.per_channel) {
|
||||
sendErrorMessage(pluginData, message.channel, `This counter is not per-channel`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `This counter is not per-channel`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.user && !counter.per_user) {
|
||||
sendErrorMessage(pluginData, message.channel, `This counter is not per-user`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `This counter is not per-user`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -69,13 +68,13 @@ export const AddCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
message.channel.send(`Which channel's counter value would you like to add to?`);
|
||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||
if (!reply || !reply.content) {
|
||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake);
|
||||
if (!potentialChannel || !(potentialChannel instanceof TextChannel)) {
|
||||
sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Channel is not a text channel, cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -87,13 +86,13 @@ export const AddCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
message.channel.send(`Which user's counter value would you like to add to?`);
|
||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||
if (!reply || !reply.content) {
|
||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialUser = await resolveUser(pluginData.client, reply.content);
|
||||
if (!potentialUser || potentialUser instanceof UnknownUser) {
|
||||
sendErrorMessage(pluginData, message.channel, "Unknown user, cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Unknown user, cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -105,13 +104,13 @@ export const AddCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
message.channel.send("How much would you like to add to the counter's value?");
|
||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||
if (!reply || !reply.content) {
|
||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialAmount = parseInt(reply.content, 10);
|
||||
if (!potentialAmount) {
|
||||
sendErrorMessage(pluginData, message.channel, "Not a number, cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Not a number, cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { guildPluginMessageCommand } from "knub";
|
||||
import { sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { trimMultilineString, ucfirst } from "../../../utils.js";
|
||||
import { getGuildPrefix } from "../../../utils/getGuildPrefix.js";
|
||||
import { CountersPluginType } from "../types.js";
|
||||
|
@ -15,7 +14,7 @@ export const CountersListCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
|
||||
const countersToShow = Array.from(Object.values(config.counters)).filter((c) => c.can_view !== false);
|
||||
if (!countersToShow.length) {
|
||||
sendErrorMessage(pluginData, message.channel, "No counters are configured for this server");
|
||||
void pluginData.state.common.sendErrorMessage(message, "No counters are configured for this server");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { guildPluginMessageCommand } from "knub";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { confirm, noop, trimMultilineString } from "../../../utils.js";
|
||||
import { resetAllCounterValues } from "../functions/resetAllCounterValues.js";
|
||||
import { CountersPluginType } from "../types.js";
|
||||
|
@ -18,17 +17,20 @@ export const ResetAllCounterValuesCmd = guildPluginMessageCommand<CountersPlugin
|
|||
const counter = config.counters[args.counterName];
|
||||
const counterId = pluginData.state.counterIds[args.counterName];
|
||||
if (!counter || !counterId) {
|
||||
sendErrorMessage(pluginData, message.channel, `Unknown counter: ${args.counterName}`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `Unknown counter: ${args.counterName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (counter.can_reset_all === false) {
|
||||
sendErrorMessage(pluginData, message.channel, `Missing permissions to reset all of this counter's values`);
|
||||
void pluginData.state.common.sendErrorMessage(
|
||||
message,
|
||||
`Missing permissions to reset all of this counter's values`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const counterName = counter.name || args.counterName;
|
||||
const confirmed = await confirm(message.channel, message.author.id, {
|
||||
const confirmed = await confirm(message, message.author.id, {
|
||||
content: trimMultilineString(`
|
||||
Do you want to reset **ALL** values for counter **${counterName}**?
|
||||
This will reset the counter for **all** users and channels.
|
||||
|
@ -36,7 +38,7 @@ export const ResetAllCounterValuesCmd = guildPluginMessageCommand<CountersPlugin
|
|||
`),
|
||||
});
|
||||
if (!confirmed) {
|
||||
sendErrorMessage(pluginData, message.channel, "Cancelled");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -47,7 +49,10 @@ export const ResetAllCounterValuesCmd = guildPluginMessageCommand<CountersPlugin
|
|||
await resetAllCounterValues(pluginData, args.counterName);
|
||||
|
||||
loadingMessage?.delete().catch(noop);
|
||||
sendSuccessMessage(pluginData, message.channel, `All counter values for **${counterName}** have been reset`);
|
||||
void pluginData.state.common.sendSuccessMessage(
|
||||
message,
|
||||
`All counter values for **${counterName}** have been reset`,
|
||||
);
|
||||
|
||||
pluginData.getKnubInstance().reloadGuild(pluginData.guild.id);
|
||||
},
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Snowflake, TextChannel } from "discord.js";
|
|||
import { guildPluginMessageCommand } from "knub";
|
||||
import { waitForReply } from "knub/helpers";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { UnknownUser, resolveUser } from "../../../utils.js";
|
||||
import { setCounterValue } from "../functions/setCounterValue.js";
|
||||
import { CountersPluginType } from "../types.js";
|
||||
|
@ -40,22 +39,22 @@ export const ResetCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
const counter = config.counters[args.counterName];
|
||||
const counterId = pluginData.state.counterIds[args.counterName];
|
||||
if (!counter || !counterId) {
|
||||
sendErrorMessage(pluginData, message.channel, `Unknown counter: ${args.counterName}`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `Unknown counter: ${args.counterName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (counter.can_edit === false) {
|
||||
sendErrorMessage(pluginData, message.channel, `Missing permissions to reset this counter's value`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `Missing permissions to reset this counter's value`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.channel && !counter.per_channel) {
|
||||
sendErrorMessage(pluginData, message.channel, `This counter is not per-channel`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `This counter is not per-channel`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.user && !counter.per_user) {
|
||||
sendErrorMessage(pluginData, message.channel, `This counter is not per-user`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `This counter is not per-user`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -64,13 +63,13 @@ export const ResetCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
message.channel.send(`Which channel's counter value would you like to reset?`);
|
||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||
if (!reply || !reply.content) {
|
||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake);
|
||||
if (!potentialChannel || !(potentialChannel instanceof TextChannel)) {
|
||||
sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Channel is not a text channel, cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -82,13 +81,13 @@ export const ResetCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
message.channel.send(`Which user's counter value would you like to reset?`);
|
||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||
if (!reply || !reply.content) {
|
||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialUser = await resolveUser(pluginData.client, reply.content);
|
||||
if (!potentialUser || potentialUser instanceof UnknownUser) {
|
||||
sendErrorMessage(pluginData, message.channel, "Unknown user, cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Unknown user, cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Snowflake, TextChannel } from "discord.js";
|
|||
import { guildPluginMessageCommand } from "knub";
|
||||
import { waitForReply } from "knub/helpers";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { UnknownUser, resolveUser } from "../../../utils.js";
|
||||
import { setCounterValue } from "../functions/setCounterValue.js";
|
||||
import { CountersPluginType } from "../types.js";
|
||||
|
@ -45,22 +44,22 @@ export const SetCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
const counter = config.counters[args.counterName];
|
||||
const counterId = pluginData.state.counterIds[args.counterName];
|
||||
if (!counter || !counterId) {
|
||||
sendErrorMessage(pluginData, message.channel, `Unknown counter: ${args.counterName}`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `Unknown counter: ${args.counterName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (counter.can_edit === false) {
|
||||
sendErrorMessage(pluginData, message.channel, `Missing permissions to edit this counter's value`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `Missing permissions to edit this counter's value`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.channel && !counter.per_channel) {
|
||||
sendErrorMessage(pluginData, message.channel, `This counter is not per-channel`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `This counter is not per-channel`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.user && !counter.per_user) {
|
||||
sendErrorMessage(pluginData, message.channel, `This counter is not per-user`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `This counter is not per-user`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -69,13 +68,13 @@ export const SetCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
message.channel.send(`Which channel's counter value would you like to change?`);
|
||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||
if (!reply || !reply.content) {
|
||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake);
|
||||
if (!potentialChannel || !(potentialChannel instanceof TextChannel)) {
|
||||
sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Channel is not a text channel, cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -87,13 +86,13 @@ export const SetCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
message.channel.send(`Which user's counter value would you like to change?`);
|
||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||
if (!reply || !reply.content) {
|
||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialUser = await resolveUser(pluginData.client, reply.content);
|
||||
if (!potentialUser || potentialUser instanceof UnknownUser) {
|
||||
sendErrorMessage(pluginData, message.channel, "Unknown user, cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Unknown user, cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -105,13 +104,13 @@ export const SetCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
message.channel.send("What would you like to set the counter's value to?");
|
||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||
if (!reply || !reply.content) {
|
||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialValue = parseInt(reply.content, 10);
|
||||
if (Number.isNaN(potentialValue)) {
|
||||
sendErrorMessage(pluginData, message.channel, "Not a number, cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Not a number, cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -119,7 +118,7 @@ export const SetCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
}
|
||||
|
||||
if (value < 0) {
|
||||
sendErrorMessage(pluginData, message.channel, "Cannot set counter value below 0");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Cannot set counter value below 0");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Snowflake } from "discord.js";
|
|||
import { guildPluginMessageCommand } from "knub";
|
||||
import { waitForReply } from "knub/helpers";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { resolveUser, UnknownUser } from "../../../utils.js";
|
||||
import { CountersPluginType } from "../types.js";
|
||||
|
||||
|
@ -39,22 +38,22 @@ export const ViewCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
const counter = config.counters[args.counterName];
|
||||
const counterId = pluginData.state.counterIds[args.counterName];
|
||||
if (!counter || !counterId) {
|
||||
sendErrorMessage(pluginData, message.channel, `Unknown counter: ${args.counterName}`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `Unknown counter: ${args.counterName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (counter.can_view === false) {
|
||||
sendErrorMessage(pluginData, message.channel, `Missing permissions to view this counter's value`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `Missing permissions to view this counter's value`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.channel && !counter.per_channel) {
|
||||
sendErrorMessage(pluginData, message.channel, `This counter is not per-channel`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `This counter is not per-channel`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.user && !counter.per_user) {
|
||||
sendErrorMessage(pluginData, message.channel, `This counter is not per-user`);
|
||||
void pluginData.state.common.sendErrorMessage(message, `This counter is not per-user`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -63,13 +62,13 @@ export const ViewCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
message.channel.send(`Which channel's counter value would you like to view?`);
|
||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||
if (!reply || !reply.content) {
|
||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake);
|
||||
if (!potentialChannel?.isTextBased()) {
|
||||
sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Channel is not a text channel, cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -81,13 +80,13 @@ export const ViewCounterCmd = guildPluginMessageCommand<CountersPluginType>()({
|
|||
message.channel.send(`Which user's counter value would you like to view?`);
|
||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||
if (!reply || !reply.content) {
|
||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialUser = await resolveUser(pluginData.client, reply.content);
|
||||
if (!potentialUser || potentialUser instanceof UnknownUser) {
|
||||
sendErrorMessage(pluginData, message.channel, "Unknown user, cancelling");
|
||||
void pluginData.state.common.sendErrorMessage(message, "Unknown user, cancelling");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { EventEmitter } from "events";
|
||||
import { BasePluginType } from "knub";
|
||||
import { BasePluginType, pluginUtils } from "knub";
|
||||
import z from "zod";
|
||||
import { GuildCounters, MAX_COUNTER_VALUE, MIN_COUNTER_VALUE } from "../../data/GuildCounters.js";
|
||||
import {
|
||||
|
@ -9,6 +9,7 @@ import {
|
|||
parseCounterConditionString,
|
||||
} from "../../data/entities/CounterTrigger.js";
|
||||
import { zBoundedCharacters, zBoundedRecord, zDelayString } from "../../utils.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
import Timeout = NodeJS.Timeout;
|
||||
|
||||
const MAX_COUNTERS = 5;
|
||||
|
@ -132,5 +133,6 @@ export interface CountersPluginType extends BasePluginType {
|
|||
decayTimers: Timeout[];
|
||||
events: CounterEventEmitter;
|
||||
counterTriggersByCounterId: Map<number, CounterTrigger[]>;
|
||||
common: pluginUtils.PluginPublicInterface<typeof CommonPlugin>;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
messageToTemplateSafeMessage,
|
||||
userToTemplateSafeUser,
|
||||
} from "../../utils/templateSafeObjects.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin.js";
|
||||
import { runEvent } from "./functions/runEvent.js";
|
||||
import { CustomEventsPluginType, zCustomEventsConfig } from "./types.js";
|
||||
|
@ -28,6 +29,10 @@ export const CustomEventsPlugin = guildPlugin<CustomEventsPluginType>()({
|
|||
configParser: (input) => zCustomEventsConfig.parse(input),
|
||||
defaultOptions,
|
||||
|
||||
beforeStart(pluginData) {
|
||||
pluginData.state.common = pluginData.getPlugin(CommonPlugin);
|
||||
},
|
||||
|
||||
afterLoad(pluginData) {
|
||||
const config = pluginData.config.get();
|
||||
for (const [key, event] of Object.entries(config.events)) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Message } from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { TemplateSafeValueContainer } from "../../../templateFormatter.js";
|
||||
import { ActionError } from "../ActionError.js";
|
||||
import { addRoleAction } from "../actions/addRoleAction.js";
|
||||
|
@ -39,7 +38,7 @@ export async function runEvent(
|
|||
} catch (e) {
|
||||
if (e instanceof ActionError) {
|
||||
if (event.trigger.type === "command") {
|
||||
sendErrorMessage(pluginData, (eventData.msg as Message).channel, e.message);
|
||||
void pluginData.state.common.sendErrorMessage((eventData.msg as Message).channel, e.message);
|
||||
} else {
|
||||
// TODO: Where to log action errors from other kinds of triggers?
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { BasePluginType } from "knub";
|
||||
import { BasePluginType, pluginUtils } from "knub";
|
||||
import z from "zod";
|
||||
import { zBoundedCharacters, zBoundedRecord } from "../../utils.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
import { zAddRoleAction } from "./actions/addRoleAction.js";
|
||||
import { zCreateCaseAction } from "./actions/createCaseAction.js";
|
||||
import { zMakeRoleMentionableAction } from "./actions/makeRoleMentionableAction.js";
|
||||
|
@ -43,5 +44,6 @@ export interface CustomEventsPluginType extends BasePluginType {
|
|||
config: z.infer<typeof zCustomEventsConfig>;
|
||||
state: {
|
||||
clearTriggers: () => void;
|
||||
common: pluginUtils.PluginPublicInterface<typeof CommonPlugin>;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { PluginOptions, guildPlugin } from "knub";
|
||||
import { onGuildEvent } from "../../data/GuildEvents.js";
|
||||
import { GuildVCAlerts } from "../../data/GuildVCAlerts.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
import { FollowCmd } from "./commands/FollowCmd.js";
|
||||
import { DeleteFollowCmd, ListFollowCmd } from "./commands/ListFollowCmd.js";
|
||||
import { WhereCmd } from "./commands/WhereCmd.js";
|
||||
|
@ -53,6 +54,10 @@ export const LocateUserPlugin = guildPlugin<LocateUserPluginType>()({
|
|||
state.usersWithAlerts = [];
|
||||
},
|
||||
|
||||
beforeStart(pluginData) {
|
||||
pluginData.state.common = pluginData.getPlugin(CommonPlugin);
|
||||
},
|
||||
|
||||
afterLoad(pluginData) {
|
||||
const { state, guild } = pluginData;
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import humanizeDuration from "humanize-duration";
|
|||
import moment from "moment-timezone";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { registerExpiringVCAlert } from "../../../data/loops/expiringVCAlertsLoop.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { MINUTES, SECONDS } from "../../../utils.js";
|
||||
import { locateUserCmd } from "../types.js";
|
||||
|
||||
|
@ -27,7 +26,7 @@ export const FollowCmd = locateUserCmd({
|
|||
const active = args.active || false;
|
||||
|
||||
if (time < 30 * SECONDS) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Sorry, but the minimum duration for an alert is 30 seconds!");
|
||||
void pluginData.state.common.sendErrorMessage(msg, "Sorry, but the minimum duration for an alert is 30 seconds!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -46,17 +45,15 @@ export const FollowCmd = locateUserCmd({
|
|||
}
|
||||
|
||||
if (active) {
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
void pluginData.state.common.sendSuccessMessage(
|
||||
msg,
|
||||
`Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration(
|
||||
time,
|
||||
)} i will notify and move you.\nPlease make sure to be in a voice channel, otherwise i cannot move you!`,
|
||||
);
|
||||
} else {
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
void pluginData.state.common.sendSuccessMessage(
|
||||
msg,
|
||||
`Every time <@${args.member.id}> joins or switches VC in the next ${humanizeDuration(time)} i will notify you`,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { clearExpiringVCAlert } from "../../../data/loops/expiringVCAlertsLoop.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { createChunkedMessage, sorter } from "../../../utils.js";
|
||||
import { locateUserCmd } from "../types.js";
|
||||
|
||||
|
@ -13,7 +12,7 @@ export const ListFollowCmd = locateUserCmd({
|
|||
async run({ message: msg, pluginData }) {
|
||||
const alerts = await pluginData.state.alerts.getAlertsByRequestorId(msg.member.id);
|
||||
if (alerts.length === 0) {
|
||||
sendErrorMessage(pluginData, msg.channel, "You have no active alerts!");
|
||||
void pluginData.state.common.sendErrorMessage(msg, "You have no active alerts!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -46,7 +45,7 @@ export const DeleteFollowCmd = locateUserCmd({
|
|||
alerts.sort(sorter("expires_at"));
|
||||
|
||||
if (args.num > alerts.length || args.num <= 0) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Unknown alert!");
|
||||
void pluginData.state.common.sendErrorMessage(msg, "Unknown alert!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -54,6 +53,6 @@ export const DeleteFollowCmd = locateUserCmd({
|
|||
clearExpiringVCAlert(toDelete);
|
||||
await pluginData.state.alerts.delete(toDelete.id);
|
||||
|
||||
sendSuccessMessage(pluginData, msg.channel, "Alert deleted");
|
||||
void pluginData.state.common.sendSuccessMessage(msg, "Alert deleted");
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub";
|
||||
import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub";
|
||||
import z from "zod";
|
||||
import { GuildVCAlerts } from "../../data/GuildVCAlerts.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
|
||||
export const zLocateUserConfig = z.strictObject({
|
||||
can_where: z.boolean(),
|
||||
|
@ -13,6 +14,7 @@ export interface LocateUserPluginType extends BasePluginType {
|
|||
alerts: GuildVCAlerts;
|
||||
usersWithAlerts: string[];
|
||||
unregisterGuildEventListener: () => void;
|
||||
common: pluginUtils.PluginPublicInterface<typeof CommonPlugin>;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { GuildMember, GuildTextBasedChannel, Snowflake } from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { LocateUserPluginType } from "../types.js";
|
||||
|
||||
export async function moveMember(
|
||||
|
@ -16,10 +15,10 @@ export async function moveMember(
|
|||
channel: target.voice.channelId,
|
||||
});
|
||||
} catch {
|
||||
sendErrorMessage(pluginData, errorChannel, "Failed to move you. Are you in a voice channel?");
|
||||
void pluginData.state.common.sendErrorMessage(errorChannel, "Failed to move you. Are you in a voice channel?");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
sendErrorMessage(pluginData, errorChannel, "Failed to move you. Are you in a voice channel?");
|
||||
void pluginData.state.common.sendErrorMessage(errorChannel, "Failed to move you. Are you in a voice channel?");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { GuildMember, GuildTextBasedChannel, Invite, VoiceChannel } from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { getInviteLink } from "knub/helpers";
|
||||
import { sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { LocateUserPluginType } from "../types.js";
|
||||
import { createOrReuseInvite } from "./createOrReuseInvite.js";
|
||||
|
||||
|
@ -22,7 +21,7 @@ export async function sendWhere(
|
|||
try {
|
||||
invite = await createOrReuseInvite(voice);
|
||||
} catch {
|
||||
sendErrorMessage(pluginData, channel, "Cannot create an invite to that channel!");
|
||||
void pluginData.state.common.sendErrorMessage(channel, "Cannot create an invite to that channel!");
|
||||
return;
|
||||
}
|
||||
channel.send({
|
||||
|
|
|
@ -30,7 +30,7 @@ import {
|
|||
import { LogsThreadCreateEvt, LogsThreadDeleteEvt, LogsThreadUpdateEvt } from "./events/LogsThreadModifyEvts.js";
|
||||
import { LogsGuildMemberUpdateEvt } from "./events/LogsUserUpdateEvts.js";
|
||||
import { LogsVoiceStateUpdateEvt } from "./events/LogsVoiceChannelEvts.js";
|
||||
import { LogsPluginType, zLogsConfig } from "./types.js";
|
||||
import { FORMAT_NO_TIMESTAMP, LogsPluginType, zLogsConfig } from "./types.js";
|
||||
import { getLogMessage } from "./util/getLogMessage.js";
|
||||
import { log } from "./util/log.js";
|
||||
import { onMessageDelete } from "./util/onMessageDelete.js";
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
userToTemplateSafeUser,
|
||||
} from "../../../utils/templateSafeObjects.js";
|
||||
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin.js";
|
||||
import { LogsPluginType } from "../types.js";
|
||||
import { FORMAT_NO_TIMESTAMP, LogsPluginType } from "../types.js";
|
||||
import { log } from "../util/log.js";
|
||||
|
||||
export interface LogMessageDeleteData {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { GuildCases } from "../../data/GuildCases.js";
|
|||
import { GuildLogs } from "../../data/GuildLogs.js";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages.js";
|
||||
import { LogType } from "../../data/LogType.js";
|
||||
import { zBoundedCharacters, zMessageContent, zRegex, zSnowflake } from "../../utils.js";
|
||||
import { keys, zBoundedCharacters, zMessageContent, zRegex, zSnowflake } from "../../utils.js";
|
||||
import { MessageBuffer } from "../../utils/MessageBuffer.js";
|
||||
import {
|
||||
TemplateSafeCase,
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
TemplateSafeUser,
|
||||
} from "../../../utils/templateSafeObjects.js";
|
||||
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin.js";
|
||||
import { ILogTypeData, LogsPluginType, TLogChannel } from "../types.js";
|
||||
import { FORMAT_NO_TIMESTAMP, ILogTypeData, LogsPluginType, TLogChannel } from "../types.js";
|
||||
|
||||
export async function getLogMessage<TLogType extends keyof ILogTypeData>(
|
||||
pluginData: GuildPluginData<LogsPluginType>,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { EmbedData, GuildTextBasedChannel, Snowflake } from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import cloneDeep from "lodash/cloneDeep.js";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import { SavedMessage } from "../../../data/entities/SavedMessage.js";
|
||||
import { resolveUser } from "../../../utils.js";
|
||||
import { logMessageEdit } from "../logFunctions/logMessageEdit.js";
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import { PluginOptions, guildPlugin } from "knub";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
import { SaveMessagesToDBCmd } from "./commands/SaveMessagesToDB.js";
|
||||
import { SavePinsToDBCmd } from "./commands/SavePinsToDB.js";
|
||||
import {
|
||||
MessageCreateEvt,
|
||||
MessageDeleteBulkEvt,
|
||||
MessageDeleteEvt,
|
||||
MessageUpdateEvt,
|
||||
} from "./events/SaveMessagesEvts.js";
|
||||
import { MessageCreateEvt, MessageDeleteBulkEvt, MessageDeleteEvt, MessageUpdateEvt } from "./events/SaveMessagesEvts.js";
|
||||
import { MessageSaverPluginType, zMessageSaverConfig } from "./types.js";
|
||||
|
||||
const defaultOptions: PluginOptions<MessageSaverPluginType> = {
|
||||
|
@ -48,4 +44,8 @@ export const MessageSaverPlugin = guildPlugin<MessageSaverPluginType>()({
|
|||
const { state, guild } = pluginData;
|
||||
state.savedMessages = GuildSavedMessages.getGuildInstance(guild.id);
|
||||
},
|
||||
|
||||
beforeStart(pluginData) {
|
||||
pluginData.state.common = pluginData.getPlugin(CommonPlugin);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { saveMessagesToDB } from "../saveMessagesToDB.js";
|
||||
import { messageSaverCmd } from "../types.js";
|
||||
|
||||
|
@ -18,13 +17,12 @@ export const SaveMessagesToDBCmd = messageSaverCmd({
|
|||
const { savedCount, failed } = await saveMessagesToDB(pluginData, args.channel, args.ids.trim().split(" "));
|
||||
|
||||
if (failed.length) {
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
void pluginData.state.common.sendSuccessMessage(
|
||||
msg,
|
||||
`Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`,
|
||||
);
|
||||
} else {
|
||||
sendSuccessMessage(pluginData, msg.channel, `Saved ${savedCount} messages!`);
|
||||
void pluginData.state.common.sendSuccessMessage(msg, `Saved ${savedCount} messages!`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { saveMessagesToDB } from "../saveMessagesToDB.js";
|
||||
import { messageSaverCmd } from "../types.js";
|
||||
|
||||
|
@ -19,13 +18,12 @@ export const SavePinsToDBCmd = messageSaverCmd({
|
|||
const { savedCount, failed } = await saveMessagesToDB(pluginData, args.channel, [...pins.keys()]);
|
||||
|
||||
if (failed.length) {
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
void pluginData.state.common.sendSuccessMessage(
|
||||
msg,
|
||||
`Saved ${savedCount} messages. The following messages could not be saved: ${failed.join(", ")}`,
|
||||
);
|
||||
} else {
|
||||
sendSuccessMessage(pluginData, msg.channel, `Saved ${savedCount} messages!`);
|
||||
void pluginData.state.common.sendSuccessMessage(msg, `Saved ${savedCount} messages!`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub";
|
||||
import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, pluginUtils } from "knub";
|
||||
import z from "zod";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
|
||||
export const zMessageSaverConfig = z.strictObject({
|
||||
can_manage: z.boolean(),
|
||||
|
@ -10,6 +11,7 @@ export interface MessageSaverPluginType extends BasePluginType {
|
|||
config: z.infer<typeof zMessageSaverConfig>;
|
||||
state: {
|
||||
savedMessages: GuildSavedMessages;
|
||||
common: pluginUtils.PluginPublicInterface<typeof CommonPlugin>;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -10,44 +10,69 @@ import { GuildTempbans } from "../../data/GuildTempbans.js";
|
|||
import { makePublicFn, mapToPublicFn } from "../../pluginUtils.js";
|
||||
import { MINUTES } from "../../utils.js";
|
||||
import { CasesPlugin } from "../Cases/CasesPlugin.js";
|
||||
import { CommonPlugin } from "../Common/CommonPlugin.js";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin.js";
|
||||
import { MutesPlugin } from "../Mutes/MutesPlugin.js";
|
||||
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin.js";
|
||||
import { AddCaseCmd } from "./commands/AddCaseCmd.js";
|
||||
import { BanCmd } from "./commands/BanCmd.js";
|
||||
import { CaseCmd } from "./commands/CaseCmd.js";
|
||||
import { CasesModCmd } from "./commands/CasesModCmd.js";
|
||||
import { CasesUserCmd } from "./commands/CasesUserCmd.js";
|
||||
import { DeleteCaseCmd } from "./commands/DeleteCaseCmd.js";
|
||||
import { ForcebanCmd } from "./commands/ForcebanCmd.js";
|
||||
import { ForcemuteCmd } from "./commands/ForcemuteCmd.js";
|
||||
import { ForceUnmuteCmd } from "./commands/ForceunmuteCmd.js";
|
||||
import { HideCaseCmd } from "./commands/HideCaseCmd.js";
|
||||
import { KickCmd } from "./commands/KickCmd.js";
|
||||
import { MassbanCmd } from "./commands/MassBanCmd.js";
|
||||
import { MassunbanCmd } from "./commands/MassUnbanCmd.js";
|
||||
import { MassmuteCmd } from "./commands/MassmuteCmd.js";
|
||||
import { MuteCmd } from "./commands/MuteCmd.js";
|
||||
import { NoteCmd } from "./commands/NoteCmd.js";
|
||||
import { SoftbanCmd } from "./commands/SoftbanCommand.js";
|
||||
import { UnbanCmd } from "./commands/UnbanCmd.js";
|
||||
import { UnhideCaseCmd } from "./commands/UnhideCaseCmd.js";
|
||||
import { UnmuteCmd } from "./commands/UnmuteCmd.js";
|
||||
import { UpdateCmd } from "./commands/UpdateCmd.js";
|
||||
import { WarnCmd } from "./commands/WarnCmd.js";
|
||||
import { AddCaseMsgCmd } from "./commands/addcase/AddCaseMsgCmd.js";
|
||||
import { AddCaseSlashCmd } from "./commands/addcase/AddCaseSlashCmd.js";
|
||||
import { BanMsgCmd } from "./commands/ban/BanMsgCmd.js";
|
||||
import { BanSlashCmd } from "./commands/ban/BanSlashCmd.js";
|
||||
import { CaseMsgCmd } from "./commands/case/CaseMsgCmd.js";
|
||||
import { CaseSlashCmd } from "./commands/case/CaseSlashCmd.js";
|
||||
import { CasesModMsgCmd } from "./commands/cases/CasesModMsgCmd.js";
|
||||
import { CasesSlashCmd } from "./commands/cases/CasesSlashCmd.js";
|
||||
import { CasesUserMsgCmd } from "./commands/cases/CasesUserMsgCmd.js";
|
||||
import { DeleteCaseMsgCmd } from "./commands/deletecase/DeleteCaseMsgCmd.js";
|
||||
import { DeleteCaseSlashCmd } from "./commands/deletecase/DeleteCaseSlashCmd.js";
|
||||
import { ForceBanMsgCmd } from "./commands/forceban/ForceBanMsgCmd.js";
|
||||
import { ForceBanSlashCmd } from "./commands/forceban/ForceBanSlashCmd.js";
|
||||
import { ForceMuteMsgCmd } from "./commands/forcemute/ForceMuteMsgCmd.js";
|
||||
import { ForceMuteSlashCmd } from "./commands/forcemute/ForceMuteSlashCmd.js";
|
||||
import { ForceUnmuteMsgCmd } from "./commands/forceunmute/ForceUnmuteMsgCmd.js";
|
||||
import { ForceUnmuteSlashCmd } from "./commands/forceunmute/ForceUnmuteSlashCmd.js";
|
||||
import { HideCaseMsgCmd } from "./commands/hidecase/HideCaseMsgCmd.js";
|
||||
import { HideCaseSlashCmd } from "./commands/hidecase/HideCaseSlashCmd.js";
|
||||
import { KickMsgCmd } from "./commands/kick/KickMsgCmd.js";
|
||||
import { KickSlashCmd } from "./commands/kick/KickSlashCmd.js";
|
||||
import { MassBanMsgCmd } from "./commands/massban/MassBanMsgCmd.js";
|
||||
import { MassBanSlashCmd } from "./commands/massban/MassBanSlashCmd.js";
|
||||
import { MassMuteMsgCmd } from "./commands/massmute/MassMuteMsgCmd.js";
|
||||
import { MassMuteSlashSlashCmd } from "./commands/massmute/MassMuteSlashCmd.js";
|
||||
import { MassUnbanMsgCmd } from "./commands/massunban/MassUnbanMsgCmd.js";
|
||||
import { MassUnbanSlashCmd } from "./commands/massunban/MassUnbanSlashCmd.js";
|
||||
import { MuteMsgCmd } from "./commands/mute/MuteMsgCmd.js";
|
||||
import { MuteSlashCmd } from "./commands/mute/MuteSlashCmd.js";
|
||||
import { NoteMsgCmd } from "./commands/note/NoteMsgCmd.js";
|
||||
import { NoteSlashCmd } from "./commands/note/NoteSlashCmd.js";
|
||||
import { UnbanMsgCmd } from "./commands/unban/UnbanMsgCmd.js";
|
||||
import { UnbanSlashCmd } from "./commands/unban/UnbanSlashCmd.js";
|
||||
import { UnhideCaseMsgCmd } from "./commands/unhidecase/UnhideCaseMsgCmd.js";
|
||||
import { UnhideCaseSlashCmd } from "./commands/unhidecase/UnhideCaseSlashCmd.js";
|
||||
import { UnmuteMsgCmd } from "./commands/unmute/UnmuteMsgCmd.js";
|
||||
import { UnmuteSlashCmd } from "./commands/unmute/UnmuteSlashCmd.js";
|
||||
import { UpdateMsgCmd } from "./commands/update/UpdateMsgCmd.js";
|
||||
import { UpdateSlashCmd } from "./commands/update/UpdateSlashCmd.js";
|
||||
import { WarnMsgCmd } from "./commands/warn/WarnMsgCmd.js";
|
||||
import { WarnSlashCmd } from "./commands/warn/WarnSlashCmd.js";
|
||||
import { AuditLogEvents } from "./events/AuditLogEvents.js";
|
||||
import { CreateBanCaseOnManualBanEvt } from "./events/CreateBanCaseOnManualBanEvt.js";
|
||||
import { CreateUnbanCaseOnManualUnbanEvt } from "./events/CreateUnbanCaseOnManualUnbanEvt.js";
|
||||
import { PostAlertOnMemberJoinEvt } from "./events/PostAlertOnMemberJoinEvt.js";
|
||||
import { banUserId } from "./functions/banUserId.js";
|
||||
import { clearTempban } from "./functions/clearTempban.js";
|
||||
import { hasMutePermission } from "./functions/hasMutePerm.js";
|
||||
import {
|
||||
hasBanPermission,
|
||||
hasMutePermission,
|
||||
hasNotePermission,
|
||||
hasWarnPermission,
|
||||
} from "./functions/hasModActionPerm.js";
|
||||
import { kickMember } from "./functions/kickMember.js";
|
||||
import { offModActionsEvent } from "./functions/offModActionsEvent.js";
|
||||
import { onModActionsEvent } from "./functions/onModActionsEvent.js";
|
||||
import { updateCase } from "./functions/updateCase.js";
|
||||
import { warnMember } from "./functions/warnMember.js";
|
||||
import { ModActionsPluginType, zModActionsConfig } from "./types.js";
|
||||
import { AttachmentLinkReactionType, ModActionsPluginType, modActionsSlashGroup, zModActionsConfig } from "./types.js";
|
||||
|
||||
const defaultOptions = {
|
||||
config: {
|
||||
|
@ -69,6 +94,7 @@ const defaultOptions = {
|
|||
warn_notify_message:
|
||||
"The user already has **{priorWarnings}** warnings!\n Please check their prior cases and assess whether or not to warn anyways.\n Proceed with the warning?",
|
||||
ban_delete_message_days: 1,
|
||||
attachment_link_reaction: "warn" as AttachmentLinkReactionType,
|
||||
|
||||
can_note: false,
|
||||
can_warn: false,
|
||||
|
@ -122,29 +148,58 @@ export const ModActionsPlugin = guildPlugin<ModActionsPluginType>()({
|
|||
|
||||
events: [CreateBanCaseOnManualBanEvt, CreateUnbanCaseOnManualUnbanEvt, PostAlertOnMemberJoinEvt, AuditLogEvents],
|
||||
|
||||
slashCommands: [
|
||||
modActionsSlashGroup({
|
||||
name: "mod",
|
||||
description: "Moderation actions",
|
||||
defaultMemberPermissions: "0",
|
||||
subcommands: [
|
||||
AddCaseSlashCmd,
|
||||
BanSlashCmd,
|
||||
CaseSlashCmd,
|
||||
CasesSlashCmd,
|
||||
DeleteCaseSlashCmd,
|
||||
ForceBanSlashCmd,
|
||||
ForceMuteSlashCmd,
|
||||
ForceUnmuteSlashCmd,
|
||||
HideCaseSlashCmd,
|
||||
KickSlashCmd,
|
||||
MassBanSlashCmd,
|
||||
MassMuteSlashSlashCmd,
|
||||
MassUnbanSlashCmd,
|
||||
MuteSlashCmd,
|
||||
NoteSlashCmd,
|
||||
UnbanSlashCmd,
|
||||
UnhideCaseSlashCmd,
|
||||
UnmuteSlashCmd,
|
||||
UpdateSlashCmd,
|
||||
WarnSlashCmd,
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
||||
messageCommands: [
|
||||
UpdateCmd,
|
||||
NoteCmd,
|
||||
WarnCmd,
|
||||
MuteCmd,
|
||||
ForcemuteCmd,
|
||||
UnmuteCmd,
|
||||
ForceUnmuteCmd,
|
||||
KickCmd,
|
||||
SoftbanCmd,
|
||||
BanCmd,
|
||||
UnbanCmd,
|
||||
ForcebanCmd,
|
||||
MassbanCmd,
|
||||
MassmuteCmd,
|
||||
MassunbanCmd,
|
||||
AddCaseCmd,
|
||||
CaseCmd,
|
||||
CasesUserCmd,
|
||||
CasesModCmd,
|
||||
HideCaseCmd,
|
||||
UnhideCaseCmd,
|
||||
DeleteCaseCmd,
|
||||
UpdateMsgCmd,
|
||||
NoteMsgCmd,
|
||||
WarnMsgCmd,
|
||||
MuteMsgCmd,
|
||||
ForceMuteMsgCmd,
|
||||
UnmuteMsgCmd,
|
||||
ForceUnmuteMsgCmd,
|
||||
KickMsgCmd,
|
||||
BanMsgCmd,
|
||||
UnbanMsgCmd,
|
||||
ForceBanMsgCmd,
|
||||
MassBanMsgCmd,
|
||||
MassMuteMsgCmd,
|
||||
MassUnbanMsgCmd,
|
||||
AddCaseMsgCmd,
|
||||
CaseMsgCmd,
|
||||
CasesUserMsgCmd,
|
||||
CasesModMsgCmd,
|
||||
HideCaseMsgCmd,
|
||||
UnhideCaseMsgCmd,
|
||||
DeleteCaseMsgCmd,
|
||||
],
|
||||
|
||||
public(pluginData) {
|
||||
|
@ -153,8 +208,11 @@ export const ModActionsPlugin = guildPlugin<ModActionsPluginType>()({
|
|||
kickMember: makePublicFn(pluginData, kickMember),
|
||||
banUserId: makePublicFn(pluginData, banUserId),
|
||||
updateCase: (msg: Message, caseNumber: number | null, note: string) =>
|
||||
updateCase(pluginData, msg, { caseNumber, note }),
|
||||
updateCase(pluginData, msg, msg.author, caseNumber ?? undefined, note, [...msg.attachments.values()]),
|
||||
hasNotePermission: makePublicFn(pluginData, hasNotePermission),
|
||||
hasWarnPermission: makePublicFn(pluginData, hasWarnPermission),
|
||||
hasMutePermission: makePublicFn(pluginData, hasMutePermission),
|
||||
hasBanPermission: makePublicFn(pluginData, hasBanPermission),
|
||||
on: mapToPublicFn(onModActionsEvent),
|
||||
off: mapToPublicFn(offModActionsEvent),
|
||||
getEventEmitter: () => pluginData.state.events,
|
||||
|
@ -178,6 +236,10 @@ export const ModActionsPlugin = guildPlugin<ModActionsPluginType>()({
|
|||
state.events = new EventEmitter();
|
||||
},
|
||||
|
||||
beforeStart(pluginData) {
|
||||
pluginData.state.common = pluginData.getPlugin(CommonPlugin);
|
||||
},
|
||||
|
||||
afterLoad(pluginData) {
|
||||
const { state, guild } = pluginData;
|
||||
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { CaseTypes } from "../../../data/CaseTypes.js";
|
||||
import { Case } from "../../../data/entities/Case.js";
|
||||
import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin.js";
|
||||
import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { renderUsername, resolveMember, resolveUser } from "../../../utils.js";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin.js";
|
||||
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments.js";
|
||||
import { modActionsCmd } from "../types.js";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
};
|
||||
|
||||
export const AddCaseCmd = modActionsCmd({
|
||||
trigger: "addcase",
|
||||
permission: "can_addcase",
|
||||
description: "Add an arbitrary case to the specified user without taking any action",
|
||||
|
||||
signature: [
|
||||
{
|
||||
type: ct.string(),
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user.id) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user exists as a guild member, make sure we can act on them first
|
||||
const member = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
if (member && !canActOn(pluginData, msg.member, member)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot add case on this user: insufficient permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
// The moderator who did the action is the message author or, if used, the specified -mod
|
||||
let mod = msg.member;
|
||||
if (args.mod) {
|
||||
if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) {
|
||||
sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod");
|
||||
return;
|
||||
}
|
||||
|
||||
mod = args.mod;
|
||||
}
|
||||
|
||||
// Verify the case type is valid
|
||||
const type: string = args.type[0].toUpperCase() + args.type.slice(1).toLowerCase();
|
||||
if (!CaseTypes[type]) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot add case: invalid case type");
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]);
|
||||
|
||||
// Create the case
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const theCase: Case = await casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes[type],
|
||||
reason,
|
||||
ppId: mod.id !== msg.author.id ? msg.author.id : undefined,
|
||||
});
|
||||
|
||||
if (user) {
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`Case #${theCase.case_number} created for **${renderUsername(user)}**`,
|
||||
);
|
||||
} else {
|
||||
sendSuccessMessage(pluginData, msg.channel, `Case #${theCase.case_number} created`);
|
||||
}
|
||||
|
||||
// Log the action
|
||||
pluginData.getPlugin(LogsPlugin).logCaseCreate({
|
||||
mod: mod.user,
|
||||
userId: user.id,
|
||||
caseNum: theCase.case_number,
|
||||
caseType: type.toUpperCase(),
|
||||
reason,
|
||||
});
|
||||
},
|
||||
});
|
|
@ -1,219 +0,0 @@
|
|||
import humanizeDuration from "humanize-duration";
|
||||
import { getMemberLevel } from "knub/helpers";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { CaseTypes } from "../../../data/CaseTypes.js";
|
||||
import { clearExpiringTempban, registerExpiringTempban } from "../../../data/loops/expiringTempbansLoop.js";
|
||||
import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin.js";
|
||||
import { renderUsername, resolveMember, resolveUser } from "../../../utils.js";
|
||||
import { banLock } from "../../../utils/lockNameHelpers.js";
|
||||
import { waitForButtonConfirm } from "../../../utils/waitForInteraction.js";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin.js";
|
||||
import { banUserId } from "../functions/banUserId.js";
|
||||
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments.js";
|
||||
import { isBanned } from "../functions/isBanned.js";
|
||||
import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs.js";
|
||||
import { modActionsCmd } from "../types.js";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
notify: ct.string({ option: true }),
|
||||
"notify-channel": ct.textChannel({ option: true }),
|
||||
"delete-days": ct.number({ option: true, shortcut: "d" }),
|
||||
};
|
||||
|
||||
export const BanCmd = modActionsCmd({
|
||||
trigger: "ban",
|
||||
permission: "can_ban",
|
||||
description: "Ban or Tempban the specified member",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
time: ct.delay(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user.id) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
return;
|
||||
}
|
||||
const time = args["time"] ? args["time"] : null;
|
||||
|
||||
const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]);
|
||||
const memberToBan = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
// The moderator who did the action is the message author or, if used, the specified -mod
|
||||
let mod = msg.member;
|
||||
if (args.mod) {
|
||||
if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id }))) {
|
||||
sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod");
|
||||
return;
|
||||
}
|
||||
|
||||
mod = args.mod;
|
||||
}
|
||||
|
||||
// acquire a lock because of the needed user-inputs below (if banned/not on server)
|
||||
const lock = await pluginData.locks.acquire(banLock(user));
|
||||
let forceban = false;
|
||||
const existingTempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id);
|
||||
if (!memberToBan) {
|
||||
const banned = await isBanned(pluginData, user.id);
|
||||
if (banned) {
|
||||
// Abort if trying to ban user indefinitely if they are already banned indefinitely
|
||||
if (!existingTempban && !time) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User is already banned indefinitely.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask the mod if we should update the existing ban
|
||||
const reply = await waitForButtonConfirm(
|
||||
msg.channel,
|
||||
{ content: "Failed to message the user. Log the warning anyway?" },
|
||||
{ confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id },
|
||||
);
|
||||
if (!reply) {
|
||||
sendErrorMessage(pluginData, msg.channel, "User already banned, update cancelled by moderator");
|
||||
lock.unlock();
|
||||
return;
|
||||
} else {
|
||||
// Update or add new tempban / remove old tempban
|
||||
if (time && time > 0) {
|
||||
if (existingTempban) {
|
||||
await pluginData.state.tempbans.updateExpiryTime(user.id, time, mod.id);
|
||||
} else {
|
||||
await pluginData.state.tempbans.addTempban(user.id, time, mod.id);
|
||||
}
|
||||
const tempban = (await pluginData.state.tempbans.findExistingTempbanForUserId(user.id))!;
|
||||
registerExpiringTempban(tempban);
|
||||
} else if (existingTempban) {
|
||||
clearExpiringTempban(existingTempban);
|
||||
pluginData.state.tempbans.clear(user.id);
|
||||
}
|
||||
|
||||
// Create a new case for the updated ban since we never stored the old case id and log the action
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Ban,
|
||||
userId: user.id,
|
||||
reason,
|
||||
noteDetails: [`Ban updated to ${time ? humanizeDuration(time) : "indefinite"}`],
|
||||
});
|
||||
if (time) {
|
||||
pluginData.getPlugin(LogsPlugin).logMemberTimedBan({
|
||||
mod: mod.user,
|
||||
user,
|
||||
caseNumber: createdCase.case_number,
|
||||
reason,
|
||||
banTime: humanizeDuration(time),
|
||||
});
|
||||
} else {
|
||||
pluginData.getPlugin(LogsPlugin).logMemberBan({
|
||||
mod: mod.user,
|
||||
user,
|
||||
caseNumber: createdCase.case_number,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`,
|
||||
);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Ask the mod if we should upgrade to a forceban as the user is not on the server
|
||||
const reply = await waitForButtonConfirm(
|
||||
msg.channel,
|
||||
{ content: "User not on server, forceban instead?" },
|
||||
{ confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id },
|
||||
);
|
||||
if (!reply) {
|
||||
sendErrorMessage(pluginData, msg.channel, "User not on server, ban cancelled by moderator");
|
||||
lock.unlock();
|
||||
return;
|
||||
} else {
|
||||
forceban = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we're allowed to ban this member if they are on the server
|
||||
if (!forceban && !canActOn(pluginData, msg.member, memberToBan!)) {
|
||||
const ourLevel = getMemberLevel(pluginData, msg.member);
|
||||
const targetLevel = getMemberLevel(pluginData, memberToBan!);
|
||||
sendErrorMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`Cannot ban: target permission level is equal or higher to yours, ${targetLevel} >= ${ourLevel}`,
|
||||
);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
let contactMethods;
|
||||
try {
|
||||
contactMethods = readContactMethodsFromArgs(args);
|
||||
} catch (e) {
|
||||
sendErrorMessage(pluginData, msg.channel, e.message);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
const deleteMessageDays =
|
||||
args["delete-days"] ?? (await pluginData.config.getForMessage(msg)).ban_delete_message_days;
|
||||
const banResult = await banUserId(
|
||||
pluginData,
|
||||
user.id,
|
||||
reason,
|
||||
{
|
||||
contactMethods,
|
||||
caseArgs: {
|
||||
modId: mod.id,
|
||||
ppId: mod.id !== msg.author.id ? msg.author.id : undefined,
|
||||
},
|
||||
deleteMessageDays,
|
||||
modId: mod.id,
|
||||
},
|
||||
time,
|
||||
);
|
||||
|
||||
if (banResult.status === "failed") {
|
||||
sendErrorMessage(pluginData, msg.channel, `Failed to ban member: ${banResult.error}`);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
let forTime = "";
|
||||
if (time && time > 0) {
|
||||
forTime = `for ${humanizeDuration(time)} `;
|
||||
}
|
||||
|
||||
// Confirm the action to the moderator
|
||||
let response = "";
|
||||
if (!forceban) {
|
||||
response = `Banned **${renderUsername(user)}** ${forTime}(Case #${banResult.case.case_number})`;
|
||||
if (banResult.notifyResult.text) response += ` (${banResult.notifyResult.text})`;
|
||||
} else {
|
||||
response = `Member forcebanned ${forTime}(Case #${banResult.case.case_number})`;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
sendSuccessMessage(pluginData, msg.channel, response);
|
||||
},
|
||||
});
|
|
@ -1,29 +0,0 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin.js";
|
||||
import { sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { modActionsCmd } from "../types.js";
|
||||
|
||||
export const CaseCmd = modActionsCmd({
|
||||
trigger: "case",
|
||||
permission: "can_view",
|
||||
description: "Show information about a specific case",
|
||||
|
||||
signature: [
|
||||
{
|
||||
caseNumber: ct.number(),
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const theCase = await pluginData.state.cases.findByCaseNumber(args.caseNumber);
|
||||
|
||||
if (!theCase) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Case not found");
|
||||
return;
|
||||
}
|
||||
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const embed = await casesPlugin.getCaseEmbed(theCase.id, msg.author.id);
|
||||
msg.channel.send(embed);
|
||||
},
|
||||
});
|
|
@ -1,83 +0,0 @@
|
|||
import { APIEmbed } from "discord.js";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { UnknownUser, emptyEmbedValue, renderUsername, resolveMember, resolveUser, trimLines } from "../../../utils.js";
|
||||
import { asyncMap } from "../../../utils/async.js";
|
||||
import { createPaginatedMessage } from "../../../utils/createPaginatedMessage.js";
|
||||
import { getGuildPrefix } from "../../../utils/getGuildPrefix.js";
|
||||
import { CasesPlugin } from "../../Cases/CasesPlugin.js";
|
||||
import { modActionsCmd } from "../types.js";
|
||||
|
||||
const opts = {
|
||||
mod: ct.userId({ option: true }),
|
||||
};
|
||||
|
||||
const casesPerPage = 5;
|
||||
|
||||
export const CasesModCmd = modActionsCmd({
|
||||
trigger: ["cases", "modlogs", "infractions"],
|
||||
permission: "can_view",
|
||||
description: "Show the most recent 5 cases by the specified -mod",
|
||||
|
||||
signature: [
|
||||
{
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const modId = args.mod || msg.author.id;
|
||||
const mod =
|
||||
(await resolveMember(pluginData.client, pluginData.guild, modId)) ||
|
||||
(await resolveUser(pluginData.client, modId));
|
||||
const modName = mod instanceof UnknownUser ? modId : renderUsername(mod);
|
||||
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const totalCases = await casesPlugin.getTotalCasesByMod(modId);
|
||||
|
||||
if (totalCases === 0) {
|
||||
sendErrorMessage(pluginData, msg.channel, `No cases by **${modName}**`);
|
||||
return;
|
||||
}
|
||||
|
||||
const totalPages = Math.max(Math.ceil(totalCases / casesPerPage), 1);
|
||||
const prefix = getGuildPrefix(pluginData);
|
||||
|
||||
createPaginatedMessage(
|
||||
pluginData.client,
|
||||
msg.channel,
|
||||
totalPages,
|
||||
async (page) => {
|
||||
const cases = await casesPlugin.getRecentCasesByMod(modId, casesPerPage, (page - 1) * casesPerPage);
|
||||
const lines = await asyncMap(cases, (c) => casesPlugin.getCaseSummary(c, true, msg.author.id));
|
||||
|
||||
const isLastPage = page === totalPages;
|
||||
const firstCaseNum = (page - 1) * casesPerPage + 1;
|
||||
const lastCaseNum = isLastPage ? totalCases : page * casesPerPage;
|
||||
const title = `Most recent cases ${firstCaseNum}-${lastCaseNum} of ${totalCases} by ${modName}`;
|
||||
|
||||
const embed = {
|
||||
author: {
|
||||
name: title,
|
||||
icon_url: mod instanceof UnknownUser ? undefined : mod.displayAvatarURL(),
|
||||
},
|
||||
description: lines.join("\n"),
|
||||
fields: [
|
||||
{
|
||||
name: emptyEmbedValue,
|
||||
value: trimLines(`
|
||||
Use \`${prefix}case <num>\` to see more information about an individual case
|
||||
Use \`${prefix}cases <user>\` to see a specific user's cases
|
||||
`),
|
||||
},
|
||||
],
|
||||
} satisfies APIEmbed;
|
||||
|
||||
return { embeds: [embed] };
|
||||
},
|
||||
{
|
||||
limitToUserId: msg.author.id,
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
|
@ -1,156 +0,0 @@
|
|||
import { APIEmbed, User } from "discord.js";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { CaseTypes } from "../../../data/CaseTypes.js";
|
||||
import { sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin.js";
|
||||
import {
|
||||
UnknownUser,
|
||||
chunkArray,
|
||||
emptyEmbedValue,
|
||||
renderUsername,
|
||||
resolveMember,
|
||||
resolveUser,
|
||||
} from "../../../utils.js";
|
||||
import { asyncMap } from "../../../utils/async.js";
|
||||
import { createPaginatedMessage } from "../../../utils/createPaginatedMessage.js";
|
||||
import { getGuildPrefix } from "../../../utils/getGuildPrefix.js";
|
||||
import { modActionsCmd } from "../types.js";
|
||||
|
||||
const opts = {
|
||||
expand: ct.bool({ option: true, isSwitch: true, shortcut: "e" }),
|
||||
hidden: ct.bool({ option: true, isSwitch: true, shortcut: "h" }),
|
||||
reverseFilters: ct.switchOption({ def: false, shortcut: "r" }),
|
||||
notes: ct.switchOption({ def: false, shortcut: "n" }),
|
||||
warns: ct.switchOption({ def: false, shortcut: "w" }),
|
||||
mutes: ct.switchOption({ def: false, shortcut: "m" }),
|
||||
unmutes: ct.switchOption({ def: false, shortcut: "um" }),
|
||||
bans: ct.switchOption({ def: false, shortcut: "b" }),
|
||||
unbans: ct.switchOption({ def: false, shortcut: "ub" }),
|
||||
};
|
||||
|
||||
const casesPerPage = 5;
|
||||
|
||||
export const CasesUserCmd = modActionsCmd({
|
||||
trigger: ["cases", "modlogs"],
|
||||
permission: "can_view",
|
||||
description: "Show a list of cases the specified user has",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user =
|
||||
(await resolveMember(pluginData.client, pluginData.guild, args.user)) ||
|
||||
(await resolveUser(pluginData.client, args.user));
|
||||
if (user instanceof UnknownUser) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
let cases = await pluginData.state.cases.with("notes").getByUserId(user.id);
|
||||
|
||||
const typesToShow: CaseTypes[] = [];
|
||||
if (args.notes) typesToShow.push(CaseTypes.Note);
|
||||
if (args.warns) typesToShow.push(CaseTypes.Warn);
|
||||
if (args.mutes) typesToShow.push(CaseTypes.Mute);
|
||||
if (args.unmutes) typesToShow.push(CaseTypes.Unmute);
|
||||
if (args.bans) typesToShow.push(CaseTypes.Ban);
|
||||
if (args.unbans) typesToShow.push(CaseTypes.Unban);
|
||||
|
||||
if (typesToShow.length > 0) {
|
||||
// Reversed: Hide specified types
|
||||
if (args.reverseFilters) cases = cases.filter((c) => !typesToShow.includes(c.type));
|
||||
// Normal: Show only specified types
|
||||
else cases = cases.filter((c) => typesToShow.includes(c.type));
|
||||
}
|
||||
|
||||
const normalCases = cases.filter((c) => !c.is_hidden);
|
||||
const hiddenCases = cases.filter((c) => c.is_hidden);
|
||||
|
||||
const userName =
|
||||
user instanceof UnknownUser && cases.length ? cases[cases.length - 1].user_name : renderUsername(user);
|
||||
|
||||
if (cases.length === 0) {
|
||||
msg.channel.send(`No cases found for **${userName}**`);
|
||||
} else {
|
||||
const casesToDisplay = args.hidden ? cases : normalCases;
|
||||
if (!casesToDisplay.length) {
|
||||
msg.channel.send(
|
||||
`No normal cases found for **${userName}**. Use "-hidden" to show ${cases.length} hidden cases.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.expand) {
|
||||
if (casesToDisplay.length > 8) {
|
||||
msg.channel.send("Too many cases for expanded view. Please use compact view instead.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Expanded view (= individual case embeds)
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
for (const theCase of casesToDisplay) {
|
||||
const embed = await casesPlugin.getCaseEmbed(theCase.id);
|
||||
msg.channel.send(embed);
|
||||
}
|
||||
} else {
|
||||
// Compact view (= regular message with a preview of each case)
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
|
||||
const totalPages = Math.max(Math.ceil(casesToDisplay.length / casesPerPage), 1);
|
||||
const prefix = getGuildPrefix(pluginData);
|
||||
|
||||
createPaginatedMessage(
|
||||
pluginData.client,
|
||||
msg.channel,
|
||||
totalPages,
|
||||
async (page) => {
|
||||
const chunkedCases = chunkArray(casesToDisplay, casesPerPage)[page - 1];
|
||||
const lines = await asyncMap(chunkedCases, (c) => casesPlugin.getCaseSummary(c, true, msg.author.id));
|
||||
|
||||
const isLastPage = page === totalPages;
|
||||
const firstCaseNum = (page - 1) * casesPerPage + 1;
|
||||
const lastCaseNum = isLastPage ? casesToDisplay.length : page * casesPerPage;
|
||||
const title =
|
||||
totalPages === 1
|
||||
? `Cases for ${userName} (${lines.length} total)`
|
||||
: `Most recent cases ${firstCaseNum}-${lastCaseNum} of ${casesToDisplay.length} for ${userName}`;
|
||||
|
||||
const embed = {
|
||||
author: {
|
||||
name: title,
|
||||
icon_url: user instanceof User ? user.displayAvatarURL() : undefined,
|
||||
},
|
||||
description: lines.join("\n"),
|
||||
fields: [
|
||||
{
|
||||
name: emptyEmbedValue,
|
||||
value: `Use \`${prefix}case <num>\` to see more information about an individual case`,
|
||||
},
|
||||
],
|
||||
} satisfies APIEmbed;
|
||||
|
||||
if (isLastPage && !args.hidden && hiddenCases.length)
|
||||
embed.fields.push({
|
||||
name: emptyEmbedValue,
|
||||
value:
|
||||
hiddenCases.length === 1
|
||||
? `*+${hiddenCases.length} hidden case, use "-hidden" to show it*`
|
||||
: `*+${hiddenCases.length} hidden cases, use "-hidden" to show them*`,
|
||||
});
|
||||
|
||||
return { embeds: [embed] };
|
||||
},
|
||||
{
|
||||
limitToUserId: msg.author.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
|
@ -1,98 +0,0 @@
|
|||
import { helpers } from "knub";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { Case } from "../../../data/entities/Case.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { SECONDS, renderUsername, trimLines } from "../../../utils.js";
|
||||
import { CasesPlugin } from "../../Cases/CasesPlugin.js";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin.js";
|
||||
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin.js";
|
||||
import { modActionsCmd } from "../types.js";
|
||||
|
||||
export const DeleteCaseCmd = modActionsCmd({
|
||||
trigger: ["delete_case", "deletecase"],
|
||||
permission: "can_deletecase",
|
||||
description: trimLines(`
|
||||
Delete the specified case. This operation can *not* be reversed.
|
||||
It is generally recommended to use \`!hidecase\` instead when possible.
|
||||
`),
|
||||
|
||||
signature: {
|
||||
caseNumber: ct.number({ rest: true }),
|
||||
|
||||
force: ct.switchOption({ def: false, shortcut: "f" }),
|
||||
},
|
||||
|
||||
async run({ pluginData, message, args }) {
|
||||
const failed: number[] = [];
|
||||
const validCases: Case[] = [];
|
||||
let cancelled = 0;
|
||||
|
||||
for (const num of args.caseNumber) {
|
||||
const theCase = await pluginData.state.cases.findByCaseNumber(num);
|
||||
if (!theCase) {
|
||||
failed.push(num);
|
||||
continue;
|
||||
}
|
||||
|
||||
validCases.push(theCase);
|
||||
}
|
||||
|
||||
if (failed.length === args.caseNumber.length) {
|
||||
sendErrorMessage(pluginData, message.channel, "None of the cases were found!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const theCase of validCases) {
|
||||
if (!args.force) {
|
||||
const cases = pluginData.getPlugin(CasesPlugin);
|
||||
const embedContent = await cases.getCaseEmbed(theCase);
|
||||
message.channel.send({
|
||||
...embedContent,
|
||||
content: "Delete the following case? Answer 'Yes' to continue, 'No' to cancel.",
|
||||
});
|
||||
|
||||
const reply = await helpers.waitForReply(pluginData.client, message.channel, message.author.id, 15 * SECONDS);
|
||||
const normalizedReply = (reply?.content || "").toLowerCase().trim();
|
||||
if (normalizedReply !== "yes" && normalizedReply !== "y") {
|
||||
message.channel.send("Cancelled. Case was not deleted.");
|
||||
cancelled++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const deletedByName = renderUsername(message.author);
|
||||
|
||||
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
|
||||
const deletedAt = timeAndDate.inGuildTz().format(timeAndDate.getDateFormat("pretty_datetime"));
|
||||
|
||||
await pluginData.state.cases.softDelete(
|
||||
theCase.id,
|
||||
message.author.id,
|
||||
deletedByName,
|
||||
`Case deleted by **${deletedByName}** (\`${message.author.id}\`) on ${deletedAt}`,
|
||||
);
|
||||
|
||||
const logs = pluginData.getPlugin(LogsPlugin);
|
||||
logs.logCaseDelete({
|
||||
mod: message.member,
|
||||
case: theCase,
|
||||
});
|
||||
}
|
||||
|
||||
const failedAddendum =
|
||||
failed.length > 0
|
||||
? `\nThe following cases were not found: ${failed.toString().replace(new RegExp(",", "g"), ", ")}`
|
||||
: "";
|
||||
const amt = validCases.length - cancelled;
|
||||
if (amt === 0) {
|
||||
sendErrorMessage(pluginData, message.channel, "All deletions were cancelled, no cases were deleted.");
|
||||
return;
|
||||
}
|
||||
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
message.channel,
|
||||
`${amt} case${amt === 1 ? " was" : "s were"} deleted!${failedAddendum}`,
|
||||
);
|
||||
},
|
||||
});
|
|
@ -1,103 +0,0 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { CaseTypes } from "../../../data/CaseTypes.js";
|
||||
import { LogType } from "../../../data/LogType.js";
|
||||
import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin.js";
|
||||
import { DAYS, MINUTES, resolveMember, resolveUser } from "../../../utils.js";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin.js";
|
||||
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments.js";
|
||||
import { ignoreEvent } from "../functions/ignoreEvent.js";
|
||||
import { isBanned } from "../functions/isBanned.js";
|
||||
import { IgnoredEventType, modActionsCmd } from "../types.js";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
};
|
||||
|
||||
export const ForcebanCmd = modActionsCmd({
|
||||
trigger: "forceban",
|
||||
permission: "can_ban",
|
||||
description: "Force-ban the specified user, even if they aren't on the server",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user.id) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user exists as a guild member, make sure we can act on them first
|
||||
const member = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
if (member && !canActOn(pluginData, msg.member, member)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot forceban this user: insufficient permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the user isn't already banned
|
||||
const banned = await isBanned(pluginData, user.id);
|
||||
if (banned) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User is already banned`);
|
||||
return;
|
||||
}
|
||||
|
||||
// The moderator who did the action is the message author or, if used, the specified -mod
|
||||
let mod = msg.member;
|
||||
if (args.mod) {
|
||||
if (!(await hasPermission(pluginData, "can_act_as_other", { message: msg }))) {
|
||||
sendErrorMessage(pluginData, msg.channel, "You don't have permission to use -mod");
|
||||
return;
|
||||
}
|
||||
|
||||
mod = args.mod;
|
||||
}
|
||||
|
||||
const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]);
|
||||
|
||||
ignoreEvent(pluginData, IgnoredEventType.Ban, user.id);
|
||||
pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, user.id);
|
||||
|
||||
try {
|
||||
// FIXME: Use banUserId()?
|
||||
await pluginData.guild.bans.create(user.id as Snowflake, {
|
||||
deleteMessageSeconds: (1 * DAYS) / MINUTES,
|
||||
reason: reason ?? undefined,
|
||||
});
|
||||
} catch {
|
||||
sendErrorMessage(pluginData, msg.channel, "Failed to forceban member");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a case
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Ban,
|
||||
reason,
|
||||
ppId: mod.id !== msg.author.id ? msg.author.id : undefined,
|
||||
});
|
||||
|
||||
// Confirm the action
|
||||
sendSuccessMessage(pluginData, msg.channel, `Member forcebanned (Case #${createdCase.case_number})`);
|
||||
|
||||
// Log the action
|
||||
pluginData.getPlugin(LogsPlugin).logMemberForceban({
|
||||
mod,
|
||||
userId: user.id,
|
||||
caseNumber: createdCase.case_number,
|
||||
reason,
|
||||
});
|
||||
|
||||
pluginData.state.events.emit("ban", user.id, reason);
|
||||
},
|
||||
});
|
|
@ -1,51 +0,0 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { canActOn, sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { resolveMember, resolveUser } from "../../../utils.js";
|
||||
import { actualMuteUserCmd } from "../functions/actualMuteUserCmd.js";
|
||||
import { modActionsCmd } from "../types.js";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
notify: ct.string({ option: true }),
|
||||
"notify-channel": ct.textChannel({ option: true }),
|
||||
};
|
||||
|
||||
export const ForcemuteCmd = modActionsCmd({
|
||||
trigger: "forcemute",
|
||||
permission: "can_mute",
|
||||
description: "Force-mute the specified user, even if they're not on the server",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
time: ct.delay(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user.id) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const memberToMute = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
|
||||
// Make sure we're allowed to mute this user
|
||||
if (memberToMute && !canActOn(pluginData, msg.member, memberToMute)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot mute: insufficient permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
actualMuteUserCmd(pluginData, user, msg, { ...args, notify: "none" });
|
||||
},
|
||||
});
|
|
@ -1,56 +0,0 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { canActOn, sendErrorMessage } from "../../../pluginUtils.js";
|
||||
import { resolveMember, resolveUser } from "../../../utils.js";
|
||||
import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd.js";
|
||||
import { modActionsCmd } from "../types.js";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
};
|
||||
|
||||
export const ForceUnmuteCmd = modActionsCmd({
|
||||
trigger: "forceunmute",
|
||||
permission: "can_mute",
|
||||
description: "Force-unmute the specified user, even if they're not on the server",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
time: ct.delay(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user.id) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if they're muted in the first place
|
||||
if (!(await pluginData.state.mutes.isMuted(user.id))) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot unmute: member is not muted");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the server member to unmute
|
||||
const memberToUnmute = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
|
||||
// Make sure we're allowed to unmute this member
|
||||
if (memberToUnmute && !canActOn(pluginData, msg.member, memberToUnmute)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot unmute: insufficient permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
actualUnmuteCmd(pluginData, user, msg, args);
|
||||
},
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils.js";
|
||||
import { modActionsCmd } from "../types.js";
|
||||
|
||||
export const HideCaseCmd = modActionsCmd({
|
||||
trigger: ["hide", "hidecase", "hide_case"],
|
||||
permission: "can_hidecase",
|
||||
description: "Hide the specified case so it doesn't appear in !cases or !info",
|
||||
|
||||
signature: [
|
||||
{
|
||||
caseNum: ct.number({ rest: true }),
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const failed: number[] = [];
|
||||
|
||||
for (const num of args.caseNum) {
|
||||
const theCase = await pluginData.state.cases.findByCaseNumber(num);
|
||||
if (!theCase) {
|
||||
failed.push(num);
|
||||
continue;
|
||||
}
|
||||
|
||||
await pluginData.state.cases.setHidden(theCase.id, true);
|
||||
}
|
||||
|
||||
if (failed.length === args.caseNum.length) {
|
||||
sendErrorMessage(pluginData, msg.channel, "None of the cases were found!");
|
||||
return;
|
||||
}
|
||||
const failedAddendum =
|
||||
failed.length > 0
|
||||
? `\nThe following cases were not found: ${failed.toString().replace(new RegExp(",", "g"), ", ")}`
|
||||
: "";
|
||||
|
||||
const amt = args.caseNum.length - failed.length;
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`${amt} case${amt === 1 ? " is" : "s are"} now hidden! Use \`unhidecase\` to unhide them.${failedAddendum}`,
|
||||
);
|
||||
},
|
||||
});
|
|
@ -1,29 +0,0 @@
|
|||
import { commandTypeHelpers as ct } from "../../../commandTypes.js";
|
||||
import { actualKickMemberCmd } from "../functions/actualKickMemberCmd.js";
|
||||
import { modActionsCmd } from "../types.js";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
notify: ct.string({ option: true }),
|
||||
"notify-channel": ct.textChannel({ option: true }),
|
||||
clean: ct.bool({ option: true, isSwitch: true }),
|
||||
};
|
||||
|
||||
export const KickCmd = modActionsCmd({
|
||||
trigger: "kick",
|
||||
permission: "can_kick",
|
||||
description: "Kick the specified member",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
actualKickMemberCmd(pluginData, msg, args);
|
||||
},
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue