mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-20 16:25:03 +00:00
Merge branch 'master' into fix_automodBanDuration
This commit is contained in:
commit
b3f5011f5d
31 changed files with 346 additions and 127 deletions
|
@ -235,14 +235,20 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
|
||||||
pluginData.state.modActionsListeners.set("note", (userId: string) =>
|
pluginData.state.modActionsListeners.set("note", (userId: string) =>
|
||||||
runAutomodOnModAction(pluginData, "note", userId),
|
runAutomodOnModAction(pluginData, "note", userId),
|
||||||
);
|
);
|
||||||
pluginData.state.modActionsListeners.set("warn", (userId: string) =>
|
pluginData.state.modActionsListeners.set(
|
||||||
runAutomodOnModAction(pluginData, "warn", userId),
|
"warn",
|
||||||
|
(userId: string, reason: string | undefined, isAutomodAction: boolean) =>
|
||||||
|
runAutomodOnModAction(pluginData, "warn", userId, reason, isAutomodAction),
|
||||||
);
|
);
|
||||||
pluginData.state.modActionsListeners.set("kick", (userId: string) =>
|
pluginData.state.modActionsListeners.set(
|
||||||
runAutomodOnModAction(pluginData, "kick", userId),
|
"kick",
|
||||||
|
(userId: string, reason: string | undefined, isAutomodAction: boolean) =>
|
||||||
|
runAutomodOnModAction(pluginData, "kick", userId, reason, isAutomodAction),
|
||||||
);
|
);
|
||||||
pluginData.state.modActionsListeners.set("ban", (userId: string) =>
|
pluginData.state.modActionsListeners.set(
|
||||||
runAutomodOnModAction(pluginData, "ban", userId),
|
"ban",
|
||||||
|
(userId: string, reason: string | undefined, isAutomodAction: boolean) =>
|
||||||
|
runAutomodOnModAction(pluginData, "ban", userId, reason, isAutomodAction),
|
||||||
);
|
);
|
||||||
pluginData.state.modActionsListeners.set("unban", (userId: string) =>
|
pluginData.state.modActionsListeners.set("unban", (userId: string) =>
|
||||||
runAutomodOnModAction(pluginData, "unban", userId),
|
runAutomodOnModAction(pluginData, "unban", userId),
|
||||||
|
@ -251,7 +257,11 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
|
||||||
|
|
||||||
const mutesEvents = pluginData.getPlugin(MutesPlugin).getEventEmitter();
|
const mutesEvents = pluginData.getPlugin(MutesPlugin).getEventEmitter();
|
||||||
pluginData.state.mutesListeners = new Map();
|
pluginData.state.mutesListeners = new Map();
|
||||||
pluginData.state.mutesListeners.set("mute", (userId: string) => runAutomodOnModAction(pluginData, "mute", userId));
|
pluginData.state.mutesListeners.set(
|
||||||
|
"mute",
|
||||||
|
(userId: string, reason: string | undefined, isAutomodAction: boolean) =>
|
||||||
|
runAutomodOnModAction(pluginData, "mute", userId, reason, isAutomodAction),
|
||||||
|
);
|
||||||
pluginData.state.mutesListeners.set("unmute", (userId: string) =>
|
pluginData.state.mutesListeners.set("unmute", (userId: string) =>
|
||||||
runAutomodOnModAction(pluginData, "unmute", userId),
|
runAutomodOnModAction(pluginData, "unmute", userId),
|
||||||
);
|
);
|
||||||
|
|
|
@ -43,7 +43,12 @@ export const BanAction = automodAction({
|
||||||
|
|
||||||
const modActions = pluginData.getPlugin(ModActionsPlugin);
|
const modActions = pluginData.getPlugin(ModActionsPlugin);
|
||||||
for (const userId of userIdsToBan) {
|
for (const userId of userIdsToBan) {
|
||||||
await modActions.banUserId(userId, reason, { contactMethods, caseArgs, deleteMessageDays }, duration);
|
await modActions.banUserId(userId, reason, {
|
||||||
|
contactMethods,
|
||||||
|
caseArgs,
|
||||||
|
deleteMessageDays,
|
||||||
|
isAutomodAction: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,7 +33,7 @@ export const KickAction = automodAction({
|
||||||
const modActions = pluginData.getPlugin(ModActionsPlugin);
|
const modActions = pluginData.getPlugin(ModActionsPlugin);
|
||||||
for (const member of membersToKick) {
|
for (const member of membersToKick) {
|
||||||
if (!member) continue;
|
if (!member) continue;
|
||||||
await modActions.kickMember(member, reason, { contactMethods, caseArgs });
|
await modActions.kickMember(member, reason, { contactMethods, caseArgs, isAutomodAction: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,7 +49,14 @@ export const MuteAction = automodAction({
|
||||||
const mutes = pluginData.getPlugin(MutesPlugin);
|
const mutes = pluginData.getPlugin(MutesPlugin);
|
||||||
for (const userId of userIdsToMute) {
|
for (const userId of userIdsToMute) {
|
||||||
try {
|
try {
|
||||||
await mutes.muteUser(userId, duration, reason, { contactMethods, caseArgs }, rolesToRemove, rolesToRestore);
|
await mutes.muteUser(
|
||||||
|
userId,
|
||||||
|
duration,
|
||||||
|
reason,
|
||||||
|
{ contactMethods, caseArgs, isAutomodAction: true },
|
||||||
|
rolesToRemove,
|
||||||
|
rolesToRestore,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) {
|
if (e instanceof RecoverablePluginError && e.code === ERRORS.NO_MUTE_ROLE_IN_CONFIG) {
|
||||||
pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, {
|
pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, {
|
||||||
|
|
|
@ -33,7 +33,7 @@ export const WarnAction = automodAction({
|
||||||
const modActions = pluginData.getPlugin(ModActionsPlugin);
|
const modActions = pluginData.getPlugin(ModActionsPlugin);
|
||||||
for (const member of membersToWarn) {
|
for (const member of membersToWarn) {
|
||||||
if (!member) continue;
|
if (!member) continue;
|
||||||
await modActions.warnMember(member, reason, { contactMethods, caseArgs });
|
await modActions.warnMember(member, reason, { contactMethods, caseArgs, isAutomodAction: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ export async function runAutomodOnModAction(
|
||||||
modAction: ModActionType,
|
modAction: ModActionType,
|
||||||
userId: string,
|
userId: string,
|
||||||
reason?: string,
|
reason?: string,
|
||||||
|
isAutomodAction: boolean = false,
|
||||||
) {
|
) {
|
||||||
const user = await resolveUser(pluginData.client, userId);
|
const user = await resolveUser(pluginData.client, userId);
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ export async function runAutomodOnModAction(
|
||||||
modAction: {
|
modAction: {
|
||||||
type: modAction,
|
type: modAction,
|
||||||
reason,
|
reason,
|
||||||
|
isAutomodAction,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -64,9 +64,9 @@ export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = {
|
||||||
reason: 'Auto-muted for spam'
|
reason: 'Auto-muted for spam'
|
||||||
my_second_filter:
|
my_second_filter:
|
||||||
triggers:
|
triggers:
|
||||||
- message_spam:
|
- emoji_spam:
|
||||||
amount: 5
|
amount: 2
|
||||||
within: 10s
|
within: 5s
|
||||||
actions:
|
actions:
|
||||||
clean: true
|
clean: true
|
||||||
overrides:
|
overrides:
|
||||||
|
|
|
@ -5,13 +5,25 @@ import { automodTrigger } from "../helpers";
|
||||||
interface BanTriggerResultType {}
|
interface BanTriggerResultType {}
|
||||||
|
|
||||||
export const BanTrigger = automodTrigger<BanTriggerResultType>()({
|
export const BanTrigger = automodTrigger<BanTriggerResultType>()({
|
||||||
configType: t.type({}),
|
configType: t.type({
|
||||||
defaultConfig: {},
|
manual: t.boolean,
|
||||||
|
automatic: t.boolean,
|
||||||
|
}),
|
||||||
|
|
||||||
async match({ context }) {
|
defaultConfig: {
|
||||||
|
manual: true,
|
||||||
|
automatic: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
async match({ context, triggerConfig }) {
|
||||||
if (context.modAction?.type !== "ban") {
|
if (context.modAction?.type !== "ban") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log(context);
|
||||||
|
// If automatic && automatic turned off -> return
|
||||||
|
if (context.modAction.isAutomodAction && !triggerConfig.automatic) return;
|
||||||
|
// If manual && manual turned off -> return
|
||||||
|
if (!context.modAction.isAutomodAction && !triggerConfig.manual) return;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
extra: {},
|
extra: {},
|
||||||
|
|
|
@ -5,13 +5,24 @@ import { automodTrigger } from "../helpers";
|
||||||
interface KickTriggerResultType {}
|
interface KickTriggerResultType {}
|
||||||
|
|
||||||
export const KickTrigger = automodTrigger<KickTriggerResultType>()({
|
export const KickTrigger = automodTrigger<KickTriggerResultType>()({
|
||||||
configType: t.type({}),
|
configType: t.type({
|
||||||
defaultConfig: {},
|
manual: t.boolean,
|
||||||
|
automatic: t.boolean,
|
||||||
|
}),
|
||||||
|
|
||||||
async match({ context }) {
|
defaultConfig: {
|
||||||
|
manual: true,
|
||||||
|
automatic: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
async match({ context, triggerConfig }) {
|
||||||
if (context.modAction?.type !== "kick") {
|
if (context.modAction?.type !== "kick") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// If automatic && automatic turned off -> return
|
||||||
|
if (context.modAction.isAutomodAction && !triggerConfig.automatic) return;
|
||||||
|
// If manual && manual turned off -> return
|
||||||
|
if (!context.modAction.isAutomodAction && !triggerConfig.manual) return;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
extra: {},
|
extra: {},
|
||||||
|
|
|
@ -5,13 +5,24 @@ import { automodTrigger } from "../helpers";
|
||||||
interface MuteTriggerResultType {}
|
interface MuteTriggerResultType {}
|
||||||
|
|
||||||
export const MuteTrigger = automodTrigger<MuteTriggerResultType>()({
|
export const MuteTrigger = automodTrigger<MuteTriggerResultType>()({
|
||||||
configType: t.type({}),
|
configType: t.type({
|
||||||
defaultConfig: {},
|
manual: t.boolean,
|
||||||
|
automatic: t.boolean,
|
||||||
|
}),
|
||||||
|
|
||||||
async match({ context }) {
|
defaultConfig: {
|
||||||
|
manual: true,
|
||||||
|
automatic: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
async match({ context, triggerConfig }) {
|
||||||
if (context.modAction?.type !== "mute") {
|
if (context.modAction?.type !== "mute") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// If automatic && automatic turned off -> return
|
||||||
|
if (context.modAction.isAutomodAction && !triggerConfig.automatic) return;
|
||||||
|
// If manual && manual turned off -> return
|
||||||
|
if (!context.modAction.isAutomodAction && !triggerConfig.manual) return;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
extra: {},
|
extra: {},
|
||||||
|
|
|
@ -5,13 +5,24 @@ import { automodTrigger } from "../helpers";
|
||||||
interface WarnTriggerResultType {}
|
interface WarnTriggerResultType {}
|
||||||
|
|
||||||
export const WarnTrigger = automodTrigger<WarnTriggerResultType>()({
|
export const WarnTrigger = automodTrigger<WarnTriggerResultType>()({
|
||||||
configType: t.type({}),
|
configType: t.type({
|
||||||
defaultConfig: {},
|
manual: t.boolean,
|
||||||
|
automatic: t.boolean,
|
||||||
|
}),
|
||||||
|
|
||||||
async match({ context }) {
|
defaultConfig: {
|
||||||
|
manual: true,
|
||||||
|
automatic: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
async match({ context, triggerConfig }) {
|
||||||
if (context.modAction?.type !== "warn") {
|
if (context.modAction?.type !== "warn") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// If automatic && automatic turned off -> return
|
||||||
|
if (context.modAction.isAutomodAction && !triggerConfig.automatic) return;
|
||||||
|
// If manual && manual turned off -> return
|
||||||
|
if (!context.modAction.isAutomodAction && !triggerConfig.manual) return;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
extra: {},
|
extra: {},
|
||||||
|
|
|
@ -122,6 +122,7 @@ export interface AutomodContext {
|
||||||
modAction?: {
|
modAction?: {
|
||||||
type: ModActionType;
|
type: ModActionType;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
|
isAutomodAction: boolean;
|
||||||
};
|
};
|
||||||
antiraid?: {
|
antiraid?: {
|
||||||
level: string | null;
|
level: string | null;
|
||||||
|
|
|
@ -67,7 +67,7 @@ export const SetCounterCmd = guildCommand<CountersPluginType>()({
|
||||||
|
|
||||||
let channel = args.channel;
|
let channel = args.channel;
|
||||||
if (!channel && counter.per_channel) {
|
if (!channel && counter.per_channel) {
|
||||||
message.channel.createMessage(`Which channel's counter value would you like to add to?`);
|
message.channel.createMessage(`Which channel's counter value would you like to change?`);
|
||||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||||
if (!reply || !reply.content) {
|
if (!reply || !reply.content) {
|
||||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||||
|
@ -85,7 +85,7 @@ export const SetCounterCmd = guildCommand<CountersPluginType>()({
|
||||||
|
|
||||||
let user = args.user;
|
let user = args.user;
|
||||||
if (!user && counter.per_user) {
|
if (!user && counter.per_user) {
|
||||||
message.channel.createMessage(`Which user's counter value would you like to add to?`);
|
message.channel.createMessage(`Which user's counter value would you like to change?`);
|
||||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||||
if (!reply || !reply.content) {
|
if (!reply || !reply.content) {
|
||||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||||
|
@ -103,7 +103,7 @@ export const SetCounterCmd = guildCommand<CountersPluginType>()({
|
||||||
|
|
||||||
let value = args.value;
|
let value = args.value;
|
||||||
if (!value) {
|
if (!value) {
|
||||||
message.channel.createMessage("How much would you like to add to the counter's value?");
|
message.channel.createMessage("What would you like to set the counter's value to?");
|
||||||
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
const reply = await waitForReply(pluginData.client, message.channel, message.author.id);
|
||||||
if (!reply || !reply.content) {
|
if (!reply || !reply.content) {
|
||||||
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
sendErrorMessage(pluginData, message.channel, "Cancelling");
|
||||||
|
@ -111,7 +111,7 @@ export const SetCounterCmd = guildCommand<CountersPluginType>()({
|
||||||
}
|
}
|
||||||
|
|
||||||
const potentialValue = parseInt(reply.content, 10);
|
const potentialValue = parseInt(reply.content, 10);
|
||||||
if (!potentialValue) {
|
if (Number.isNaN(potentialValue)) {
|
||||||
sendErrorMessage(pluginData, message.channel, "Not a number, cancelling");
|
sendErrorMessage(pluginData, message.channel, "Not a number, cancelling");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { GuildCases } from "../../data/GuildCases";
|
||||||
import { GuildLogs } from "../../data/GuildLogs";
|
import { GuildLogs } from "../../data/GuildLogs";
|
||||||
import { ForceUnmuteCmd } from "./commands/ForceunmuteCmd";
|
import { ForceUnmuteCmd } from "./commands/ForceunmuteCmd";
|
||||||
import { warnMember } from "./functions/warnMember";
|
import { warnMember } from "./functions/warnMember";
|
||||||
import { Member } from "eris";
|
import { Member, Message } from "eris";
|
||||||
import { kickMember } from "./functions/kickMember";
|
import { kickMember } from "./functions/kickMember";
|
||||||
import { banUserId } from "./functions/banUserId";
|
import { banUserId } from "./functions/banUserId";
|
||||||
import { MassmuteCmd } from "./commands/MassmuteCmd";
|
import { MassmuteCmd } from "./commands/MassmuteCmd";
|
||||||
|
@ -43,6 +43,7 @@ import { EventEmitter } from "events";
|
||||||
import { mapToPublicFn } from "../../pluginUtils";
|
import { mapToPublicFn } from "../../pluginUtils";
|
||||||
import { onModActionsEvent } from "./functions/onModActionsEvent";
|
import { onModActionsEvent } from "./functions/onModActionsEvent";
|
||||||
import { offModActionsEvent } from "./functions/offModActionsEvent";
|
import { offModActionsEvent } from "./functions/offModActionsEvent";
|
||||||
|
import { updateCase } from "./functions/updateCase";
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
config: {
|
config: {
|
||||||
|
@ -170,6 +171,12 @@ export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()("mod
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateCase(pluginData) {
|
||||||
|
return (msg: Message, caseNumber: number | null, note: string) => {
|
||||||
|
updateCase(pluginData, msg, { caseNumber, note });
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
on: mapToPublicFn(onModActionsEvent),
|
on: mapToPublicFn(onModActionsEvent),
|
||||||
off: mapToPublicFn(offModActionsEvent),
|
off: mapToPublicFn(offModActionsEvent),
|
||||||
getEventEmitter(pluginData) {
|
getEventEmitter(pluginData) {
|
||||||
|
|
|
@ -66,8 +66,8 @@ export const BanCmd = modActionsCmd({
|
||||||
const lock = await pluginData.locks.acquire(banLock(user));
|
const lock = await pluginData.locks.acquire(banLock(user));
|
||||||
let forceban = false;
|
let forceban = false;
|
||||||
const existingTempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id);
|
const existingTempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id);
|
||||||
const banned = await isBanned(pluginData, user.id);
|
|
||||||
if (!memberToBan) {
|
if (!memberToBan) {
|
||||||
|
const banned = await isBanned(pluginData, user.id);
|
||||||
if (banned) {
|
if (banned) {
|
||||||
// Abort if trying to ban user indefinitely if they are already banned indefinitely
|
// Abort if trying to ban user indefinitely if they are already banned indefinitely
|
||||||
if (!existingTempban && !time) {
|
if (!existingTempban && !time) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ const opts = {
|
||||||
const casesPerPage = 5;
|
const casesPerPage = 5;
|
||||||
|
|
||||||
export const CasesModCmd = modActionsCmd({
|
export const CasesModCmd = modActionsCmd({
|
||||||
trigger: ["cases", "modlogs"],
|
trigger: ["cases", "modlogs", "infractions"],
|
||||||
permission: "can_view",
|
permission: "can_view",
|
||||||
description: "Show the most recent 5 cases by the specified -mod",
|
description: "Show the most recent 5 cases by the specified -mod",
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
import { modActionsCmd } from "../types";
|
import { modActionsCmd } from "../types";
|
||||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
import { Case } from "../../../data/entities/Case";
|
import { updateCase } from "../functions/updateCase";
|
||||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
|
|
||||||
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
|
|
||||||
import { CasesPlugin } from "../../Cases/CasesPlugin";
|
|
||||||
import { LogType } from "../../../data/LogType";
|
|
||||||
import { CaseTypes } from "../../../data/CaseTypes";
|
|
||||||
|
|
||||||
export const UpdateCmd = modActionsCmd({
|
export const UpdateCmd = modActionsCmd({
|
||||||
trigger: ["update", "reason"],
|
trigger: ["update", "reason"],
|
||||||
|
@ -24,39 +19,6 @@ export const UpdateCmd = modActionsCmd({
|
||||||
],
|
],
|
||||||
|
|
||||||
async run({ pluginData, message: msg, args }) {
|
async run({ pluginData, message: msg, args }) {
|
||||||
let theCase: Case | undefined;
|
await updateCase(pluginData, msg, args);
|
||||||
if (args.caseNumber != null) {
|
|
||||||
theCase = await pluginData.state.cases.findByCaseNumber(args.caseNumber);
|
|
||||||
} else {
|
|
||||||
theCase = await pluginData.state.cases.findLatestByModId(msg.author.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!theCase) {
|
|
||||||
sendErrorMessage(pluginData, msg.channel, "Case not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args.note && msg.attachments.length === 0) {
|
|
||||||
sendErrorMessage(pluginData, msg.channel, "Text or attachment required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const note = formatReasonWithAttachments(args.note, msg.attachments);
|
|
||||||
|
|
||||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
|
||||||
await casesPlugin.createCaseNote({
|
|
||||||
caseId: theCase.id,
|
|
||||||
modId: msg.author.id,
|
|
||||||
body: note,
|
|
||||||
});
|
|
||||||
|
|
||||||
pluginData.state.serverLogs.log(LogType.CASE_UPDATE, {
|
|
||||||
mod: msg.author,
|
|
||||||
caseNumber: theCase.case_number,
|
|
||||||
caseType: CaseTypes[theCase.type],
|
|
||||||
note,
|
|
||||||
});
|
|
||||||
|
|
||||||
sendSuccessMessage(pluginData, msg.channel, `Case \`#${theCase.case_number}\` updated`);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -112,7 +112,5 @@ export const WarnCmd = modActionsCmd({
|
||||||
msg.channel,
|
msg.channel,
|
||||||
`Warned **${memberToWarn.user.username}#${memberToWarn.user.discriminator}** (Case #${warnResult.case.case_number})${messageResultText}`,
|
`Warned **${memberToWarn.user.username}#${memberToWarn.user.discriminator}** (Case #${warnResult.case.case_number})${messageResultText}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
pluginData.state.events.emit("warn", user.id, reason);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -137,7 +137,7 @@ export async function banUserId(
|
||||||
banTime: banTime ? humanizeDuration(banTime) : null,
|
banTime: banTime ? humanizeDuration(banTime) : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
pluginData.state.events.emit("ban", user.id, reason);
|
pluginData.state.events.emit("ban", user.id, reason, banOptions.isAutomodAction);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "success",
|
status: "success",
|
||||||
|
|
|
@ -1,16 +1,44 @@
|
||||||
import { GuildPluginData } from "knub";
|
import { GuildPluginData } from "knub";
|
||||||
import { ModActionsPluginType } from "../types";
|
import { ModActionsPluginType } from "../types";
|
||||||
import { isDiscordHTTPError } from "../../../utils";
|
import { isDiscordHTTPError, isDiscordRESTError, SECONDS, sleep } from "../../../utils";
|
||||||
|
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||||
|
import { LogType } from "../../../data/LogType";
|
||||||
|
import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions";
|
||||||
|
import { Constants } from "eris";
|
||||||
|
|
||||||
|
export async function isBanned(
|
||||||
|
pluginData: GuildPluginData<ModActionsPluginType>,
|
||||||
|
userId: string,
|
||||||
|
timeout: number = 5 * SECONDS,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const botMember = pluginData.guild.members.get(pluginData.client.user.id);
|
||||||
|
if (botMember && !hasDiscordPermissions(botMember.permissions, Constants.Permissions.banMembers)) {
|
||||||
|
pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, {
|
||||||
|
body: `Missing "Ban Members" permission to check for existing bans`,
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export async function isBanned(pluginData: GuildPluginData<ModActionsPluginType>, userId: string): Promise<boolean> {
|
|
||||||
try {
|
try {
|
||||||
const bans = await pluginData.guild.getBans();
|
const potentialBan = await Promise.race([pluginData.guild.getBan(userId), sleep(timeout)]);
|
||||||
return bans.some(b => b.user.id === userId);
|
return potentialBan != null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (isDiscordHTTPError(e) && e.code === 500) {
|
if (isDiscordRESTError(e) && e.code === 10026) {
|
||||||
|
// [10026]: Unknown Ban
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isDiscordHTTPError(e) && e.code === 500) {
|
||||||
|
// Internal server error, ignore
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDiscordRESTError(e) && e.code === 50013) {
|
||||||
|
pluginData.getPlugin(LogsPlugin).log(LogType.BOT_ALERT, {
|
||||||
|
body: `Missing "Ban Members" permission to check for existing bans`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ export async function kickMember(
|
||||||
reason,
|
reason,
|
||||||
});
|
});
|
||||||
|
|
||||||
pluginData.state.events.emit("kick", member.id, reason);
|
pluginData.state.events.emit("kick", member.id, reason, kickOptions.isAutomodAction);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "success",
|
status: "success",
|
||||||
|
|
44
backend/src/plugins/ModActions/functions/updateCase.ts
Normal file
44
backend/src/plugins/ModActions/functions/updateCase.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { Message } from "eris";
|
||||||
|
import { CaseTypes } from "../../../data/CaseTypes";
|
||||||
|
import { Case } from "../../../data/entities/Case";
|
||||||
|
import { LogType } from "../../../data/LogType";
|
||||||
|
import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin";
|
||||||
|
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
|
||||||
|
import { formatReasonWithAttachments } from "./formatReasonWithAttachments";
|
||||||
|
|
||||||
|
export async function updateCase(pluginData, msg: Message, args) {
|
||||||
|
let theCase: Case | undefined;
|
||||||
|
if (args.caseNumber != null) {
|
||||||
|
theCase = await pluginData.state.cases.findByCaseNumber(args.caseNumber);
|
||||||
|
} else {
|
||||||
|
theCase = await pluginData.state.cases.findLatestByModId(msg.author.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!theCase) {
|
||||||
|
sendErrorMessage(pluginData, msg.channel, "Case not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.note && msg.attachments.length === 0) {
|
||||||
|
sendErrorMessage(pluginData, msg.channel, "Text or attachment required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const note = formatReasonWithAttachments(args.note, msg.attachments);
|
||||||
|
|
||||||
|
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||||
|
await casesPlugin.createCaseNote({
|
||||||
|
caseId: theCase.id,
|
||||||
|
modId: msg.author.id,
|
||||||
|
body: note,
|
||||||
|
});
|
||||||
|
|
||||||
|
pluginData.state.serverLogs.log(LogType.CASE_UPDATE, {
|
||||||
|
mod: msg.author,
|
||||||
|
caseNumber: theCase.case_number,
|
||||||
|
caseType: CaseTypes[theCase.type],
|
||||||
|
note,
|
||||||
|
});
|
||||||
|
|
||||||
|
sendSuccessMessage(pluginData, msg.channel, `Case \`#${theCase.case_number}\` updated`);
|
||||||
|
}
|
|
@ -82,6 +82,8 @@ export async function warnMember(
|
||||||
reason,
|
reason,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pluginData.state.events.emit("warn", member.id, reason, warnOptions.isAutomodAction);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "success",
|
status: "success",
|
||||||
case: createdCase,
|
case: createdCase,
|
||||||
|
|
|
@ -48,9 +48,9 @@ export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||||
|
|
||||||
export interface ModActionsEvents {
|
export interface ModActionsEvents {
|
||||||
note: (userId: string, reason?: string) => void;
|
note: (userId: string, reason?: string) => void;
|
||||||
warn: (userId: string, reason?: string) => void;
|
warn: (userId: string, reason?: string, isAutomodAction?: boolean) => void;
|
||||||
kick: (userId: string, reason?: string) => void;
|
kick: (userId: string, reason?: string, isAutomodAction?: boolean) => void;
|
||||||
ban: (userId: string, reason?: string) => void;
|
ban: (userId: string, reason?: string, isAutomodAction?: boolean) => void;
|
||||||
unban: (userId: string, reason?: string) => void;
|
unban: (userId: string, reason?: string) => void;
|
||||||
// mute/unmute are in the Mutes plugin
|
// mute/unmute are in the Mutes plugin
|
||||||
}
|
}
|
||||||
|
@ -126,11 +126,13 @@ export interface WarnOptions {
|
||||||
caseArgs?: Partial<CaseArgs> | null;
|
caseArgs?: Partial<CaseArgs> | null;
|
||||||
contactMethods?: UserNotificationMethod[] | null;
|
contactMethods?: UserNotificationMethod[] | null;
|
||||||
retryPromptChannel?: TextChannel | null;
|
retryPromptChannel?: TextChannel | null;
|
||||||
|
isAutomodAction?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KickOptions {
|
export interface KickOptions {
|
||||||
caseArgs?: Partial<CaseArgs>;
|
caseArgs?: Partial<CaseArgs>;
|
||||||
contactMethods?: UserNotificationMethod[];
|
contactMethods?: UserNotificationMethod[];
|
||||||
|
isAutomodAction?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BanOptions {
|
export interface BanOptions {
|
||||||
|
@ -138,6 +140,7 @@ export interface BanOptions {
|
||||||
contactMethods?: UserNotificationMethod[];
|
contactMethods?: UserNotificationMethod[];
|
||||||
deleteMessageDays?: number;
|
deleteMessageDays?: number;
|
||||||
modId?: string;
|
modId?: string;
|
||||||
|
isAutomodAction?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModActionType = "note" | "warn" | "mute" | "unmute" | "kick" | "ban" | "unban";
|
export type ModActionType = "note" | "warn" | "mute" | "unmute" | "kick" | "ban" | "unban";
|
||||||
|
|
|
@ -247,7 +247,7 @@ export async function muteUser(
|
||||||
|
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
pluginData.state.events.emit("mute", user.id, reason);
|
pluginData.state.events.emit("mute", user.id, reason, muteOptions.isAutomodAction);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
case: theCase,
|
case: theCase,
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const ConfigSchema = t.type({
|
||||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||||
|
|
||||||
export interface MutesEvents {
|
export interface MutesEvents {
|
||||||
mute: (userId: string, reason?: string) => void;
|
mute: (userId: string, reason?: string, isAutomodAction?: boolean) => void;
|
||||||
unmute: (userId: string, reason?: string) => void;
|
unmute: (userId: string, reason?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +75,7 @@ export type UnmuteResult = {
|
||||||
export interface MuteOptions {
|
export interface MuteOptions {
|
||||||
caseArgs?: Partial<CaseArgs>;
|
caseArgs?: Partial<CaseArgs>;
|
||||||
contactMethods?: UserNotificationMethod[];
|
contactMethods?: UserNotificationMethod[];
|
||||||
|
isAutomodAction?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutesCmd = guildCommand<MutesPluginType>();
|
export const mutesCmd = guildCommand<MutesPluginType>();
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { remindersCmd } from "../types";
|
||||||
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
|
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
|
||||||
|
|
||||||
export const RemindCmd = remindersCmd({
|
export const RemindCmd = remindersCmd({
|
||||||
trigger: ["remind", "remindme"],
|
trigger: ["remind", "remindme", "reminder"],
|
||||||
usage: "!remind 3h Remind me of this in 3 hours please",
|
usage: "!remind 3h Remind me of this in 3 hours please",
|
||||||
permission: "can_use",
|
permission: "can_use",
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ import { SnowflakeInfoCmd } from "./commands/SnowflakeInfoCmd";
|
||||||
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
|
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
|
||||||
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
|
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
|
||||||
import { VcdisconnectCmd } from "./commands/VcdisconnectCmd";
|
import { VcdisconnectCmd } from "./commands/VcdisconnectCmd";
|
||||||
|
import { ModActionsPlugin } from "../ModActions/ModActionsPlugin";
|
||||||
import { refreshMembersIfNeeded } from "./refreshMembers";
|
import { refreshMembersIfNeeded } from "./refreshMembers";
|
||||||
|
|
||||||
const defaultOptions: PluginOptions<UtilityPluginType> = {
|
const defaultOptions: PluginOptions<UtilityPluginType> = {
|
||||||
|
@ -106,7 +107,7 @@ export const UtilityPlugin = zeppelinGuildPlugin<UtilityPluginType>()("utility",
|
||||||
prettyName: "Utility",
|
prettyName: "Utility",
|
||||||
},
|
},
|
||||||
|
|
||||||
dependencies: [TimeAndDatePlugin],
|
dependencies: [TimeAndDatePlugin, ModActionsPlugin],
|
||||||
configSchema: ConfigSchema,
|
configSchema: ConfigSchema,
|
||||||
defaultOptions,
|
defaultOptions,
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { GuildPluginData } from "knub";
|
||||||
import { SavedMessage } from "../../../data/entities/SavedMessage";
|
import { SavedMessage } from "../../../data/entities/SavedMessage";
|
||||||
import { LogType } from "../../../data/LogType";
|
import { LogType } from "../../../data/LogType";
|
||||||
import { allowTimeout } from "../../../RegExpRunner";
|
import { allowTimeout } from "../../../RegExpRunner";
|
||||||
|
import { ModActionsPlugin } from "../../../plugins/ModActions/ModActionsPlugin";
|
||||||
|
|
||||||
const MAX_CLEAN_COUNT = 150;
|
const MAX_CLEAN_COUNT = 150;
|
||||||
const MAX_CLEAN_TIME = 1 * DAYS;
|
const MAX_CLEAN_TIME = 1 * DAYS;
|
||||||
|
@ -49,23 +50,36 @@ async function cleanMessages(
|
||||||
return { archiveUrl };
|
return { archiveUrl };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
user: ct.userId({ option: true, shortcut: "u" }),
|
||||||
|
channel: ct.channelId({ option: true, shortcut: "c" }),
|
||||||
|
bots: ct.switchOption({ shortcut: "b" }),
|
||||||
|
"delete-pins": ct.switchOption({ shortcut: "p" }),
|
||||||
|
"has-invites": ct.switchOption({ shortcut: "i" }),
|
||||||
|
match: ct.regex({ option: true, shortcut: "m" }),
|
||||||
|
"to-id": ct.anyId({ option: true, shortcut: "id" }),
|
||||||
|
};
|
||||||
|
|
||||||
export const CleanCmd = utilityCmd({
|
export const CleanCmd = utilityCmd({
|
||||||
trigger: ["clean", "clear"],
|
trigger: ["clean", "clear"],
|
||||||
description: "Remove a number of recent messages",
|
description: "Remove a number of recent messages",
|
||||||
usage: "!clean 20",
|
usage: "!clean 20",
|
||||||
permission: "can_clean",
|
permission: "can_clean",
|
||||||
|
|
||||||
signature: {
|
signature: [
|
||||||
count: ct.number(),
|
{
|
||||||
|
count: ct.number(),
|
||||||
|
update: ct.number({ option: true, shortcut: "up" }),
|
||||||
|
|
||||||
user: ct.userId({ option: true, shortcut: "u" }),
|
...opts,
|
||||||
channel: ct.channelId({ option: true, shortcut: "c" }),
|
},
|
||||||
bots: ct.switchOption({ shortcut: "b" }),
|
{
|
||||||
"delete-pins": ct.switchOption({ shortcut: "p" }),
|
count: ct.number(),
|
||||||
"has-invites": ct.switchOption({ shortcut: "i" }),
|
update: ct.switchOption({ shortcut: "up" }),
|
||||||
match: ct.regex({ option: true, shortcut: "m" }),
|
|
||||||
"to-id": ct.anyId({ option: true, shortcut: "id" }),
|
...opts,
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
|
||||||
async run({ message: msg, args, pluginData }) {
|
async run({ message: msg, args, pluginData }) {
|
||||||
if (args.count > MAX_CLEAN_COUNT || args.count <= 0) {
|
if (args.count > MAX_CLEAN_COUNT || args.count <= 0) {
|
||||||
|
@ -155,6 +169,19 @@ export const CleanCmd = utilityCmd({
|
||||||
responseText += ` in <#${targetChannel.id}>\n${cleanResult.archiveUrl}`;
|
responseText += ` in <#${targetChannel.id}>\n${cleanResult.archiveUrl}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.update) {
|
||||||
|
const modActions = pluginData.getPlugin(ModActionsPlugin);
|
||||||
|
const channelId = targetChannel.id !== msg.channel.id ? targetChannel.id : msg.channel.id;
|
||||||
|
const updateMessage = `Cleaned ${messagesToClean.length} ${
|
||||||
|
messagesToClean.length === 1 ? "message" : "messages"
|
||||||
|
} in <#${channelId}>: ${cleanResult.archiveUrl}`;
|
||||||
|
if (typeof args.update === "number") {
|
||||||
|
modActions.updateCase(msg, args.update, updateMessage);
|
||||||
|
} else {
|
||||||
|
modActions.updateCase(msg, null, updateMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
responseMsg = await sendSuccessMessage(pluginData, msg.channel, responseText);
|
responseMsg = await sendSuccessMessage(pluginData, msg.channel, responseText);
|
||||||
} else {
|
} else {
|
||||||
responseMsg = await sendErrorMessage(pluginData, msg.channel, `Found no messages to clean!`);
|
responseMsg = await sendErrorMessage(pluginData, msg.channel, `Found no messages to clean!`);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Member, Message, User } from "eris";
|
import { Constants, Member, Message, User } from "eris";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import escapeStringRegexp from "escape-string-regexp";
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
import { isFullMessage, MINUTES, multiSorter, noop, sorter, trimLines } from "../../utils";
|
import { isFullMessage, MINUTES, multiSorter, noop, sorter, trimLines } from "../../utils";
|
||||||
|
@ -10,10 +10,11 @@ import { banSearchSignature } from "./commands/BanSearchCmd";
|
||||||
import { UtilityPluginType } from "./types";
|
import { UtilityPluginType } from "./types";
|
||||||
import { refreshMembersIfNeeded } from "./refreshMembers";
|
import { refreshMembersIfNeeded } from "./refreshMembers";
|
||||||
import { getUserInfoEmbed } from "./functions/getUserInfoEmbed";
|
import { getUserInfoEmbed } from "./functions/getUserInfoEmbed";
|
||||||
import { allowTimeout } from "../../RegExpRunner";
|
import { allowTimeout, RegExpRunner } from "../../RegExpRunner";
|
||||||
import { inputPatternToRegExp, InvalidRegexError } from "../../validatorUtils";
|
import { inputPatternToRegExp, InvalidRegexError } from "../../validatorUtils";
|
||||||
import { asyncFilter } from "../../utils/async";
|
import { asyncFilter } from "../../utils/async";
|
||||||
import Timeout = NodeJS.Timeout;
|
import Timeout = NodeJS.Timeout;
|
||||||
|
import { hasDiscordPermissions } from "../../utils/hasDiscordPermissions";
|
||||||
|
|
||||||
const SEARCH_RESULTS_PER_PAGE = 15;
|
const SEARCH_RESULTS_PER_PAGE = 15;
|
||||||
const SEARCH_ID_RESULTS_PER_PAGE = 50;
|
const SEARCH_ID_RESULTS_PER_PAGE = 50;
|
||||||
|
@ -29,6 +30,29 @@ class SearchError extends Error {}
|
||||||
type MemberSearchParams = ArgsFromSignatureOrArray<typeof searchCmdSignature>;
|
type MemberSearchParams = ArgsFromSignatureOrArray<typeof searchCmdSignature>;
|
||||||
type BanSearchParams = ArgsFromSignatureOrArray<typeof banSearchSignature>;
|
type BanSearchParams = ArgsFromSignatureOrArray<typeof banSearchSignature>;
|
||||||
|
|
||||||
|
type RegexRunner = InstanceType<typeof RegExpRunner>["exec"];
|
||||||
|
function getOptimizedRegExpRunner(pluginData: GuildPluginData<UtilityPluginType>, isSafeRegex: boolean): RegexRunner {
|
||||||
|
if (isSafeRegex) {
|
||||||
|
return async (regex: RegExp, str: string) => {
|
||||||
|
if (!regex.global) {
|
||||||
|
const singleMatch = regex.exec(str);
|
||||||
|
return singleMatch ? [singleMatch] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches: RegExpExecArray[] = [];
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
// tslint:disable-next-line:no-conditional-assignment
|
||||||
|
while ((match = regex.exec(str)) != null) {
|
||||||
|
matches.push(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches.length ? matches : null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return pluginData.state.regexRunner.exec.bind(pluginData.state.regexRunner);
|
||||||
|
}
|
||||||
|
|
||||||
export async function displaySearch(
|
export async function displaySearch(
|
||||||
pluginData: GuildPluginData<UtilityPluginType>,
|
pluginData: GuildPluginData<UtilityPluginType>,
|
||||||
args: MemberSearchParams,
|
args: MemberSearchParams,
|
||||||
|
@ -270,59 +294,51 @@ async function performMemberSearch(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.query) {
|
if (args.query) {
|
||||||
|
let isSafeRegex = true;
|
||||||
let queryRegex: RegExp;
|
let queryRegex: RegExp;
|
||||||
if (args.regex) {
|
if (args.regex) {
|
||||||
const flags = args["case-sensitive"] ? "" : "i";
|
const flags = args["case-sensitive"] ? "" : "i";
|
||||||
queryRegex = inputPatternToRegExp(args.query.trimStart());
|
queryRegex = inputPatternToRegExp(args.query.trimStart());
|
||||||
queryRegex = new RegExp(queryRegex.source, flags);
|
queryRegex = new RegExp(queryRegex.source, flags);
|
||||||
|
isSafeRegex = false;
|
||||||
} else {
|
} else {
|
||||||
queryRegex = new RegExp(escapeStringRegexp(args.query.trimStart()), args["case-sensitive"] ? "" : "i");
|
queryRegex = new RegExp(escapeStringRegexp(args.query.trimStart()), args["case-sensitive"] ? "" : "i");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const execRegExp = getOptimizedRegExpRunner(pluginData, isSafeRegex);
|
||||||
|
|
||||||
if (args["status-search"]) {
|
if (args["status-search"]) {
|
||||||
matchingMembers = await asyncFilter(matchingMembers, async member => {
|
matchingMembers = await asyncFilter(matchingMembers, async member => {
|
||||||
if (member.game) {
|
if (member.game) {
|
||||||
if (
|
if (member.game.name && (await execRegExp(queryRegex, member.game.name).catch(allowTimeout))) {
|
||||||
member.game.name &&
|
|
||||||
(await pluginData.state.regexRunner.exec(queryRegex, member.game.name).catch(allowTimeout))
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (member.game.state && (await execRegExp(queryRegex, member.game.state).catch(allowTimeout))) {
|
||||||
member.game.state &&
|
|
||||||
(await pluginData.state.regexRunner.exec(queryRegex, member.game.state).catch(allowTimeout))
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (member.game.details && (await execRegExp(queryRegex, member.game.details).catch(allowTimeout))) {
|
||||||
member.game.details &&
|
|
||||||
(await pluginData.state.regexRunner.exec(queryRegex, member.game.details).catch(allowTimeout))
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (member.game.assets) {
|
if (member.game.assets) {
|
||||||
if (
|
if (
|
||||||
member.game.assets.small_text &&
|
member.game.assets.small_text &&
|
||||||
(await pluginData.state.regexRunner.exec(queryRegex, member.game.assets.small_text).catch(allowTimeout))
|
(await execRegExp(queryRegex, member.game.assets.small_text).catch(allowTimeout))
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
member.game.assets.large_text &&
|
member.game.assets.large_text &&
|
||||||
(await pluginData.state.regexRunner.exec(queryRegex, member.game.assets.large_text).catch(allowTimeout))
|
(await execRegExp(queryRegex, member.game.assets.large_text).catch(allowTimeout))
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (member.game.emoji && (await execRegExp(queryRegex, member.game.emoji.name).catch(allowTimeout))) {
|
||||||
member.game.emoji &&
|
|
||||||
(await pluginData.state.regexRunner.exec(queryRegex, member.game.emoji.name).catch(allowTimeout))
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,12 +346,12 @@ async function performMemberSearch(
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
matchingMembers = await asyncFilter(matchingMembers, async member => {
|
matchingMembers = await asyncFilter(matchingMembers, async member => {
|
||||||
if (member.nick && (await pluginData.state.regexRunner.exec(queryRegex, member.nick).catch(allowTimeout))) {
|
if (member.nick && (await execRegExp(queryRegex, member.nick).catch(allowTimeout))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fullUsername = `${member.user.username}#${member.user.discriminator}`;
|
const fullUsername = `${member.user.username}#${member.user.discriminator}`;
|
||||||
if (await pluginData.state.regexRunner.exec(queryRegex, fullUsername).catch(allowTimeout)) return true;
|
if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -380,21 +396,29 @@ async function performBanSearch(
|
||||||
page = 1,
|
page = 1,
|
||||||
perPage = SEARCH_RESULTS_PER_PAGE,
|
perPage = SEARCH_RESULTS_PER_PAGE,
|
||||||
): Promise<{ results: User[]; totalResults: number; page: number; lastPage: number; from: number; to: number }> {
|
): Promise<{ results: User[]; totalResults: number; page: number; lastPage: number; from: number; to: number }> {
|
||||||
|
const member = pluginData.guild.members.get(pluginData.client.user.id);
|
||||||
|
if (member && !hasDiscordPermissions(member.permissions, Constants.Permissions.banMembers)) {
|
||||||
|
throw new SearchError(`Unable to search bans: missing "Ban Members" permission`);
|
||||||
|
}
|
||||||
|
|
||||||
let matchingBans = (await pluginData.guild.getBans()).map(x => x.user);
|
let matchingBans = (await pluginData.guild.getBans()).map(x => x.user);
|
||||||
|
|
||||||
if (args.query) {
|
if (args.query) {
|
||||||
|
let isSafeRegex = true;
|
||||||
let queryRegex: RegExp;
|
let queryRegex: RegExp;
|
||||||
if (args.regex) {
|
if (args.regex) {
|
||||||
const flags = args["case-sensitive"] ? "" : "i";
|
const flags = args["case-sensitive"] ? "" : "i";
|
||||||
queryRegex = inputPatternToRegExp(args.query.trimStart());
|
queryRegex = inputPatternToRegExp(args.query.trimStart());
|
||||||
queryRegex = new RegExp(queryRegex.source, flags);
|
queryRegex = new RegExp(queryRegex.source, flags);
|
||||||
|
isSafeRegex = false;
|
||||||
} else {
|
} else {
|
||||||
queryRegex = new RegExp(escapeStringRegexp(args.query.trimStart()), args["case-sensitive"] ? "" : "i");
|
queryRegex = new RegExp(escapeStringRegexp(args.query.trimStart()), args["case-sensitive"] ? "" : "i");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const execRegExp = getOptimizedRegExpRunner(pluginData, isSafeRegex);
|
||||||
matchingBans = await asyncFilter(matchingBans, async user => {
|
matchingBans = await asyncFilter(matchingBans, async user => {
|
||||||
const fullUsername = `${user.username}#${user.discriminator}`;
|
const fullUsername = `${user.username}#${user.discriminator}`;
|
||||||
if (await pluginData.state.regexRunner.exec(queryRegex, fullUsername).catch(allowTimeout)) return true;
|
if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {
|
||||||
TextChannel,
|
TextChannel,
|
||||||
User,
|
User,
|
||||||
} from "eris";
|
} from "eris";
|
||||||
import url from "url";
|
import { URL } from "url";
|
||||||
import tlds from "tlds";
|
import tlds from "tlds";
|
||||||
import emojiRegex from "emoji-regex";
|
import emojiRegex from "emoji-regex";
|
||||||
import * as t from "io-ts";
|
import * as t from "io-ts";
|
||||||
|
@ -481,7 +481,7 @@ const plainLinkRegex = /((?!https?:\/\/)\S)+\.\S+/; // anything.anything, withou
|
||||||
const urlRegex = new RegExp(`(${realLinkRegex.source}|${plainLinkRegex.source})`, "g");
|
const urlRegex = new RegExp(`(${realLinkRegex.source}|${plainLinkRegex.source})`, "g");
|
||||||
const protocolRegex = /^[a-z]+:\/\//;
|
const protocolRegex = /^[a-z]+:\/\//;
|
||||||
|
|
||||||
interface MatchedURL extends url.URL {
|
interface MatchedURL extends URL {
|
||||||
input: string;
|
input: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -496,7 +496,7 @@ export function getUrlsInString(str: string, onlyUnique = false): MatchedURL[] {
|
||||||
|
|
||||||
let matchUrl: MatchedURL;
|
let matchUrl: MatchedURL;
|
||||||
try {
|
try {
|
||||||
matchUrl = new url.URL(withProtocol) as MatchedURL;
|
matchUrl = new URL(withProtocol) as MatchedURL;
|
||||||
matchUrl.input = match;
|
matchUrl.input = match;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return urls;
|
return urls;
|
||||||
|
@ -520,9 +520,61 @@ export function parseInviteCodeInput(str: string): string {
|
||||||
return getInviteCodesInString(str)[0];
|
return getInviteCodesInString(str)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isNotNull(value): value is Exclude<typeof value, null> {
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// discord.com/invite/<code>
|
||||||
|
// discordapp.com/invite/<code>
|
||||||
|
// discord.gg/invite/<code>
|
||||||
|
// discord.gg/<code>
|
||||||
|
const quickInviteDetection = /(?:discord.com|discordapp.com)\/invite\/([^\s\/#?]+)|discord.gg\/(?:\S+\/)?([^\s\/#?]+)/gi;
|
||||||
|
|
||||||
|
const isInviteHostRegex = /(?:^|\.)(?:discord.gg|discord.com|discordapp.com)$/;
|
||||||
|
const longInvitePathRegex = /^\/invite\/([^\s\/]+)$/;
|
||||||
|
|
||||||
export function getInviteCodesInString(str: string): string[] {
|
export function getInviteCodesInString(str: string): string[] {
|
||||||
const inviteCodeRegex = /(?:discord.gg|discordapp.com\/invite|discord.com\/invite)\/([a-z0-9\-]+)/gi;
|
const inviteCodes: string[] = [];
|
||||||
return Array.from(str.matchAll(inviteCodeRegex)).map(m => m[1]);
|
|
||||||
|
// Clean up markdown
|
||||||
|
str = str.replace(/[|*_~]/g, "");
|
||||||
|
|
||||||
|
// Quick detection
|
||||||
|
const quickDetectionMatch = str.matchAll(quickInviteDetection);
|
||||||
|
if (quickDetectionMatch) {
|
||||||
|
inviteCodes.push(...[...quickDetectionMatch].map(m => m[1] || m[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep detection via URL parsing
|
||||||
|
const linksInString = getUrlsInString(str, true);
|
||||||
|
const potentialInviteLinks = linksInString.filter(url => isInviteHostRegex.test(url.hostname));
|
||||||
|
const withNormalizedPaths = potentialInviteLinks.map(url => {
|
||||||
|
url.pathname = url.pathname.replace(/\/{2,}/g, "/").replace(/\/+$/g, "");
|
||||||
|
return url;
|
||||||
|
});
|
||||||
|
|
||||||
|
const codesFromInviteLinks = withNormalizedPaths
|
||||||
|
.map(url => {
|
||||||
|
// discord.gg/[anything/]<code>
|
||||||
|
if (url.hostname === "discord.gg") {
|
||||||
|
const parts = url.pathname.split("/").filter(Boolean);
|
||||||
|
return parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// discord.com/invite/<code>[/anything]
|
||||||
|
// discordapp.com/invite/<code>[/anything]
|
||||||
|
const longInviteMatch = url.pathname.match(longInvitePathRegex);
|
||||||
|
if (longInviteMatch) {
|
||||||
|
return longInviteMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
inviteCodes.push(...codesFromInviteLinks);
|
||||||
|
|
||||||
|
return unique(inviteCodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unicodeEmojiRegex = emojiRegex();
|
export const unicodeEmojiRegex = emojiRegex();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue