3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-05-10 12:25:02 +00:00

Turn on strict TS compilation. Fix up and tweak types accordingly.

This commit is contained in:
Dragory 2020-11-09 20:03:57 +02:00
parent 690955a399
commit 629002b8d9
No known key found for this signature in database
GPG key ID: 5F387BA66DF8AAC1
172 changed files with 720 additions and 534 deletions

View file

@ -4,6 +4,7 @@ import { tDelayString, MINUTES } from "../../utils";
import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { SavedMessage } from "../../data/entities/SavedMessage";
import Timeout = NodeJS.Timeout;
export const MAX_DELAY = 5 * MINUTES;
@ -25,8 +26,8 @@ export interface AutoDeletePluginType extends BasePluginType {
guildLogs: GuildLogs;
deletionQueue: IDeletionQueueItem[];
nextDeletion: number;
nextDeletionTimeout;
nextDeletion: number | null;
nextDeletionTimeout: Timeout | null;
maxDelayWarningSent: boolean;

View file

@ -9,7 +9,7 @@ export async function onMessageCreate(pluginData: GuildPluginData<AutoDeletePlug
const member = await resolveMember(pluginData.client, pluginData.guild, msg.user_id);
const config = pluginData.config.getMatchingConfig({ member, channelId: msg.channel_id });
if (config.enabled) {
let delay = convertDelayStringToMS(config.delay);
let delay = convertDelayStringToMS(config.delay)!;
if (delay > MAX_DELAY) {
delay = MAX_DELAY;

View file

@ -4,11 +4,11 @@ import { deleteNextItem } from "./deleteNextItem";
export function scheduleNextDeletion(pluginData: GuildPluginData<AutoDeletePluginType>) {
if (pluginData.state.deletionQueue.length === 0) {
clearTimeout(pluginData.state.nextDeletionTimeout);
clearTimeout(pluginData.state.nextDeletionTimeout!);
return;
}
const firstDeleteAt = pluginData.state.deletionQueue[0].deleteAt;
clearTimeout(pluginData.state.nextDeletionTimeout);
clearTimeout(pluginData.state.nextDeletionTimeout!);
pluginData.state.nextDeletionTimeout = setTimeout(() => deleteNextItem(pluginData), firstDeleteAt - Date.now());
}

View file

@ -20,9 +20,9 @@ export const NewAutoReactionsCmd = autoReactionsCmd({
},
async run({ message: msg, args, pluginData }) {
const finalReactions = [];
const finalReactions: string[] = [];
const me = pluginData.guild.members.get(pluginData.client.user.id);
const me = pluginData.guild.members.get(pluginData.client.user.id)!;
const missingPermissions = getMissingChannelPermissions(me, args.channel as GuildChannel, requiredPermissions);
if (missingPermissions) {
sendErrorMessage(

View file

@ -19,7 +19,7 @@ export const AddReactionsEvt = autoReactionsEvt({
const autoReaction = await pluginData.state.autoReactions.getForChannel(message.channel.id);
if (!autoReaction) return;
const me = pluginData.guild.members.get(pluginData.client.user.id);
const me = pluginData.guild.members.get(pluginData.client.user.id)!;
const missingPermissions = getMissingChannelPermissions(
me,
message.channel as GuildChannel,

View file

@ -158,7 +158,7 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()("automod",
configPreprocessor,
customOverrideMatcher(pluginData, criteria, matchParams) {
return criteria?.antiraid_level && criteria.antiraid_level === pluginData.state.cachedAntiraidLevel;
return criteria?.antiraid_level ? criteria.antiraid_level === pluginData.state.cachedAntiraidLevel : false;
},
events: [

View file

@ -1,7 +1,7 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { unique } from "../../../utils";
import { nonNullish, unique } from "../../../utils";
import { Constants } from "eris";
import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions";
import { LogsPlugin } from "../../Logs/LogsPlugin";
@ -17,8 +17,8 @@ export const AddRolesAction = automodAction({
defaultConfig: [],
async apply({ pluginData, contexts, actionConfig, ruleName }) {
const members = unique(contexts.map(c => c.member).filter(Boolean));
const me = pluginData.guild.members.get(pluginData.client.user.id);
const members = unique(contexts.map(c => c.member).filter(nonNullish));
const me = pluginData.guild.members.get(pluginData.client.user.id)!;
const missingPermissions = getMissingPermissions(me.permission, p.manageRoles);
if (missingPermissions) {
@ -29,8 +29,8 @@ export const AddRolesAction = automodAction({
return;
}
const rolesToAssign = [];
const rolesWeCannotAssign = [];
const rolesToAssign: string[] = [];
const rolesWeCannotAssign: string[] = [];
for (const roleId of actionConfig) {
if (canAssignRole(pluginData.guild, me, roleId)) {
rolesToAssign.push(roleId);

View file

@ -1,7 +1,7 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { asyncMap, resolveMember, tNullable, unique } from "../../../utils";
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
@ -20,14 +20,14 @@ export const BanAction = automodAction({
async apply({ pluginData, contexts, actionConfig, matchResult }) {
const reason = actionConfig.reason || "Kicked automatically";
const contactMethods = resolveActionContactMethods(pluginData, actionConfig);
const deleteMessageDays = actionConfig.deleteMessageDays;
const deleteMessageDays = actionConfig.deleteMessageDays || undefined;
const caseArgs = {
modId: pluginData.client.user.id,
extraNotes: [matchResult.fullSummary],
extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [],
};
const userIdsToBan = unique(contexts.map(c => c.user?.id).filter(Boolean));
const userIdsToBan = unique(contexts.map(c => c.user?.id).filter(nonNullish));
const modActions = pluginData.getPlugin(ModActionsPlugin);
for (const userId of userIdsToBan) {

View file

@ -2,7 +2,7 @@ import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { unique } from "../../../utils";
import { nonNullish, unique } from "../../../utils";
export const ChangeNicknameAction = automodAction({
configType: t.union([
@ -15,7 +15,7 @@ export const ChangeNicknameAction = automodAction({
defaultConfig: {},
async apply({ pluginData, contexts, actionConfig }) {
const members = unique(contexts.map(c => c.member).filter(Boolean));
const members = unique(contexts.map(c => c.member).filter(nonNullish));
for (const member of members) {
if (pluginData.state.recentNicknameChanges.has(member.id)) continue;

View file

@ -15,12 +15,12 @@ export const CleanAction = automodAction({
messageIdsToDeleteByChannelId.set(context.message.channel_id, []);
}
if (messageIdsToDeleteByChannelId.get(context.message.channel_id).includes(context.message.id)) {
if (messageIdsToDeleteByChannelId.get(context.message.channel_id)!.includes(context.message.id)) {
console.warn(`Message ID to delete was already present: ${pluginData.guild.name}, rule ${ruleName}`);
continue;
}
messageIdsToDeleteByChannelId.get(context.message.channel_id).push(context.message.id);
messageIdsToDeleteByChannelId.get(context.message.channel_id)!.push(context.message.id);
}
}

View file

@ -1,7 +1,7 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { asyncMap, resolveMember, tNullable, unique } from "../../../utils";
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
@ -22,14 +22,15 @@ export const KickAction = automodAction({
const caseArgs = {
modId: pluginData.client.user.id,
extraNotes: [matchResult.fullSummary],
extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [],
};
const userIdsToKick = unique(contexts.map(c => c.user?.id).filter(Boolean));
const userIdsToKick = unique(contexts.map(c => c.user?.id).filter(nonNullish));
const membersToKick = await asyncMap(userIdsToKick, id => resolveMember(pluginData.client, pluginData.guild, id));
const modActions = pluginData.getPlugin(ModActionsPlugin);
for (const member of membersToKick) {
if (!member) continue;
await modActions.kickMember(member, reason, { contactMethods, caseArgs });
}
},

View file

@ -1,7 +1,15 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { asyncMap, convertDelayStringToMS, resolveMember, tDelayString, tNullable, unique } from "../../../utils";
import {
asyncMap,
convertDelayStringToMS,
nonNullish,
resolveMember,
tDelayString,
tNullable,
unique,
} from "../../../utils";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { MutesPlugin } from "../../Mutes/MutesPlugin";
@ -21,16 +29,16 @@ export const MuteAction = automodAction({
},
async apply({ pluginData, contexts, actionConfig, ruleName, matchResult }) {
const duration = actionConfig.duration ? convertDelayStringToMS(actionConfig.duration) : null;
const duration = actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : undefined;
const reason = actionConfig.reason || "Muted automatically";
const contactMethods = resolveActionContactMethods(pluginData, actionConfig);
const caseArgs = {
modId: pluginData.client.user.id,
extraNotes: [matchResult.fullSummary],
extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [],
};
const userIdsToMute = unique(contexts.map(c => c.user?.id).filter(Boolean));
const userIdsToMute = unique(contexts.map(c => c.user?.id).filter(nonNullish));
const mutes = pluginData.getPlugin(MutesPlugin);
for (const userId of userIdsToMute) {

View file

@ -1,7 +1,7 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { asyncMap, resolveMember, tNullable, unique } from "../../../utils";
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
@ -19,8 +19,8 @@ export const RemoveRolesAction = automodAction({
defaultConfig: [],
async apply({ pluginData, contexts, actionConfig, ruleName }) {
const members = unique(contexts.map(c => c.member).filter(Boolean));
const me = pluginData.guild.members.get(pluginData.client.user.id);
const members = unique(contexts.map(c => c.member).filter(nonNullish));
const me = pluginData.guild.members.get(pluginData.client.user.id)!;
const missingPermissions = getMissingPermissions(me.permission, p.manageRoles);
if (missingPermissions) {
@ -31,8 +31,8 @@ export const RemoveRolesAction = automodAction({
return;
}
const rolesToRemove = [];
const rolesWeCannotRemove = [];
const rolesToRemove: string[] = [];
const rolesWeCannotRemove: string[] = [];
for (const roleId of actionConfig) {
if (canAssignRole(pluginData.guild, me, roleId)) {
rolesToRemove.push(roleId);

View file

@ -28,14 +28,14 @@ export const ReplyAction = automodAction({
async apply({ pluginData, contexts, actionConfig }) {
const contextsWithTextChannels = contexts
.filter(c => c.message?.channel_id)
.filter(c => pluginData.guild.channels.get(c.message.channel_id) instanceof TextChannel);
.filter(c => pluginData.guild.channels.get(c.message!.channel_id) instanceof TextChannel);
const contextsByChannelId = contextsWithTextChannels.reduce((map: Map<string, AutomodContext[]>, context) => {
if (!map.has(context.message.channel_id)) {
map.set(context.message.channel_id, []);
if (!map.has(context.message!.channel_id)) {
map.set(context.message!.channel_id, []);
}
map.get(context.message.channel_id).push(context);
map.get(context.message!.channel_id)!.push(context);
return map;
}, new Map());
@ -57,7 +57,7 @@ export const ReplyAction = automodAction({
const replyMsg = await channel.createMessage(formatted);
if (typeof actionConfig === "object" && actionConfig.auto_delete) {
const delay = convertDelayStringToMS(String(actionConfig.auto_delete));
const delay = convertDelayStringToMS(String(actionConfig.auto_delete))!;
setTimeout(() => replyMsg.delete().catch(noop), delay);
}
}

View file

@ -4,7 +4,7 @@ import { setAntiraidLevel } from "../functions/setAntiraidLevel";
export const SetAntiraidLevelAction = automodAction({
configType: t.string,
defaultConfig: null,
defaultConfig: "",
async apply({ pluginData, contexts, actionConfig }) {
setAntiraidLevel(pluginData, actionConfig);

View file

@ -1,7 +1,7 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { asyncMap, resolveMember, tNullable, unique } from "../../../utils";
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
@ -22,14 +22,15 @@ export const WarnAction = automodAction({
const caseArgs = {
modId: pluginData.client.user.id,
extraNotes: [matchResult.fullSummary],
extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [],
};
const userIdsToWarn = unique(contexts.map(c => c.user?.id).filter(Boolean));
const userIdsToWarn = unique(contexts.map(c => c.user?.id).filter(nonNullish));
const membersToWarn = await asyncMap(userIdsToWarn, id => resolveMember(pluginData.client, pluginData.guild, id));
const modActions = pluginData.getPlugin(ModActionsPlugin);
for (const member of membersToWarn) {
if (!member) continue;
await modActions.warnMember(member, reason, { contactMethods, caseArgs });
}
},

View file

@ -4,8 +4,9 @@ import { RECENT_ACTION_EXPIRY_TIME, RecentActionType } from "../constants";
import { getEmojiInString, getRoleMentions, getUrlsInString, getUserMentions } from "../../../utils";
export function addRecentActionsFromMessage(pluginData: GuildPluginData<AutomodPluginType>, context: AutomodContext) {
const globalIdentifier = context.message.user_id;
const perChannelIdentifier = `${context.message.channel_id}-${context.message.user_id}`;
const message = context.message!;
const globalIdentifier = message.user_id;
const perChannelIdentifier = `${message.channel_id}-${message.user_id}`;
const expiresAt = Date.now() + RECENT_ACTION_EXPIRY_TIME;
pluginData.state.recentActions.push({
@ -23,8 +24,7 @@ export function addRecentActionsFromMessage(pluginData: GuildPluginData<AutomodP
});
const mentionCount =
getUserMentions(context.message.data.content || "").length +
getRoleMentions(context.message.data.content || "").length;
getUserMentions(message.data.content || "").length + getRoleMentions(message.data.content || "").length;
if (mentionCount) {
pluginData.state.recentActions.push({
context,
@ -41,7 +41,7 @@ export function addRecentActionsFromMessage(pluginData: GuildPluginData<AutomodP
});
}
const linkCount = getUrlsInString(context.message.data.content || "").length;
const linkCount = getUrlsInString(message.data.content || "").length;
if (linkCount) {
pluginData.state.recentActions.push({
context,
@ -58,7 +58,7 @@ export function addRecentActionsFromMessage(pluginData: GuildPluginData<AutomodP
});
}
const attachmentCount = context.message.data.attachments && context.message.data.attachments.length;
const attachmentCount = message.data.attachments && message.data.attachments.length;
if (attachmentCount) {
pluginData.state.recentActions.push({
context,
@ -75,7 +75,7 @@ export function addRecentActionsFromMessage(pluginData: GuildPluginData<AutomodP
});
}
const emojiCount = getEmojiInString(context.message.data.content || "").length;
const emojiCount = getEmojiInString(message.data.content || "").length;
if (emojiCount) {
pluginData.state.recentActions.push({
context,
@ -93,7 +93,7 @@ export function addRecentActionsFromMessage(pluginData: GuildPluginData<AutomodP
}
// + 1 is for the first line of the message (which doesn't have a line break)
const lineCount = context.message.data.content ? (context.message.data.content.match(/\n/g) || []).length + 1 : 0;
const lineCount = message.data.content ? (message.data.content.match(/\n/g) || []).length + 1 : 0;
if (lineCount) {
pluginData.state.recentActions.push({
context,
@ -110,7 +110,7 @@ export function addRecentActionsFromMessage(pluginData: GuildPluginData<AutomodP
});
}
const characterCount = [...(context.message.data.content || "")].length;
const characterCount = [...(message.data.content || "")].length;
if (characterCount) {
pluginData.state.recentActions.push({
context,
@ -127,7 +127,7 @@ export function addRecentActionsFromMessage(pluginData: GuildPluginData<AutomodP
});
}
const stickerCount = (context.message.data.stickers || []).length;
const stickerCount = (message.data.stickers || []).length;
if (stickerCount) {
pluginData.state.recentActions.push({
context,

View file

@ -4,8 +4,9 @@ import { RECENT_ACTION_EXPIRY_TIME, RecentActionType } from "../constants";
import { getEmojiInString, getRoleMentions, getUrlsInString, getUserMentions } from "../../../utils";
export function clearRecentActionsForMessage(pluginData: GuildPluginData<AutomodPluginType>, context: AutomodContext) {
const globalIdentifier = context.message.user_id;
const perChannelIdentifier = `${context.message.channel_id}-${context.message.user_id}`;
const message = context.message!;
const globalIdentifier = message.user_id;
const perChannelIdentifier = `${message.channel_id}-${message.user_id}`;
pluginData.state.recentActions = pluginData.state.recentActions.filter(act => {
return act.identifier !== globalIdentifier && act.identifier !== perChannelIdentifier;

View file

@ -7,6 +7,7 @@ import { findRecentSpam } from "./findRecentSpam";
import { getMatchingMessageRecentActions } from "./getMatchingMessageRecentActions";
import * as t from "io-ts";
import { getMessageSpamIdentifier } from "./getSpamIdentifier";
import { SavedMessage } from "../../../data/entities/SavedMessage";
const MessageSpamTriggerConfig = t.type({
amount: t.number,
@ -29,22 +30,25 @@ export function createMessageSpamTrigger(spamType: RecentActionType, prettyName:
return;
}
const spamIdentifier = getMessageSpamIdentifier(context.message, triggerConfig.per_channel);
const spamIdentifier = getMessageSpamIdentifier(context.message, Boolean(triggerConfig.per_channel));
const recentSpam = findRecentSpam(pluginData, spamType, spamIdentifier);
if (recentSpam) {
await pluginData.state.archives.addSavedMessagesToArchive(
recentSpam.archiveId,
[context.message],
pluginData.guild,
);
if (recentSpam.archiveId) {
await pluginData.state.archives.addSavedMessagesToArchive(
recentSpam.archiveId,
[context.message],
pluginData.guild,
);
}
return {
silentClean: true,
extra: { archiveId: "" }, // FIXME: Fix up automod trigger match() typings so extra is not required when doing a silentClean
};
}
const within = convertDelayStringToMS(triggerConfig.within);
const within = convertDelayStringToMS(triggerConfig.within) ?? 0;
const matchedSpam = getMatchingMessageRecentActions(
pluginData,
context.message,
@ -58,7 +62,7 @@ export function createMessageSpamTrigger(spamType: RecentActionType, prettyName:
const messages = matchedSpam.recentActions
.map(action => action.context.message)
.filter(Boolean)
.sort(sorter("posted_at"));
.sort(sorter("posted_at")) as SavedMessage[];
const archiveId = await pluginData.state.archives.createFromSavedMessages(messages, pluginData.guild);

View file

@ -16,7 +16,7 @@ export function getMatchingRecentActions(
action.type === type &&
(!identifier || action.identifier === identifier) &&
action.context.timestamp >= since &&
action.context.timestamp <= to &&
action.context.timestamp <= to! &&
!action.context.actioned
);
});

View file

@ -10,23 +10,25 @@ export function getTextMatchPartialSummary(
context: AutomodContext,
) {
if (type === "message") {
const channel = pluginData.guild.channels.get(context.message.channel_id);
const channelMention = channel ? verboseChannelMention(channel) : `\`#${context.message.channel_id}\``;
const message = context.message!;
const channel = pluginData.guild.channels.get(message.channel_id);
const channelMention = channel ? verboseChannelMention(channel) : `\`#${message.channel_id}\``;
return `message in ${channelMention}:\n${messageSummary(context.message)}`;
return `message in ${channelMention}:\n${messageSummary(message)}`;
} else if (type === "embed") {
const channel = pluginData.guild.channels.get(context.message.channel_id);
const channelMention = channel ? verboseChannelMention(channel) : `\`#${context.message.channel_id}\``;
const message = context.message!;
const channel = pluginData.guild.channels.get(message.channel_id);
const channelMention = channel ? verboseChannelMention(channel) : `\`#${message.channel_id}\``;
return `message embed in ${channelMention}:\n${messageSummary(context.message)}`;
return `message embed in ${channelMention}:\n${messageSummary(message)}`;
} else if (type === "username") {
return `username: ${context.user.username}`;
return `username: ${context.user!.username}`;
} else if (type === "nickname") {
return `nickname: ${context.member.nick}`;
return `nickname: ${context.member!.nick}`;
} else if (type === "visiblename") {
const visibleName = context.member?.nick || context.user.username;
const visibleName = context.member?.nick || context.user!.username;
return `visible name: ${visibleName}`;
} else if (type === "customstatus") {
return `custom status: ${context.member.game.state}`;
return `custom status: ${context.member!.game!.state}`;
}
}

View file

@ -7,10 +7,10 @@ import { AutomodPluginType } from "../types";
export function resolveActionContactMethods(
pluginData: GuildPluginData<AutomodPluginType>,
actionConfig: {
notify?: string;
notifyChannel?: string;
notify?: string | null;
notifyChannel?: string | null;
},
): UserNotificationMethod[] | null {
): UserNotificationMethod[] {
if (actionConfig.notify === "dm") {
return [{ type: "dm" }];
} else if (actionConfig.notify === "channel") {
@ -28,5 +28,5 @@ export function resolveActionContactMethods(
return [];
}
return null;
return [];
}

View file

@ -5,13 +5,14 @@ import { availableActions } from "../actions/availableActions";
import { AutomodTriggerMatchResult } from "../helpers";
import { CleanAction } from "../actions/clean";
import { checkAndUpdateCooldown } from "./checkAndUpdateCooldown";
import { TextChannel } from "eris";
export async function runAutomod(pluginData: GuildPluginData<AutomodPluginType>, context: AutomodContext) {
const userId = context.user?.id || context.member?.id || context.message?.user_id;
const user = context.user || (userId && pluginData.client.users.get(userId));
const member = context.member || (userId && pluginData.guild.members.get(userId));
const member = context.member || (userId && pluginData.guild.members.get(userId)) || null;
const channelId = context.message?.channel_id;
const channel = channelId && pluginData.guild.channels.get(channelId);
const channel = channelId ? (pluginData.guild.channels.get(channelId) as TextChannel) : null;
const categoryId = channel?.parentID;
const config = pluginData.config.getMatchingConfig({
@ -29,8 +30,8 @@ export async function runAutomod(pluginData: GuildPluginData<AutomodPluginType>,
return;
}
let matchResult: AutomodTriggerMatchResult<any>;
let contexts: AutomodContext[];
let matchResult: AutomodTriggerMatchResult<any> | null | undefined;
let contexts: AutomodContext[] = [];
triggerLoop: for (const triggerItem of rule.triggers) {
for (const [triggerName, triggerConfig] of Object.entries(triggerItem)) {

View file

@ -3,9 +3,8 @@ import { Awaitable } from "knub/dist/utils";
import * as t from "io-ts";
import { AutomodContext, AutomodPluginType } from "./types";
export interface AutomodTriggerMatchResult<TExtra extends any = unknown> {
interface BaseAutomodTriggerMatchResult {
extraContexts?: AutomodContext[];
extra?: TExtra;
silentClean?: boolean; // TODO: Maybe generalize to a "silent" value in general, which mutes alert/log
@ -13,12 +12,16 @@ export interface AutomodTriggerMatchResult<TExtra extends any = unknown> {
fullSummary?: string;
}
export type AutomodTriggerMatchResult<TExtra extends any = unknown> = unknown extends TExtra
? BaseAutomodTriggerMatchResult
: BaseAutomodTriggerMatchResult & { extra: TExtra };
type AutomodTriggerMatchFn<TConfigType, TMatchResultExtra> = (meta: {
ruleName: string;
pluginData: GuildPluginData<AutomodPluginType>;
context: AutomodContext;
triggerConfig: TConfigType;
}) => Awaitable<null | AutomodTriggerMatchResult<TMatchResultExtra>>;
}) => Awaitable<null | undefined | AutomodTriggerMatchResult<TMatchResultExtra>>;
type AutomodTriggerRenderMatchInformationFn<TConfigType, TMatchResultExtra> = (meta: {
ruleName: string;

View file

@ -73,15 +73,15 @@ export const MatchAttachmentTypeTrigger = automodTrigger<MatchResultType>()({
},
renderMatchInformation({ pluginData, contexts, matchResult }) {
const channel = pluginData.guild.channels.get(contexts[0].message.channel_id);
const channel = pluginData.guild.channels.get(contexts[0].message!.channel_id)!;
const prettyChannel = verboseChannelMention(channel);
return (
asSingleLine(`
Matched attachment type \`${disableInlineCode(matchResult.extra.matchedType)}\`
(${matchResult.extra.mode === "blacklist" ? "(blacklisted)" : "(not in whitelist)"})
in message (\`${contexts[0].message.id}\`) in ${prettyChannel}:
`) + messageSummary(contexts[0].message)
in message (\`${contexts[0].message!.id}\`) in ${prettyChannel}:
`) + messageSummary(contexts[0].message!)
);
},
});

View file

@ -1,10 +1,10 @@
import * as t from "io-ts";
import { GuildInvite } from "eris";
import { automodTrigger } from "../helpers";
import {
disableCodeBlocks,
disableInlineCode,
getInviteCodesInString,
GuildInvite,
isGuildInvite,
resolveInvite,
tNullable,

View file

@ -19,7 +19,7 @@ export const MemberJoinTrigger = automodTrigger<unknown>()({
}
if (triggerConfig.only_new) {
const threshold = Date.now() - convertDelayStringToMS(triggerConfig.new_threshold);
const threshold = Date.now() - convertDelayStringToMS(triggerConfig.new_threshold)!;
return context.member.createdAt >= threshold ? {} : null;
}
@ -27,6 +27,6 @@ export const MemberJoinTrigger = automodTrigger<unknown>()({
},
renderMatchInformation({ pluginData, contexts, triggerConfig }) {
return null;
return "";
},
});

View file

@ -25,7 +25,7 @@ export const MemberJoinSpamTrigger = automodTrigger<unknown>()({
return {};
}
const since = Date.now() - convertDelayStringToMS(triggerConfig.within);
const since = Date.now() - convertDelayStringToMS(triggerConfig.within)!;
const matchingActions = getMatchingRecentActions(pluginData, RecentActionType.MemberJoin, null, since);
const totalCount = sumRecentActionCounts(matchingActions);
@ -46,6 +46,6 @@ export const MemberJoinSpamTrigger = automodTrigger<unknown>()({
},
renderMatchInformation({ pluginData, contexts, triggerConfig }) {
return null;
return "";
},
});

View file

@ -9,16 +9,16 @@ interface RoleAddedMatchResult {
export const RoleAddedTrigger = automodTrigger<RoleAddedMatchResult>()({
configType: t.union([t.string, t.array(t.string)]),
defaultConfig: null,
defaultConfig: [],
async match({ triggerConfig, context, pluginData }) {
if (!context.member || !context.rolesChanged || context.rolesChanged.added.length === 0) {
if (!context.member || !context.rolesChanged || context.rolesChanged.added!.length === 0) {
return;
}
const triggerRoles = Array.isArray(triggerConfig) ? triggerConfig : [triggerConfig];
for (const roleId of triggerRoles) {
if (context.rolesChanged.added.includes(roleId)) {
if (context.rolesChanged.added!.includes(roleId)) {
if (consumeIgnoredRoleChange(pluginData, context.member.id, roleId)) {
continue;
}

View file

@ -9,10 +9,10 @@ interface RoleAddedMatchResult {
export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({
configType: t.union([t.string, t.array(t.string)]),
defaultConfig: null,
defaultConfig: [],
async match({ triggerConfig, context, pluginData }) {
if (!context.member || !context.rolesChanged || context.rolesChanged.removed.length === 0) {
if (!context.member || !context.rolesChanged || context.rolesChanged.removed!.length === 0) {
return;
}
@ -22,7 +22,7 @@ export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({
continue;
}
if (context.rolesChanged.removed.includes(roleId)) {
if (context.rolesChanged.removed!.includes(roleId)) {
return {
extra: {
matchedRoleId: roleId,

View file

@ -105,13 +105,13 @@ export interface AutomodContext {
export interface RecentAction {
type: RecentActionType;
identifier: string;
identifier: string | null;
count: number;
context: AutomodContext;
}
export interface RecentSpam {
archiveId: string;
archiveId: string | null;
type: RecentActionType;
identifiers: string[];
timestamp: number;

View file

@ -45,8 +45,9 @@ export const BotControlPlugin = zeppelinGlobalPlugin<BotControlPluginType>()("bo
pluginData.state.configs = new Configs();
pluginData.state.apiPermissionAssignments = new ApiPermissionAssignments();
if (getActiveReload()) {
const [guildId, channelId] = getActiveReload();
const activeReload = getActiveReload();
if (activeReload) {
const [guildId, channelId] = activeReload;
resetActiveReload();
const guild = pluginData.client.guilds.get(guildId);

View file

@ -1,4 +1,4 @@
let activeReload: [string, string] = null;
let activeReload: [string, string] | null = null;
export function getActiveReload() {
return activeReload;

View file

@ -19,7 +19,7 @@ export const LeaveServerCmd = botControlCmd({
return;
}
const guildToLeave = pluginData.client.guilds.get(args.guildId);
const guildToLeave = pluginData.client.guilds.get(args.guildId)!;
const guildName = guildToLeave.name;
try {

View file

@ -21,7 +21,7 @@ export const ServersCmd = botControlCmd({
async run({ pluginData, message: msg, args }) {
const showList = Boolean(args.all || args.initialized || args.uninitialized || args.search);
const search = args.search && new RegExp([...args.search].map(s => escapeStringRegexp(s)).join(".*"), "i");
const search = args.search ? new RegExp([...args.search].map(s => escapeStringRegexp(s)).join(".*"), "i") : null;
const joinedGuilds = Array.from(pluginData.client.guilds.values());
const loadedGuilds = pluginData.getKnubInstance().getLoadedGuilds();
@ -39,7 +39,7 @@ export const ServersCmd = botControlCmd({
}
if (args.search) {
filteredGuilds = filteredGuilds.filter(g => search.test(`${g.id} ${g.name}`));
filteredGuilds = filteredGuilds.filter(g => search!.test(`${g.id} ${g.name}`));
}
if (filteredGuilds.length) {

View file

@ -12,7 +12,7 @@ export async function createCase(pluginData: GuildPluginData<CasesPluginType>, a
const mod = await resolveUser(pluginData.client, args.modId);
const modName = `${mod.username}#${mod.discriminator}`;
let ppName = null;
let ppName: string | null = null;
if (args.ppId) {
const pp = await resolveUser(pluginData.client, args.ppId);
ppName = `${pp.username}#${pp.discriminator}`;

View file

@ -15,7 +15,9 @@ export async function getCaseEmbed(
requestMemberId?: string,
): Promise<AdvancedMessageContent> {
const theCase = await pluginData.state.cases.with("notes").find(resolveCaseId(caseOrCaseId));
if (!theCase) return null;
if (!theCase) {
throw new Error("Unknown case");
}
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);

View file

@ -28,12 +28,13 @@ export async function getCaseSummary(
caseOrCaseId: Case | number,
withLinks = false,
requestMemberId?: string,
) {
): Promise<string | null> {
const config = pluginData.config.get();
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
const caseId = caseOrCaseId instanceof Case ? caseOrCaseId.id : caseOrCaseId;
const theCase = await pluginData.state.cases.with("notes").find(caseId);
if (!theCase) return null;
const firstNote = theCase.notes[0];
let reason = firstNote ? firstNote.body : "";
@ -47,7 +48,7 @@ export async function getCaseSummary(
if (reason.length > CASE_SUMMARY_REASON_MAX_LENGTH) {
const match = reason.slice(CASE_SUMMARY_REASON_MAX_LENGTH, 100).match(/(?:[.,!?\s]|$)/);
const nextWhitespaceIndex = match ? CASE_SUMMARY_REASON_MAX_LENGTH + match.index : CASE_SUMMARY_REASON_MAX_LENGTH;
const nextWhitespaceIndex = match ? CASE_SUMMARY_REASON_MAX_LENGTH + match.index! : CASE_SUMMARY_REASON_MAX_LENGTH;
if (nextWhitespaceIndex < reason.length) {
reason = reason.slice(0, nextWhitespaceIndex - 1) + "...";
}
@ -56,7 +57,7 @@ export async function getCaseSummary(
reason = disableLinkPreviews(reason);
const timestamp = moment.utc(theCase.created_at, DBDateFormat);
const relativeTimeCutoff = convertDelayStringToMS(config.relative_time_cutoff);
const relativeTimeCutoff = convertDelayStringToMS(config.relative_time_cutoff)!;
const useRelativeTime = config.show_relative_times && Date.now() - timestamp.valueOf() < relativeTimeCutoff;
const timestampWithTz = requestMemberId
? await timeAndDate.inMemberTz(requestMemberId, timestamp)

View file

@ -11,13 +11,13 @@ import { logger } from "../../../logger";
export async function postToCaseLogChannel(
pluginData: GuildPluginData<CasesPluginType>,
content: MessageContent,
file: MessageFile = null,
file?: MessageFile,
): Promise<Message | null> {
const caseLogChannelId = pluginData.config.get().case_log_channel;
if (!caseLogChannelId) return;
if (!caseLogChannelId) return null;
const caseLogChannel = pluginData.guild.channels.get(caseLogChannelId);
if (!caseLogChannel || !(caseLogChannel instanceof TextChannel)) return;
if (!caseLogChannel || !(caseLogChannel instanceof TextChannel)) return null;
let result;
try {
@ -30,7 +30,7 @@ export async function postToCaseLogChannel(
pluginData.state.logs.log(LogType.BOT_ALERT, {
body: `Missing permissions to post mod cases in <#${caseLogChannel.id}>`,
});
return;
return null;
}
throw e;
@ -44,17 +44,17 @@ export async function postCaseToCaseLogChannel(
caseOrCaseId: Case | number,
): Promise<Message | null> {
const theCase = await pluginData.state.cases.find(resolveCaseId(caseOrCaseId));
if (!theCase) return;
if (!theCase) return null;
const caseEmbed = await getCaseEmbed(pluginData, caseOrCaseId);
if (!caseEmbed) return;
if (!caseEmbed) return null;
if (theCase.log_message_id) {
const [channelId, messageId] = theCase.log_message_id.split("-");
try {
await pluginData.client.editMessage(channelId, messageId, caseEmbed);
return;
return null;
} catch (e) {} // tslint:disable-line:no-empty
}

View file

@ -1,16 +1,9 @@
import { GuildPluginData } from "knub";
import { CensorPluginType } from "../types";
import { SavedMessage } from "../../../data/entities/SavedMessage";
import { AnyInvite, Embed, GuildInvite } from "eris";
import { Embed, Invite } from "eris";
import { ZalgoRegex } from "../../../data/Zalgo";
import {
getInviteCodesInString,
getUrlsInString,
resolveMember,
resolveInvite,
isGuildInvite,
isRESTGuildInvite,
} from "../../../utils";
import { getInviteCodesInString, getUrlsInString, resolveMember, resolveInvite, isGuildInvite } from "../../../utils";
import cloneDeep from "lodash.clonedeep";
import { censorMessage } from "./censorMessage";
import escapeStringRegexp from "escape-string-regexp";
@ -59,7 +52,7 @@ export async function applyFiltersToMsg(
const inviteCodes = getInviteCodesInString(messageContent);
const invites: Array<AnyInvite | null> = await Promise.all(
const invites: Array<Invite | null> = await Promise.all(
inviteCodes.map(code => resolveInvite(pluginData.client, code)),
);
@ -75,7 +68,7 @@ export async function applyFiltersToMsg(
return true;
}
if (isRESTGuildInvite(invite)) {
if (isGuildInvite(invite)) {
if (inviteGuildWhitelist && !inviteGuildWhitelist.includes(invite.guild.id)) {
censorMessage(
pluginData,

View file

@ -46,9 +46,9 @@ export const ArchiveChannelCmd = channelArchiverCmd({
const maxMessagesToArchive = args.messages ? Math.min(args.messages, MAX_ARCHIVED_MESSAGES) : MAX_ARCHIVED_MESSAGES;
if (maxMessagesToArchive <= 0) return;
const archiveLines = [];
const archiveLines: string[] = [];
let archivedMessages = 0;
let previousId;
let previousId: string | undefined;
const startTime = Date.now();
const progressMsg = await msg.channel.createMessage("Creating archive...");
@ -80,7 +80,7 @@ export const ArchiveChannelCmd = channelArchiverCmd({
}
if (message.reactions && Object.keys(message.reactions).length > 0) {
const reactionCounts = [];
const reactionCounts: string[] = [];
for (const [emoji, info] of Object.entries(message.reactions)) {
reactionCounts.push(`${info.count}x ${emoji}`);
}

View file

@ -15,7 +15,8 @@ export function getCompanionChannelOptsForVoiceChannelId(
return Object.values(config.entries)
.filter(
opts =>
opts.voice_channel_ids.includes(voiceChannel.id) || opts.voice_channel_ids.includes(voiceChannel.parentID),
opts.voice_channel_ids.includes(voiceChannel.id) ||
(voiceChannel.parentID && opts.voice_channel_ids.includes(voiceChannel.parentID)),
)
.map(opts => Object.assign({}, defaultCompanionChannelOpts, opts));
}

View file

@ -24,7 +24,7 @@ export async function handleCompanionPermissions(
export async function handleCompanionPermissions(
pluginData: GuildPluginData<CompanionChannelsPluginType>,
userId: string,
voiceChannel?: VoiceChannel,
voiceChannel: VoiceChannel | null,
oldChannel?: VoiceChannel,
) {
if (pluginData.state.errorCooldownManager.isOnCooldown(ERROR_COOLDOWN_KEY)) {
@ -65,7 +65,7 @@ export async function handleCompanionPermissions(
for (const channelId of permsToDelete) {
const channel = pluginData.guild.channels.get(channelId);
if (!channel || !(channel instanceof TextChannel)) continue;
await channel.deletePermission(userId, `Companion Channel for ${oldChannel.id} | User Left`);
await channel.deletePermission(userId, `Companion Channel for ${oldChannel!.id} | User Left`);
}
for (const [channelId, permissions] of permsToSet) {
@ -76,7 +76,7 @@ export async function handleCompanionPermissions(
permissions,
0,
"member",
`Companion Channel for ${voiceChannel.id} | User Joined`,
`Companion Channel for ${voiceChannel!.id} | User Joined`,
);
}
} catch (e) {

View file

@ -25,7 +25,7 @@ export async function addRoleAction(
const target = await resolveMember(pluginData.client, pluginData.guild, targetId);
if (!target) throw new ActionError(`Unknown target member: ${targetId}`);
if (event.trigger.type === "command" && !canActOn(pluginData, (eventData.msg as Message).member, target)) {
if (event.trigger.type === "command" && !canActOn(pluginData, eventData.msg.member, target)) {
throw new ActionError("Missing permissions");
}

View file

@ -25,7 +25,7 @@ export async function moveToVoiceChannelAction(
const target = await resolveMember(pluginData.client, pluginData.guild, targetId);
if (!target) throw new ActionError("Unknown target member");
if (event.trigger.type === "command" && !canActOn(pluginData, (eventData.msg as Message).member, target)) {
if (event.trigger.type === "command" && !canActOn(pluginData, eventData.msg.member, target)) {
throw new ActionError("Missing permissions");
}

View file

@ -10,6 +10,7 @@ import { DeleteFollowCmd, ListFollowCmd } from "./commands/ListFollowCmd";
import { ChannelJoinAlertsEvt, ChannelLeaveAlertsEvt, ChannelSwitchAlertsEvt } from "./events/SendAlertsEvts";
import { GuildBanRemoveAlertsEvt } from "./events/BanRemoveAlertsEvt";
import { trimPluginDescription } from "../../utils";
import Timeout = NodeJS.Timeout;
const defaultOptions: PluginOptions<LocateUserPluginType> = {
config: {
@ -70,7 +71,7 @@ export const LocateUserPlugin = zeppelinGuildPlugin<LocateUserPluginType>()("loc
},
onUnload(pluginData) {
clearTimeout(pluginData.state.outdatedAlertsTimeout);
clearTimeout(pluginData.state.outdatedAlertsTimeout as Timeout);
pluginData.state.unloaded = true;
},
});

View file

@ -14,7 +14,6 @@ export const WhereCmd = locateUserCmd({
},
async run({ message: msg, args, pluginData }) {
const member = await resolveMember(pluginData.client, pluginData.guild, args.member.id);
sendWhere(pluginData, member, msg.channel, `${msg.member.mention} | `);
sendWhere(pluginData, args.member, msg.channel, `${msg.member.mention} | `);
},
});

View file

@ -13,7 +13,7 @@ export interface LocateUserPluginType extends BasePluginType {
config: TConfigSchema;
state: {
alerts: GuildVCAlerts;
outdatedAlertsTimeout: Timeout;
outdatedAlertsTimeout: Timeout | null;
usersWithAlerts: string[];
unloaded: boolean;
};

View file

@ -8,6 +8,7 @@ import { moveMember } from "./moveMember";
export async function sendAlerts(pluginData: GuildPluginData<LocateUserPluginType>, userId: string) {
const triggeredAlerts = await pluginData.state.alerts.getAlertsByUserId(userId);
const member = await resolveMember(pluginData.client, pluginData.guild, userId);
if (!member) return;
triggeredAlerts.forEach(alert => {
const prepend = `<@!${alert.requestor_id}>, an alert requested by you has triggered!\nReminder: \`${alert.body}\`\n`;

View file

@ -1,4 +1,4 @@
import { Member, TextableChannel, VoiceChannel } from "eris";
import { Invite, Member, TextableChannel, VoiceChannel } from "eris";
import { getInviteLink } from "knub/dist/helpers";
import { createOrReuseInvite } from "./createOrReuseInvite";
import { GuildPluginData } from "knub";
@ -11,12 +11,14 @@ export async function sendWhere(
channel: TextableChannel,
prepend: string,
) {
const voice = pluginData.guild.channels.get(member.voiceState.channelID) as VoiceChannel;
const voice = member.voiceState.channelID
? (pluginData.guild.channels.get(member.voiceState.channelID) as VoiceChannel)
: null;
if (voice == null) {
channel.createMessage(prepend + "That user is not in a channel");
} else {
let invite = null;
let invite: Invite;
try {
invite = await createOrReuseInvite(voice);
} catch (e) {

View file

@ -28,11 +28,11 @@ export const LogsGuildMemberAddEvt = logsEvt({
cases.sort((a, b) => (a.created_at > b.created_at ? -1 : 1));
if (cases.length) {
const recentCaseLines = [];
const recentCaseLines: string[] = [];
const recentCases = cases.slice(0, 2);
const casesPlugin = pluginData.getPlugin(CasesPlugin);
for (const theCase of recentCases) {
recentCaseLines.push(await casesPlugin.getCaseSummary(theCase));
recentCaseLines.push((await casesPlugin.getCaseSummary(theCase))!);
}
let recentCaseSummary = recentCaseLines.join("\n");

View file

@ -14,16 +14,17 @@ import { renderTemplate, TemplateParseError } from "../../../templateFormatter";
import { logger } from "../../../logger";
import moment from "moment-timezone";
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
import { MessageContent } from "eris";
export async function getLogMessage(
pluginData: GuildPluginData<LogsPluginType>,
type: LogType,
data: any,
opts?: Pick<TLogChannel, "format" | "timestamp_format" | "include_embed_timestamp">,
): Promise<string> {
): Promise<MessageContent | null> {
const config = pluginData.config.get();
const format = opts?.format?.[LogType[type]] || config.format[LogType[type]] || "";
if (format === "" || format == null) return;
if (format === "" || format == null) return null;
// See comment on FORMAT_NO_TIMESTAMP in types.ts
const timestampFormat =
@ -45,7 +46,7 @@ export async function getLogMessage(
const usersOrMembers = Array.isArray(inputUserOrMember) ? inputUserOrMember : [inputUserOrMember];
const mentions = [];
const mentions: string[] = [];
for (const userOrMember of usersOrMembers) {
let user;
let member;
@ -91,7 +92,7 @@ export async function getLogMessage(
} catch (e) {
if (e instanceof TemplateParseError) {
logger.error(`Error when parsing template:\nError: ${e.message}\nTemplate: ${format}`);
return;
return null;
} else {
throw e;
}

View file

@ -109,13 +109,13 @@ export async function log(pluginData: GuildPluginData<LogsPluginType>, type: Log
if (!pluginData.state.batches.has(channel.id)) {
pluginData.state.batches.set(channel.id, []);
setTimeout(async () => {
const batchedMessage = pluginData.state.batches.get(channel.id).join("\n");
const batchedMessage = pluginData.state.batches.get(channel.id)!.join("\n");
pluginData.state.batches.delete(channel.id);
createChunkedMessage(channel, batchedMessage).catch(noop);
}, batchTime);
}
pluginData.state.batches.get(channel.id).push(message);
pluginData.state.batches.get(channel.id)!.push(message);
} else {
// If we're not batching log messages, just send them immediately
await createChunkedMessage(channel, message).catch(noop);

View file

@ -7,7 +7,7 @@ export async function saveMessagesToDB(
channel: TextChannel,
ids: string[],
) {
const failed = [];
const failed: string[] = [];
for (const id of ids) {
const savedMessage = await pluginData.state.savedMessages.find(id);
if (savedMessage) continue;

View file

@ -65,7 +65,7 @@ export const AddCaseCmd = modActionsCmd({
modId: mod.id,
type: CaseTypes[type],
reason,
ppId: mod.id !== msg.author.id ? msg.author.id : null,
ppId: mod.id !== msg.author.id ? msg.author.id : undefined,
});
if (user) {

View file

@ -78,7 +78,7 @@ export const BanCmd = modActionsCmd({
contactMethods,
caseArgs: {
modId: mod.id,
ppId: mod.id !== msg.author.id ? msg.author.id : null,
ppId: mod.id !== msg.author.id ? msg.author.id : undefined,
},
deleteMessageDays,
});

View file

@ -9,6 +9,7 @@ import { LogsPlugin } from "../../Logs/LogsPlugin";
import { LogType } from "../../../data/LogType";
import moment from "moment-timezone";
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
import { Case } from "../../../data/entities/Case";
export const DeleteCaseCmd = modActionsCmd({
trigger: ["delete_case", "deletecase"],
@ -25,8 +26,8 @@ export const DeleteCaseCmd = modActionsCmd({
},
async run({ pluginData, message, args }) {
const failed = [];
const validCases = [];
const failed: number[] = [];
const validCases: Case[] = [];
let cancelled = 0;
for (const num of args.caseNumber) {

View file

@ -77,7 +77,7 @@ export const ForcebanCmd = modActionsCmd({
modId: mod.id,
type: CaseTypes.Ban,
reason,
ppId: mod.id !== msg.author.id ? msg.author.id : null,
ppId: mod.id !== msg.author.id ? msg.author.id : undefined,
});
// Confirm the action

View file

@ -14,7 +14,7 @@ export const HideCaseCmd = modActionsCmd({
],
async run({ pluginData, message: msg, args }) {
const failed = [];
const failed: number[] = [];
for (const num of args.caseNum) {
const theCase = await pluginData.state.cases.findByCaseNumber(num);

View file

@ -62,7 +62,7 @@ export const MassbanCmd = modActionsCmd({
const loadingMsg = await msg.channel.createMessage("Banning...");
// Ban each user and count failed bans (if any)
const failedBans = [];
const failedBans: string[] = [];
const casesPlugin = pluginData.getPlugin(CasesPlugin);
for (const userId of args.userIds) {
try {

View file

@ -62,7 +62,7 @@ export const MassmuteCmd = modActionsCmd({
// Mute everyone and count fails
const modId = msg.author.id;
const failedMutes = [];
const failedMutes: string[] = [];
const mutesPlugin = pluginData.getPlugin(MutesPlugin);
for (const userId of args.userIds) {
try {

View file

@ -59,7 +59,7 @@ export const UnbanCmd = modActionsCmd({
modId: mod.id,
type: CaseTypes.Unban,
reason,
ppId: mod.id !== msg.author.id ? msg.author.id : null,
ppId: mod.id !== msg.author.id ? msg.author.id : undefined,
});
// Confirm the action

View file

@ -14,7 +14,7 @@ export const UnhideCaseCmd = modActionsCmd({
],
async run({ pluginData, message: msg, args }) {
const failed = [];
const failed: number[] = [];
for (const num of args.caseNum) {
const theCase = await pluginData.state.cases.findByCaseNumber(num);

View file

@ -24,7 +24,7 @@ export const UpdateCmd = modActionsCmd({
],
async run({ pluginData, message: msg, args }) {
let theCase: Case;
let theCase: Case | undefined;
if (args.caseNumber != null) {
theCase = await pluginData.state.cases.findByCaseNumber(args.caseNumber);
} else {

View file

@ -91,7 +91,7 @@ export const WarnCmd = modActionsCmd({
contactMethods,
caseArgs: {
modId: mod.id,
ppId: mod.id !== msg.author.id ? msg.author.id : null,
ppId: mod.id !== msg.author.id ? msg.author.id : undefined,
reason,
},
retryPromptChannel: msg.channel as TextChannel,

View file

@ -1,12 +1,12 @@
import { IgnoredEventType, modActionsEvt } from "../types";
import { isEventIgnored } from "../functions/isEventIgnored";
import { clearIgnoredEvents } from "../functions/clearIgnoredEvents";
import { Constants as ErisConstants } from "eris";
import { Constants as ErisConstants, User } from "eris";
import { CasesPlugin } from "../../Cases/CasesPlugin";
import { CaseTypes } from "../../../data/CaseTypes";
import { safeFindRelevantAuditLogEntry } from "../../../utils/safeFindRelevantAuditLogEntry";
import { LogType } from "../../../data/LogType";
import { stripObjectToScalars, resolveUser } from "../../../utils";
import { stripObjectToScalars, resolveUser, UnknownUser } from "../../../utils";
/**
* Create a BAN case automatically when a user is banned manually.
@ -29,27 +29,27 @@ export const CreateBanCaseOnManualBanEvt = modActionsEvt(
const casesPlugin = pluginData.getPlugin(CasesPlugin);
let createdCase;
let mod = null;
let mod: User | UnknownUser | null = null;
let reason = "";
if (relevantAuditLogEntry) {
const modId = relevantAuditLogEntry.user.id;
const auditLogId = relevantAuditLogEntry.id;
mod = resolveUser(pluginData.client, modId);
reason = relevantAuditLogEntry.reason;
mod = await resolveUser(pluginData.client, modId);
reason = relevantAuditLogEntry.reason || "";
createdCase = await casesPlugin.createCase({
userId: user.id,
modId,
type: CaseTypes.Ban,
auditLogId,
reason,
reason: reason || undefined,
automatic: true,
});
} else {
createdCase = await casesPlugin.createCase({
userId: user.id,
modId: null,
modId: "0",
type: CaseTypes.Ban,
});
}

View file

@ -41,7 +41,7 @@ export const CreateKickCaseOnManualKickEvt = modActionsEvt(
modId: kickAuditLogEntry.user.id,
type: CaseTypes.Kick,
auditLogId: kickAuditLogEntry.id,
reason: kickAuditLogEntry.reason,
reason: kickAuditLogEntry.reason || undefined,
automatic: true,
});
}

View file

@ -1,11 +1,11 @@
import { IgnoredEventType, modActionsEvt } from "../types";
import { isEventIgnored } from "../functions/isEventIgnored";
import { clearIgnoredEvents } from "../functions/clearIgnoredEvents";
import { Constants as ErisConstants } from "eris";
import { Constants as ErisConstants, User } from "eris";
import { CasesPlugin } from "../../Cases/CasesPlugin";
import { CaseTypes } from "../../../data/CaseTypes";
import { safeFindRelevantAuditLogEntry } from "../../../utils/safeFindRelevantAuditLogEntry";
import { stripObjectToScalars, resolveUser } from "../../../utils";
import { stripObjectToScalars, resolveUser, UnknownUser } from "../../../utils";
import { LogType } from "../../../data/LogType";
/**
@ -29,13 +29,13 @@ export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt(
const casesPlugin = pluginData.getPlugin(CasesPlugin);
let createdCase;
let mod = null;
let mod: User | UnknownUser | null = null;
if (relevantAuditLogEntry) {
const modId = relevantAuditLogEntry.user.id;
const auditLogId = relevantAuditLogEntry.id;
mod = resolveUser(pluginData.client, modId);
mod = await resolveUser(pluginData.client, modId);
createdCase = await casesPlugin.createCase({
userId: user.id,
modId,
@ -46,7 +46,7 @@ export const CreateUnbanCaseOnManualUnbanEvt = modActionsEvt(
} else {
createdCase = await casesPlugin.createCase({
userId: user.id,
modId: null,
modId: "0",
type: CaseTypes.Unban,
automatic: true,
});

View file

@ -10,6 +10,7 @@ import { MutesPlugin } from "../../Mutes/MutesPlugin";
import { readContactMethodsFromArgs } from "./readContactMethodsFromArgs";
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
import { logger } from "../../../logger";
import { GuildMessage } from "knub/dist/helpers";
/**
* The actual function run by both !mute and !forcemute.
@ -18,12 +19,12 @@ import { logger } from "../../../logger";
export async function actualMuteUserCmd(
pluginData: GuildPluginData<ModActionsPluginType>,
user: User | UnknownUser,
msg: Message,
msg: GuildMessage,
args: { time?: number; reason?: string; mod: Member; notify?: string; "notify-channel"?: TextChannel },
) {
// The moderator who did the action is the message author or, if used, the specified -mod
let mod = msg.member;
let pp = null;
let mod: Member = msg.member;
let pp: User | null = null;
if (args.mod) {
if (!hasPermission(pluginData, "can_act_as_other", { message: msg })) {
@ -36,7 +37,7 @@ export async function actualMuteUserCmd(
}
const timeUntilUnmute = args.time && humanizeDuration(args.time);
const reason = formatReasonWithAttachments(args.reason, msg.attachments);
const reason = args.reason ? formatReasonWithAttachments(args.reason, msg.attachments) : undefined;
let muteResult: MuteResult;
const mutesPlugin = pluginData.getPlugin(MutesPlugin);
@ -54,7 +55,7 @@ export async function actualMuteUserCmd(
contactMethods,
caseArgs: {
modId: mod.id,
ppId: pp && pp.id,
ppId: pp ? pp.id : undefined,
},
});
} catch (e) {

View file

@ -15,7 +15,7 @@ export async function actualUnmuteCmd(
) {
// The moderator who did the action is the message author or, if used, the specified -mod
let mod = msg.author;
let pp = null;
let pp: User | null = null;
if (args.mod) {
if (!hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id })) {
@ -27,15 +27,20 @@ export async function actualUnmuteCmd(
pp = msg.author;
}
const reason = formatReasonWithAttachments(args.reason, msg.attachments);
const reason = args.reason ? formatReasonWithAttachments(args.reason, msg.attachments) : undefined;
const mutesPlugin = pluginData.getPlugin(MutesPlugin);
const result = await mutesPlugin.unmuteUser(user.id, args.time, {
modId: mod.id,
ppId: pp && pp.id,
ppId: pp ? pp.id : undefined,
reason,
});
if (!result) {
sendErrorMessage(pluginData, msg.channel, "User is not muted!");
return;
}
// Confirm the action to the moderator
if (args.time) {
const timeUntilUnmute = args.time && humanizeDuration(args.time);

View file

@ -1,6 +1,13 @@
import { GuildPluginData } from "knub";
import { BanOptions, BanResult, IgnoredEventType, ModActionsPluginType } from "../types";
import { notifyUser, resolveUser, stripObjectToScalars, ucfirst, UserNotificationResult } from "../../../utils";
import {
createUserNotificationError,
notifyUser,
resolveUser,
stripObjectToScalars,
ucfirst,
UserNotificationResult,
} from "../../../utils";
import { User } from "eris";
import { renderTemplate } from "../../../templateFormatter";
import { getDefaultContactMethods } from "./getDefaultContactMethods";
@ -15,7 +22,7 @@ import { CaseTypes } from "../../../data/CaseTypes";
export async function banUserId(
pluginData: GuildPluginData<ModActionsPluginType>,
userId: string,
reason: string = null,
reason?: string,
banOptions: BanOptions = {},
): Promise<BanResult> {
const config = pluginData.config.get();
@ -30,15 +37,22 @@ export async function banUserId(
// Attempt to message the user *before* banning them, as doing it after may not be possible
let notifyResult: UserNotificationResult = { method: null, success: true };
if (reason && user instanceof User) {
const banMessage = await renderTemplate(config.ban_message, {
guildName: pluginData.guild.name,
reason,
});
const contactMethods = banOptions?.contactMethods
? banOptions.contactMethods
: getDefaultContactMethods(pluginData, "ban");
notifyResult = await notifyUser(user, banMessage, contactMethods);
if (contactMethods.length) {
if (config.ban_message) {
const banMessage = await renderTemplate(config.ban_message, {
guildName: pluginData.guild.name,
reason,
});
notifyResult = await notifyUser(user, banMessage, contactMethods);
} else {
notifyResult = createUserNotificationError("No ban message specified in config");
}
}
}
// (Try to) ban the user
@ -59,18 +73,19 @@ export async function banUserId(
}
// Create a case for this action
const modId = banOptions.caseArgs?.modId || pluginData.client.user.id;
const casesPlugin = pluginData.getPlugin(CasesPlugin);
const createdCase = await casesPlugin.createCase({
...(banOptions.caseArgs || {}),
userId,
modId: banOptions.caseArgs?.modId,
modId,
type: CaseTypes.Ban,
reason,
noteDetails: notifyResult.text ? [ucfirst(notifyResult.text)] : [],
});
// Log the action
const mod = await resolveUser(pluginData.client, banOptions.caseArgs?.modId);
const mod = await resolveUser(pluginData.client, modId);
pluginData.state.serverLogs.log(LogType.MEMBER_BAN, {
mod: stripObjectToScalars(mod),
user: stripObjectToScalars(user),

View file

@ -1,7 +1,14 @@
import { GuildPluginData } from "knub";
import { IgnoredEventType, KickOptions, KickResult, ModActionsPluginType } from "../types";
import { Member } from "eris";
import { notifyUser, resolveUser, stripObjectToScalars, ucfirst, UserNotificationResult } from "../../../utils";
import {
createUserNotificationError,
notifyUser,
resolveUser,
stripObjectToScalars,
ucfirst,
UserNotificationResult,
} from "../../../utils";
import { renderTemplate } from "../../../templateFormatter";
import { getDefaultContactMethods } from "./getDefaultContactMethods";
import { LogType } from "../../../data/LogType";
@ -15,7 +22,7 @@ import { CasesPlugin } from "../../Cases/CasesPlugin";
export async function kickMember(
pluginData: GuildPluginData<ModActionsPluginType>,
member: Member,
reason: string = null,
reason?: string,
kickOptions: KickOptions = {},
): Promise<KickResult> {
const config = pluginData.config.get();
@ -23,15 +30,22 @@ export async function kickMember(
// Attempt to message the user *before* kicking them, as doing it after may not be possible
let notifyResult: UserNotificationResult = { method: null, success: true };
if (reason) {
const kickMessage = await renderTemplate(config.kick_message, {
guildName: pluginData.guild.name,
reason,
});
const contactMethods = kickOptions?.contactMethods
? kickOptions.contactMethods
: getDefaultContactMethods(pluginData, "kick");
notifyResult = await notifyUser(member.user, kickMessage, contactMethods);
if (contactMethods.length) {
if (config.kick_message) {
const kickMessage = await renderTemplate(config.kick_message, {
guildName: pluginData.guild.name,
reason,
});
notifyResult = await notifyUser(member.user, kickMessage, contactMethods);
} else {
notifyResult = createUserNotificationError("No kick message specified in the config");
}
}
}
// Kick the user
@ -46,19 +60,21 @@ export async function kickMember(
};
}
const modId = kickOptions.caseArgs?.modId || pluginData.client.user.id;
// Create a case for this action
const casesPlugin = pluginData.getPlugin(CasesPlugin);
const createdCase = await casesPlugin.createCase({
...(kickOptions.caseArgs || {}),
userId: member.id,
modId: kickOptions.caseArgs?.modId,
modId,
type: CaseTypes.Kick,
reason,
noteDetails: notifyResult.text ? [ucfirst(notifyResult.text)] : [],
});
// Log the action
const mod = await resolveUser(pluginData.client, kickOptions.caseArgs?.modId);
const mod = await resolveUser(pluginData.client, modId);
pluginData.state.serverLogs.log(LogType.MEMBER_KICK, {
mod: stripObjectToScalars(mod),
user: stripObjectToScalars(member.user),

View file

@ -2,7 +2,14 @@ import { GuildPluginData } from "knub";
import { ModActionsPluginType, WarnOptions, WarnResult } from "../types";
import { Member } from "eris";
import { getDefaultContactMethods } from "./getDefaultContactMethods";
import { notifyUser, resolveUser, stripObjectToScalars, ucfirst } from "../../../utils";
import {
createUserNotificationError,
notifyUser,
resolveUser,
stripObjectToScalars,
ucfirst,
UserNotificationResult,
} from "../../../utils";
import { waitForReaction } from "knub/dist/helpers";
import { CasesPlugin } from "../../Cases/CasesPlugin";
import { CaseTypes } from "../../../data/CaseTypes";
@ -13,14 +20,19 @@ export async function warnMember(
member: Member,
reason: string,
warnOptions: WarnOptions = {},
): Promise<WarnResult | null> {
): Promise<WarnResult> {
const config = pluginData.config.get();
const warnMessage = config.warn_message.replace("{guildName}", pluginData.guild.name).replace("{reason}", reason);
const contactMethods = warnOptions?.contactMethods
? warnOptions.contactMethods
: getDefaultContactMethods(pluginData, "warn");
const notifyResult = await notifyUser(member.user, warnMessage, contactMethods);
let notifyResult: UserNotificationResult;
if (config.warn_message) {
const warnMessage = config.warn_message.replace("{guildName}", pluginData.guild.name).replace("{reason}", reason);
const contactMethods = warnOptions?.contactMethods
? warnOptions.contactMethods
: getDefaultContactMethods(pluginData, "warn");
notifyResult = await notifyUser(member.user, warnMessage, contactMethods);
} else {
notifyResult = createUserNotificationError("No warn message specified in config");
}
if (!notifyResult.success) {
if (warnOptions.retryPromptChannel && pluginData.guild.channels.has(warnOptions.retryPromptChannel.id)) {
@ -43,17 +55,19 @@ export async function warnMember(
}
}
const modId = warnOptions.caseArgs?.modId ?? pluginData.client.user.id;
const casesPlugin = pluginData.getPlugin(CasesPlugin);
const createdCase = await casesPlugin.createCase({
...(warnOptions.caseArgs || {}),
userId: member.id,
modId: warnOptions.caseArgs?.modId,
modId,
type: CaseTypes.Warn,
reason,
noteDetails: notifyResult.text ? [ucfirst(notifyResult.text)] : [],
});
const mod = await resolveUser(pluginData.client, warnOptions.caseArgs?.modId);
const mod = await resolveUser(pluginData.client, modId);
pluginData.state.serverLogs.log(LogType.MEMBER_WARN, {
mod: stripObjectToScalars(mod),
member: stripObjectToScalars(member, ["user", "roles"]),

View file

@ -98,9 +98,9 @@ export type BanResult =
export type WarnMemberNotifyRetryCallback = () => boolean | Promise<boolean>;
export interface WarnOptions {
caseArgs?: Partial<CaseArgs>;
contactMethods?: UserNotificationMethod[];
retryPromptChannel?: TextChannel;
caseArgs?: Partial<CaseArgs> | null;
contactMethods?: UserNotificationMethod[] | null;
retryPromptChannel?: TextChannel | null;
}
export interface KickOptions {

View file

@ -17,6 +17,7 @@ import { CaseArgs } from "../Cases/types";
import { Member } from "eris";
import { ClearActiveMuteOnMemberBanEvt } from "./events/ClearActiveMuteOnMemberBanEvt";
import { ReapplyActiveMuteOnJoinEvt } from "./events/ReapplyActiveMuteOnJoinEvt";
import { mapToPublicFn } from "../../pluginUtils";
const defaultOptions = {
config: {
@ -82,19 +83,12 @@ export const MutesPlugin = zeppelinGuildPlugin<MutesPluginType>()("mutes", {
],
public: {
muteUser(pluginData) {
return (userId: string, muteTime: number = null, reason: string = null, muteOptions: MuteOptions = {}) => {
return muteUser(pluginData, userId, muteTime, reason, muteOptions);
};
},
unmuteUser(pluginData) {
return (userId: string, unmuteTime: number = null, args: Partial<CaseArgs>) => {
return unmuteUser(pluginData, userId, unmuteTime, args);
};
},
muteUser: mapToPublicFn(muteUser),
unmuteUser: mapToPublicFn(unmuteUser),
hasMutedRole(pluginData) {
return (member: Member) => {
return member.roles.includes(pluginData.config.get().mute_role);
const muteRole = pluginData.config.get().mute_role;
return muteRole ? member.roles.includes(muteRole) : false;
};
},
},

View file

@ -12,7 +12,7 @@ export const ClearMutesCmd = mutesCmd({
},
async run({ pluginData, message: msg, args }) {
const failed = [];
const failed: string[] = [];
for (const id of args.userIds) {
const mute = await pluginData.state.mutes.findExistingMuteForUserId(id);
if (!mute) {

View file

@ -4,6 +4,7 @@ import { DBDateFormat, isFullMessage, MINUTES, noop, resolveMember } from "../..
import moment from "moment-timezone";
import { humanizeDurationShort } from "../../../humanizeDurationShort";
import { getBaseUrl } from "../../../pluginUtils";
import { Member } from "eris";
export const MutesCmd = mutesCmd({
trigger: "mutes",
@ -31,7 +32,7 @@ export const MutesCmd = mutesCmd({
let clearReactionsTimeout;
const clearReactionsDebounce = 5 * MINUTES;
let lines = [];
let lines: string[] = [];
// Active, logged mutes
const activeMutes = await pluginData.state.mutes.getActiveMutes();
@ -41,13 +42,13 @@ export const MutesCmd = mutesCmd({
if (a.expires_at == null && b.expires_at == null) {
return a.created_at > b.created_at ? -1 : 1;
}
return a.expires_at > b.expires_at ? 1 : -1;
return a.expires_at! > b.expires_at! ? 1 : -1;
});
if (args.manual) {
// Show only manual mutes (i.e. "Muted" role added without a logged mute)
const muteUserIds = new Set(activeMutes.map(m => m.user_id));
const manuallyMutedMembers = [];
const manuallyMutedMembers: Member[] = [];
const muteRole = pluginData.config.get().mute_role;
if (muteRole) {
@ -65,7 +66,7 @@ export const MutesCmd = mutesCmd({
} else {
// Show filtered active mutes (but not manual mutes)
let filteredMutes: IMuteWithDetails[] = activeMutes;
let bannedIds: string[] = null;
let bannedIds: string[] | null = null;
// Filter: mute age
if (args.age) {

View file

@ -10,9 +10,11 @@ export const ReapplyActiveMuteOnJoinEvt = mutesEvt("guildMemberAdd", async ({ pl
if (mute) {
const muteRole = pluginData.config.get().mute_role;
const memberRolesLock = await pluginData.locks.acquire(`member-roles-${member.id}`);
await member.addRole(muteRole);
memberRolesLock.unlock();
if (muteRole) {
const memberRolesLock = await pluginData.locks.acquire(`member-roles-${member.id}`);
await member.addRole(muteRole);
memberRolesLock.unlock();
}
pluginData.state.serverLogs.log(LogType.MEMBER_MUTE_REJOIN, {
member: stripObjectToScalars(member, ["user", "roles"]),

View file

@ -10,7 +10,10 @@ export async function clearExpiredMutes(pluginData: GuildPluginData<MutesPluginT
if (member) {
try {
await member.removeRole(pluginData.config.get().mute_role);
const muteRole = pluginData.config.get().mute_role;
if (muteRole) {
await member.removeRole(muteRole);
}
} catch (e) {
pluginData.state.serverLogs.log(LogType.BOT_ALERT, {
body: `Failed to remove mute role from {userMention(member)}`,

View file

@ -2,6 +2,7 @@ import { Member } from "eris";
import { GuildPluginData } from "knub";
import { MutesPluginType } from "../types";
export function memberHasMutedRole(pluginData: GuildPluginData<MutesPluginType>, member: Member) {
return member.roles.includes(pluginData.config.get().mute_role);
export function memberHasMutedRole(pluginData: GuildPluginData<MutesPluginType>, member: Member): boolean {
const muteRole = pluginData.config.get().mute_role;
return muteRole ? member.roles.includes(muteRole) : false;
}

View file

@ -9,6 +9,7 @@ import {
ucfirst,
UserNotificationResult,
resolveMember,
UserNotificationMethod,
} from "../../../utils";
import { renderTemplate } from "../../../templateFormatter";
import { TextChannel, User } from "eris";
@ -20,8 +21,8 @@ import { Case } from "../../../data/entities/Case";
export async function muteUser(
pluginData: GuildPluginData<MutesPluginType>,
userId: string,
muteTime: number = null,
reason: string = null,
muteTime?: number,
reason?: string,
muteOptions: MuteOptions = {},
) {
const lock = await pluginData.locks.acquire(`mute-${userId}`);
@ -90,7 +91,7 @@ export async function muteUser(
}));
if (muteMessage && user instanceof User) {
let contactMethods = [];
let contactMethods: UserNotificationMethod[] = [];
if (muteOptions?.contactMethods) {
contactMethods = muteOptions.contactMethods;
@ -112,18 +113,21 @@ export async function muteUser(
// Create/update a case
const casesPlugin = pluginData.getPlugin(CasesPlugin);
let theCase: Case;
let theCase: Case | undefined =
existingMute && existingMute.case_id ? await pluginData.state.cases.find(existingMute.case_id) : undefined;
if (existingMute && existingMute.case_id) {
if (theCase) {
// Update old case
// Since mutes can often have multiple notes (extraNotes), we won't post each case note individually,
// but instead we'll post the entire case afterwards
theCase = await pluginData.state.cases.find(existingMute.case_id);
const noteDetails = [`Mute updated to ${muteTime ? timeUntilUnmute : "indefinite"}`];
const reasons = [reason, ...(muteOptions.caseArgs?.extraNotes || [])];
const reasons = reason ? [reason] : [];
if (muteOptions.caseArgs?.extraNotes) {
reasons.push(...muteOptions.caseArgs.extraNotes);
}
for (const noteReason of reasons) {
await casesPlugin.createCaseNote({
caseId: existingMute.case_id,
caseId: existingMute!.case_id,
modId: muteOptions.caseArgs?.modId,
body: noteReason,
noteDetails,
@ -132,7 +136,7 @@ export async function muteUser(
}
if (muteOptions.caseArgs?.postInCaseLogOverride !== false) {
casesPlugin.postCaseToCaseLogChannel(existingMute.case_id);
casesPlugin.postCaseToCaseLogChannel(existingMute!.case_id);
}
} else {
// Create new case

View file

@ -7,18 +7,20 @@ import humanizeDuration from "humanize-duration";
import { CasesPlugin } from "../../Cases/CasesPlugin";
import { CaseTypes } from "../../../data/CaseTypes";
import { LogType } from "../../../data/LogType";
import { WithRequiredProps } from "../../../utils/typeUtils";
export async function unmuteUser(
pluginData: GuildPluginData<MutesPluginType>,
userId: string,
unmuteTime: number = null,
unmuteTime?: number,
caseArgs: Partial<CaseArgs> = {},
): Promise<UnmuteResult> {
): Promise<UnmuteResult | null> {
const existingMute = await pluginData.state.mutes.findExistingMuteForUserId(userId);
const user = await resolveUser(pluginData.client, userId);
const member = await resolveMember(pluginData.client, pluginData.guild, userId); // Grab the fresh member so we don't have stale role info
const modId = caseArgs.modId || pluginData.client.user.id;
if (!existingMute && !memberHasMutedRole(pluginData, member)) return;
if (!existingMute && member && !memberHasMutedRole(pluginData, member)) return null;
if (unmuteTime) {
// Schedule timed unmute (= just set the mute's duration)
@ -31,7 +33,7 @@ export async function unmuteUser(
// Unmute immediately
if (member) {
const muteRole = pluginData.config.get().mute_role;
if (member.roles.includes(muteRole)) {
if (muteRole && member.roles.includes(muteRole)) {
await member.removeRole(muteRole);
}
} else {
@ -47,7 +49,7 @@ export async function unmuteUser(
const timeUntilUnmute = unmuteTime && humanizeDuration(unmuteTime);
// Create a case
const noteDetails = [];
const noteDetails: string[] = [];
if (unmuteTime) {
noteDetails.push(`Scheduled unmute in ${timeUntilUnmute}`);
} else {
@ -61,13 +63,13 @@ export async function unmuteUser(
const createdCase = await casesPlugin.createCase({
...caseArgs,
userId,
modId: caseArgs.modId,
modId,
type: CaseTypes.Unmute,
noteDetails,
});
// Log the action
const mod = pluginData.client.users.get(caseArgs.modId);
const mod = pluginData.client.users.get(modId);
if (unmuteTime) {
pluginData.state.serverLogs.log(LogType.MEMBER_TIMED_UNMUTE, {
mod: stripObjectToScalars(mod),

View file

@ -13,6 +13,6 @@ export const MessageCreateEvt = nameHistoryEvt({
event: "messageCreate",
async listener(meta) {
meta.pluginData.state.updateQueue.add(() => updateNickname(meta.pluginData, meta.args.message.member));
meta.pluginData.state.updateQueue.add(() => updateNickname(meta.pluginData, meta.args.message.member!));
},
});

View file

@ -27,10 +27,10 @@ export const LoadDataEvt = persistEvt({
const toRestore: MemberOptions = {};
const config = pluginData.config.getForMember(member);
const restoredData = [];
const restoredData: string[] = [];
// Check permissions
const me = pluginData.guild.members.get(pluginData.client.user.id);
const me = pluginData.guild.members.get(pluginData.client.user.id)!;
let requiredPermissions = 0;
if (config.persist_nicknames) requiredPermissions |= p.manageNicknames;
if (config.persisted_roles) requiredPermissions |= p.manageRoles;

View file

@ -10,5 +10,5 @@ export async function getPingableRolesForChannel(
pluginData.state.cache.set(channelId, await pluginData.state.pingableRoles.getForChannel(channelId));
}
return pluginData.state.cache.get(channelId);
return pluginData.state.cache.get(channelId)!;
}

View file

@ -31,7 +31,7 @@ export const EditEmbedCmd = postCmd({
const content = args.content || args.maincontent;
let color = null;
let color: number | null = null;
if (args.color) {
const colorRgb = parseColor(args.color);
if (colorRgb) {
@ -42,8 +42,7 @@ export const EditEmbedCmd = postCmd({
}
}
const embed: Embed = savedMessage.data.embeds[0] as Embed;
embed.type = "rich";
const embed: Embed = savedMessage.data.embeds![0] as Embed;
if (args.title) embed.title = args.title;
if (content) embed.description = formatContent(content);
if (color) embed.color = color;

View file

@ -35,7 +35,7 @@ export const PostEmbedCmd = postCmd({
return;
}
let color = null;
let color: number | null = null;
if (args.color) {
const colorRgb = parseColor(args.color);
if (colorRgb) {

View file

@ -39,7 +39,7 @@ export const ScheduledPostsListCmd = postCmd({
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
const prettyPostAt = timeAndDate
.inGuildTz(moment.utc(p.post_at, DBDateFormat))
.inGuildTz(moment.utc(p.post_at!, DBDateFormat))
.format(timeAndDate.getDateFormat("pretty_datetime"));
const parts = [`\`#${i++}\` \`[${prettyPostAt}]\` ${previewText}${isTruncated ? "..." : ""}`];
if (p.attachments.length) parts.push("*(with attachment)*");

View file

@ -19,13 +19,13 @@ export async function actualPostCmd(
msg: Message,
targetChannel: Channel,
content: StrictMessageContent,
opts?: {
opts: {
"enable-mentions"?: boolean;
schedule?: string;
repeat?: number;
"repeat-until"?: string;
"repeat-times"?: number;
},
} = {},
) {
if (!(targetChannel instanceof TextChannel)) {
msg.channel.createMessage(errorMessage("Channel is not a text channel"));
@ -63,9 +63,9 @@ export async function actualPostCmd(
}
// For repeated posts, make sure repeat-until or repeat-times is specified
let repeatUntil: moment.Moment = null;
let repeatTimes: number = null;
let repeatDetailsStr: string = null;
let repeatUntil: moment.Moment | null = null;
let repeatTimes: number | null = null;
let repeatDetailsStr: string | null = null;
if (opts["repeat-until"]) {
repeatUntil = await parseScheduleTime(pluginData, msg.author.id, opts["repeat-until"]);

View file

@ -8,7 +8,7 @@ export async function parseScheduleTime(
pluginData: GuildPluginData<any>,
memberId: string,
str: string,
): Promise<Moment> {
): Promise<Moment | null> {
const tz = await pluginData.getPlugin(TimeAndDatePlugin).getMemberTz(memberId);
const dt1 = moment.tz(str, "YYYY-MM-DD HH:mm:ss", tz);

View file

@ -111,7 +111,7 @@ export const InitReactionRolesCmd = reactionRolesCmd({
reactionRoles,
);
if (errors.length) {
if (errors?.length) {
sendErrorMessage(pluginData, msg.channel, `Errors while adding reaction roles:\n${errors.join("\n")}`);
} else {
sendSuccessMessage(pluginData, msg.channel, "Reaction roles added");

View file

@ -14,7 +14,7 @@ export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export type RoleChangeMode = "+" | "-";
export type PendingMemberRoleChanges = {
timeout: NodeJS.Timeout;
timeout: NodeJS.Timeout | null;
applyFn: () => void;
changes: Array<{
mode: RoleChangeMode;

View file

@ -48,7 +48,7 @@ export async function addMemberPendingRoleChange(
pluginData.state.pendingRoleChanges.set(memberId, newPendingRoleChangeObj);
}
const pendingRoleChangeObj = pluginData.state.pendingRoleChanges.get(memberId);
const pendingRoleChangeObj = pluginData.state.pendingRoleChanges.get(memberId)!;
pendingRoleChangeObj.changes.push({ mode, roleId });
if (pendingRoleChangeObj.timeout) clearTimeout(pendingRoleChangeObj.timeout);

View file

@ -17,11 +17,11 @@ export async function applyReactionRoleReactionsToMessage(
channelId: string,
messageId: string,
reactionRoles: ReactionRole[],
): Promise<string[]> {
): Promise<string[] | undefined> {
const channel = pluginData.guild.channels.get(channelId) as TextChannel;
if (!channel) return;
const errors = [];
const errors: string[] = [];
const logs = pluginData.getPlugin(LogsPlugin);
let targetMessage;

View file

@ -4,6 +4,7 @@ import { rolesCmd } from "../types";
import { resolveMember, resolveRoleId, stripObjectToScalars, successMessage } from "../../../utils";
import { LogType } from "../../../data/LogType";
import { logger } from "../../../logger";
import { Member } from "eris";
export const MassAddRoleCmd = rolesCmd({
trigger: "massaddrole",
@ -17,8 +18,8 @@ export const MassAddRoleCmd = rolesCmd({
async run({ message: msg, args, pluginData }) {
msg.channel.createMessage(`Resolving members...`);
const members = [];
const unknownMembers = [];
const members: Member[] = [];
const unknownMembers: string[] = [];
for (const memberId of args.members) {
const member = await resolveMember(pluginData.client, pluginData.guild, memberId);
if (member) members.push(member);
@ -55,7 +56,7 @@ export const MassAddRoleCmd = rolesCmd({
const membersWithoutTheRole = members.filter(m => !m.roles.includes(roleId));
let assigned = 0;
const failed = [];
const failed: string[] = [];
const alreadyHadRole = members.length - membersWithoutTheRole.length;
msg.channel.createMessage(

View file

@ -4,6 +4,7 @@ import { rolesCmd } from "../types";
import { resolveMember, stripObjectToScalars, successMessage, resolveRoleId } from "../../../utils";
import { LogType } from "../../../data/LogType";
import { logger } from "../../../logger";
import { Member } from "eris";
export const MassRemoveRoleCmd = rolesCmd({
trigger: "massremoverole",
@ -17,8 +18,8 @@ export const MassRemoveRoleCmd = rolesCmd({
async run({ message: msg, args, pluginData }) {
msg.channel.createMessage(`Resolving members...`);
const members = [];
const unknownMembers = [];
const members: Member[] = [];
const unknownMembers: string[] = [];
for (const memberId of args.members) {
const member = await resolveMember(pluginData.client, pluginData.guild, memberId);
if (member) members.push(member);
@ -55,7 +56,7 @@ export const MassRemoveRoleCmd = rolesCmd({
const membersWithTheRole = members.filter(m => m.roles.includes(roleId));
let assigned = 0;
const failed = [];
const failed: string[] = [];
const didNotHaveRole = members.length - membersWithTheRole.length;
msg.channel.createMessage(

View file

@ -30,7 +30,7 @@ export const RoleAddCmd = selfGrantableRolesCmd({
const hasUnknownRoles = matchedRoleIds.length !== roleNames.length;
const rolesToAdd: Map<string, Role> = Array.from(matchedRoleIds.values())
.map(id => pluginData.guild.roles.get(id))
.map(id => pluginData.guild.roles.get(id)!)
.filter(Boolean)
.reduce((map, role) => {
map.set(role.id, role);
@ -68,9 +68,9 @@ export const RoleAddCmd = selfGrantableRolesCmd({
rolesToAdd.delete(roleId);
if (msg.member.roles.includes(roleId)) {
removed.add(pluginData.guild.roles.get(roleId));
removed.add(pluginData.guild.roles.get(roleId)!);
} else {
skipped.add(pluginData.guild.roles.get(roleId));
skipped.add(pluginData.guild.roles.get(roleId)!);
}
}
}
@ -94,7 +94,7 @@ export const RoleAddCmd = selfGrantableRolesCmd({
const addedRolesStr = Array.from(rolesToAdd.values()).map(r => (mentionRoles ? `<@&${r.id}>` : `**${r.name}**`));
const addedRolesWord = rolesToAdd.size === 1 ? "role" : "roles";
const messageParts = [];
const messageParts: string[] = [];
messageParts.push(`Granted you the ${addedRolesStr.join(", ")} ${addedRolesWord}`);
if (skipped.size || removed.size) {

View file

@ -10,7 +10,7 @@ export const RoleHelpCmd = selfGrantableRolesCmd({
const applyingEntries = getApplyingEntries(pluginData, msg);
if (applyingEntries.length === 0) return;
const allPrimaryAliases = [];
const allPrimaryAliases: string[] = [];
for (const entry of applyingEntries) {
for (const aliases of Object.values(entry.roles)) {
if (aliases[0]) {

View file

@ -26,7 +26,7 @@ export const RoleRemoveCmd = selfGrantableRolesCmd({
const roleNames = normalizeRoleNames(splitRoleNames(args.roleNames));
const matchedRoleIds = findMatchingRoles(roleNames, applyingEntries);
const rolesToRemove = Array.from(matchedRoleIds.values()).map(id => pluginData.guild.roles.get(id));
const rolesToRemove = Array.from(matchedRoleIds.values()).map(id => pluginData.guild.roles.get(id)!);
const roleIdsToRemove = rolesToRemove.map(r => r.id);
// Remove the roles

View file

@ -25,7 +25,7 @@ export const SlowmodeClearCmd = slowmodeCmd({
return;
}
const me = pluginData.guild.members.get(pluginData.client.user.id);
const me = pluginData.guild.members.get(pluginData.client.user.id)!;
const missingPermissions = getMissingChannelPermissions(me, args.channel, BOT_SLOWMODE_CLEAR_PERMISSIONS);
if (missingPermissions) {
sendErrorMessage(

Some files were not shown because too many files have changed in this diff Show more