mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-23 17:45:03 +00:00
initial - tempbans & mutes
This commit is contained in:
parent
c84d1a0be1
commit
744b9273bb
14 changed files with 322 additions and 109 deletions
|
@ -11,6 +11,14 @@ export class GuildMutes extends BaseGuildRepository {
|
|||
this.mutes = getRepository(Mute);
|
||||
}
|
||||
|
||||
async getAllTemporaryMutes(): Promise<Mute[]> {
|
||||
return this.mutes
|
||||
.createQueryBuilder("mutes")
|
||||
.where("guild_id = :guild_id", { guild_id: this.guildId })
|
||||
.andWhere("expires_at IS NOT NULL")
|
||||
.getMany();
|
||||
}
|
||||
|
||||
async getExpiredMutes(): Promise<Mute[]> {
|
||||
return this.mutes
|
||||
.createQueryBuilder("mutes")
|
||||
|
|
|
@ -11,9 +11,17 @@ export class GuildTempbans extends BaseGuildRepository {
|
|||
this.tempbans = getRepository(Tempban);
|
||||
}
|
||||
|
||||
async getAllTempbans(): Promise<Tempban[]> {
|
||||
return this.tempbans
|
||||
.createQueryBuilder("tempbans")
|
||||
.where("guild_id = :guild_id", { guild_id: this.guildId })
|
||||
.andWhere("expires_at IS NOT NULL")
|
||||
.getMany();
|
||||
}
|
||||
|
||||
async getExpiredTempbans(): Promise<Tempban[]> {
|
||||
return this.tempbans
|
||||
.createQueryBuilder("mutes")
|
||||
.createQueryBuilder("tempbans")
|
||||
.where("guild_id = :guild_id", { guild_id: this.guildId })
|
||||
.andWhere("expires_at IS NOT NULL")
|
||||
.andWhere("expires_at <= NOW()")
|
||||
|
|
|
@ -42,7 +42,7 @@ import { hasMutePermission } from "./functions/hasMutePerm";
|
|||
import { kickMember } from "./functions/kickMember";
|
||||
import { offModActionsEvent } from "./functions/offModActionsEvent";
|
||||
import { onModActionsEvent } from "./functions/onModActionsEvent";
|
||||
import { outdatedTempbansLoop } from "./functions/outdatedTempbansLoop";
|
||||
import { loadExpiringTimers } from "./functions/outdatedTempbansLoop";
|
||||
import { updateCase } from "./functions/updateCase";
|
||||
import { warnMember } from "./functions/warnMember";
|
||||
import { BanOptions, ConfigSchema, KickOptions, ModActionsPluginType, WarnOptions } from "./types";
|
||||
|
@ -112,6 +112,8 @@ const defaultOptions = {
|
|||
],
|
||||
};
|
||||
|
||||
const EXPIRED_BANS_CHECK_INTERVAL = 30 * MINUTES;
|
||||
|
||||
export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()({
|
||||
name: "mod_actions",
|
||||
showInDocs: true,
|
||||
|
@ -200,8 +202,8 @@ export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()({
|
|||
state.serverLogs = new GuildLogs(guild.id);
|
||||
|
||||
state.unloaded = false;
|
||||
state.outdatedTempbansTimeout = null;
|
||||
state.ignoredEvents = [];
|
||||
pluginData.state.timers = [];
|
||||
// Massbans can take a while depending on rate limits,
|
||||
// so we're giving each massban 15 minutes to complete before launching the next massban
|
||||
state.massbanQueue = new Queue(15 * MINUTES);
|
||||
|
@ -210,11 +212,17 @@ export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()({
|
|||
},
|
||||
|
||||
afterLoad(pluginData) {
|
||||
outdatedTempbansLoop(pluginData);
|
||||
loadExpiringTimers(pluginData);
|
||||
pluginData.state.banClearIntervalId = setInterval(
|
||||
() => loadExpiringTimers(pluginData),
|
||||
EXPIRED_BANS_CHECK_INTERVAL,
|
||||
);
|
||||
},
|
||||
|
||||
beforeUnload(pluginData) {
|
||||
clearInterval(pluginData.state.banClearIntervalId);
|
||||
pluginData.state.unloaded = true;
|
||||
pluginData.state.events.removeAllListeners();
|
||||
pluginData.state.timers = [];
|
||||
},
|
||||
});
|
||||
|
|
|
@ -15,6 +15,8 @@ import { isBanned } from "../functions/isBanned";
|
|||
import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs";
|
||||
import { modActionsCmd } from "../types";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import moment from "moment";
|
||||
import { addTimer, removeTimer, removeTimerByUserId } from "../functions/outdatedTempbansLoop";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
|
@ -93,11 +95,18 @@ export const BanCmd = modActionsCmd({
|
|||
if (time && time > 0) {
|
||||
if (existingTempban) {
|
||||
pluginData.state.tempbans.updateExpiryTime(user.id, time, mod.id);
|
||||
removeTimer(pluginData, existingTempban);
|
||||
addTimer(pluginData, {
|
||||
...existingTempban,
|
||||
expires_at: moment().utc().add(time, "ms").format("YYYY-MM-DD HH:mm:ss"),
|
||||
});
|
||||
} else {
|
||||
pluginData.state.tempbans.addTempban(user.id, time, mod.id);
|
||||
const tempban = await pluginData.state.tempbans.addTempban(user.id, time, mod.id);
|
||||
addTimer(pluginData, tempban);
|
||||
}
|
||||
} else if (existingTempban) {
|
||||
pluginData.state.tempbans.clear(user.id);
|
||||
removeTimerByUserId(pluginData, user.id);
|
||||
}
|
||||
|
||||
// Create a new case for the updated ban since we never stored the old case id and log the action
|
||||
|
|
|
@ -10,6 +10,7 @@ import { formatReasonWithAttachments } from "../functions/formatReasonWithAttach
|
|||
import { ignoreEvent } from "../functions/ignoreEvent";
|
||||
import { IgnoredEventType, modActionsCmd } from "../types";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { removeTimerByUserId } from "../functions/outdatedTempbansLoop";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
|
@ -69,7 +70,7 @@ export const UnbanCmd = modActionsCmd({
|
|||
});
|
||||
// Delete the tempban, if one exists
|
||||
pluginData.state.tempbans.clear(user.id);
|
||||
|
||||
removeTimerByUserId(pluginData, user.id);
|
||||
// Confirm the action
|
||||
sendSuccessMessage(pluginData, msg.channel, `Member unbanned (Case #${createdCase.case_number})`);
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ import { BanOptions, BanResult, IgnoredEventType, ModActionsPluginType } from ".
|
|||
import { getDefaultContactMethods } from "./getDefaultContactMethods";
|
||||
import { ignoreEvent } from "./ignoreEvent";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { addTimer, removeTimer } from "./outdatedTempbansLoop";
|
||||
import moment from "moment";
|
||||
|
||||
/**
|
||||
* Ban the specified user id, whether or not they're actually on the server at the time. Generates a case.
|
||||
|
@ -109,8 +111,14 @@ export async function banUserId(
|
|||
const selfId = pluginData.client.user!.id;
|
||||
if (existingTempban) {
|
||||
pluginData.state.tempbans.updateExpiryTime(user.id, banTime, banOptions.modId ?? selfId);
|
||||
removeTimer(pluginData, existingTempban);
|
||||
addTimer(pluginData, {
|
||||
...existingTempban,
|
||||
expires_at: moment().utc().add(banTime, "ms").format("YYYY-MM-DD HH:mm:ss"),
|
||||
});
|
||||
} else {
|
||||
pluginData.state.tempbans.addTempban(user.id, banTime, banOptions.modId ?? selfId);
|
||||
const tempban = await pluginData.state.tempbans.addTempban(user.id, banTime, banOptions.modId ?? selfId);
|
||||
addTimer(pluginData, tempban);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,25 +4,86 @@ import { GuildPluginData } from "knub";
|
|||
import moment from "moment-timezone";
|
||||
import { LogType } from "src/data/LogType";
|
||||
import { logger } from "src/logger";
|
||||
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
|
||||
import { CaseTypes } from "../../../data/CaseTypes";
|
||||
import { resolveUser, SECONDS } from "../../../utils";
|
||||
import { MINUTES, resolveUser } from "../../../utils";
|
||||
import { CasesPlugin } from "../../Cases/CasesPlugin";
|
||||
import { IgnoredEventType, ModActionsPluginType } from "../types";
|
||||
import { formatReasonWithAttachments } from "./formatReasonWithAttachments";
|
||||
import { ignoreEvent } from "./ignoreEvent";
|
||||
import { isBanned } from "./isBanned";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { Tempban } from "src/data/entities/Tempban";
|
||||
import { ExpiringTimer } from "src/utils/timers";
|
||||
|
||||
const TEMPBAN_LOOP_TIME = 60 * SECONDS;
|
||||
const LOAD_LESS_THAN_MIN_COUNT = 60 * MINUTES;
|
||||
|
||||
export async function outdatedTempbansLoop(pluginData: GuildPluginData<ModActionsPluginType>) {
|
||||
const outdatedTempbans = await pluginData.state.tempbans.getExpiredTempbans();
|
||||
export function addTimer(pluginData: GuildPluginData<ModActionsPluginType>, tempban: Tempban) {
|
||||
const existingMute = pluginData.state.timers.find(
|
||||
(tm) => tm.options.key === tempban.user_id && tm.options.guildId === tempban.guild_id && !tm.done,
|
||||
); // for future-proof when you do global events
|
||||
if (!existingMute && tempban.expires_at) {
|
||||
const exp = moment(tempban.expires_at!).toDate().getTime() - moment.utc().toDate().getTime();
|
||||
const newTimer = new ExpiringTimer({
|
||||
key: tempban.user_id,
|
||||
guildId: tempban.guild_id,
|
||||
plugin: "tempban",
|
||||
expiry: exp,
|
||||
callback: async () => {
|
||||
await clearTempBan(pluginData, tempban);
|
||||
},
|
||||
});
|
||||
pluginData.state.timers.push(newTimer);
|
||||
}
|
||||
}
|
||||
|
||||
for (const tempban of outdatedTempbans) {
|
||||
export function removeTimer(pluginData: GuildPluginData<ModActionsPluginType>, tempban: Tempban) {
|
||||
const existingMute = pluginData.state.timers.findIndex(
|
||||
(tm) => tm.options.key === tempban.user_id && tm.options.guildId === tempban.guild_id && !tm.done,
|
||||
);
|
||||
if (existingMute) {
|
||||
const tm = pluginData.state.timers[existingMute];
|
||||
tm.clear();
|
||||
tm.done = true;
|
||||
pluginData.state.timers.splice(existingMute, 1);
|
||||
}
|
||||
}
|
||||
|
||||
export function removeTimerByUserId(pluginData: GuildPluginData<ModActionsPluginType>, user_id: Snowflake) {
|
||||
const existingMute = pluginData.state.timers.findIndex((tm) => tm.options.key === user_id && !tm.done);
|
||||
if (existingMute) {
|
||||
const tm = pluginData.state.timers[existingMute];
|
||||
tm.clear();
|
||||
tm.done = true;
|
||||
pluginData.state.timers.splice(existingMute, 1);
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadExpiringTimers(pluginData: GuildPluginData<ModActionsPluginType>) {
|
||||
const now = moment.utc().toDate().getTime();
|
||||
pluginData.state.timers = pluginData.state.timers.filter((tm) => !tm.done || !tm.timeout);
|
||||
const tempbans = (await pluginData.state.tempbans.getAllTempbans()).filter((m) => m.expires_at);
|
||||
const expiredBans = tempbans.filter((m) => now >= moment(m.expires_at!).toDate().getTime());
|
||||
const expiringBans = tempbans.filter(
|
||||
(m) => !expiredBans.find((exp) => exp.user_id === m.user_id && exp.guild_id === m.guild_id),
|
||||
);
|
||||
|
||||
for (const tempban of expiringBans) {
|
||||
const expires = moment(tempban.expires_at!).toDate().getTime();
|
||||
if (expires <= now) continue; // exclude expired mutes, just in case
|
||||
if (expires > now + LOAD_LESS_THAN_MIN_COUNT) continue; // exclude timers that are expiring in over 180 mins
|
||||
|
||||
addTimer(pluginData, tempban);
|
||||
}
|
||||
|
||||
for (const tempban of expiredBans) {
|
||||
await clearTempBan(pluginData, tempban);
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearTempBan(pluginData: GuildPluginData<ModActionsPluginType>, tempban: Tempban) {
|
||||
if (!(await isBanned(pluginData, tempban.user_id))) {
|
||||
pluginData.state.tempbans.clear(tempban.user_id);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, tempban.user_id);
|
||||
|
@ -62,9 +123,4 @@ export async function outdatedTempbansLoop(pluginData: GuildPluginData<ModAction
|
|||
reason,
|
||||
banTime: humanizeDuration(banTime),
|
||||
});
|
||||
}
|
||||
|
||||
if (!pluginData.state.unloaded) {
|
||||
pluginData.state.outdatedTempbansTimeout = setTimeout(() => outdatedTempbansLoop(pluginData), TEMPBAN_LOOP_TIME);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { TextChannel } from "discord.js";
|
|||
import { EventEmitter } from "events";
|
||||
import * as t from "io-ts";
|
||||
import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
|
||||
import { ExpiringTimer } from "src/utils/timers";
|
||||
import { Case } from "../../data/entities/Case";
|
||||
import { GuildCases } from "../../data/GuildCases";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
|
@ -70,9 +71,9 @@ export interface ModActionsPluginType extends BasePluginType {
|
|||
cases: GuildCases;
|
||||
tempbans: GuildTempbans;
|
||||
serverLogs: GuildLogs;
|
||||
|
||||
banClearIntervalId: Timeout;
|
||||
timers: ExpiringTimer[];
|
||||
unloaded: boolean;
|
||||
outdatedTempbansTimeout: Timeout | null;
|
||||
ignoredEvents: IIgnoredEvent[];
|
||||
massbanQueue: Queue;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { GuildMember, Snowflake } from "discord.js";
|
||||
import { EventEmitter } from "events";
|
||||
import { MINUTES } from "src/utils";
|
||||
import { GuildArchives } from "../../data/GuildArchives";
|
||||
import { GuildCases } from "../../data/GuildCases";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
|
@ -15,7 +16,7 @@ import { MutesCmd } from "./commands/MutesCmd";
|
|||
import { ClearActiveMuteOnMemberBanEvt } from "./events/ClearActiveMuteOnMemberBanEvt";
|
||||
import { ClearActiveMuteOnRoleRemovalEvt } from "./events/ClearActiveMuteOnRoleRemovalEvt";
|
||||
import { ReapplyActiveMuteOnJoinEvt } from "./events/ReapplyActiveMuteOnJoinEvt";
|
||||
import { clearExpiredMutes } from "./functions/clearExpiredMutes";
|
||||
import { loadExpiringTimers } from "./functions/clearExpiredMutes";
|
||||
import { muteUser } from "./functions/muteUser";
|
||||
import { offMutesEvent } from "./functions/offMutesEvent";
|
||||
import { onMutesEvent } from "./functions/onMutesEvent";
|
||||
|
@ -58,7 +59,7 @@ const defaultOptions = {
|
|||
],
|
||||
};
|
||||
|
||||
const EXPIRED_MUTE_CHECK_INTERVAL = 60 * 1000;
|
||||
const EXPIRED_MUTE_CHECK_INTERVAL = 30 * MINUTES;
|
||||
|
||||
export const MutesPlugin = zeppelinGuildPlugin<MutesPluginType>()({
|
||||
name: "mutes",
|
||||
|
@ -108,14 +109,14 @@ export const MutesPlugin = zeppelinGuildPlugin<MutesPluginType>()({
|
|||
pluginData.state.cases = GuildCases.getGuildInstance(pluginData.guild.id);
|
||||
pluginData.state.serverLogs = new GuildLogs(pluginData.guild.id);
|
||||
pluginData.state.archives = GuildArchives.getGuildInstance(pluginData.guild.id);
|
||||
|
||||
pluginData.state.timers = [];
|
||||
pluginData.state.events = new EventEmitter();
|
||||
},
|
||||
|
||||
afterLoad(pluginData) {
|
||||
clearExpiredMutes(pluginData);
|
||||
loadExpiringTimers(pluginData);
|
||||
pluginData.state.muteClearIntervalId = setInterval(
|
||||
() => clearExpiredMutes(pluginData),
|
||||
() => loadExpiringTimers(pluginData),
|
||||
EXPIRED_MUTE_CHECK_INTERVAL,
|
||||
);
|
||||
},
|
||||
|
@ -123,5 +124,6 @@ export const MutesPlugin = zeppelinGuildPlugin<MutesPluginType>()({
|
|||
beforeUnload(pluginData) {
|
||||
clearInterval(pluginData.state.muteClearIntervalId);
|
||||
pluginData.state.events.removeAllListeners();
|
||||
pluginData.state.timers = [];
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,15 +1,68 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { memberToTemplateSafeMember } from "../../../utils/templateSafeObjects";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import { resolveMember, UnknownUser, verboseUserMention } from "../../../utils";
|
||||
import { MINUTES, resolveMember, UnknownUser, verboseUserMention } from "../../../utils";
|
||||
import { memberRolesLock } from "../../../utils/lockNameHelpers";
|
||||
import { MutesPluginType } from "../types";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { ExpiringTimer } from "src/utils/timers";
|
||||
import { Mute } from "src/data/entities/Mute";
|
||||
import moment from "moment";
|
||||
|
||||
const LOAD_LESS_THAN_MIN_COUNT = 60 * MINUTES;
|
||||
|
||||
export function addTimer(pluginData: GuildPluginData<MutesPluginType>, mute: Mute) {
|
||||
const existingMute = pluginData.state.timers.find(
|
||||
(tm) => tm.options.key === mute.user_id && tm.options.guildId === mute.guild_id && !tm.done,
|
||||
); // for future-proof when you do global events
|
||||
if (!existingMute && mute.expires_at) {
|
||||
const exp = moment(mute.expires_at!).toDate().getTime() - moment.utc().toDate().getTime();
|
||||
const newTimer = new ExpiringTimer({
|
||||
key: mute.user_id,
|
||||
guildId: mute.guild_id,
|
||||
plugin: "mutes",
|
||||
expiry: exp,
|
||||
callback: async () => {
|
||||
await clearExpiredMute(pluginData, mute);
|
||||
},
|
||||
});
|
||||
pluginData.state.timers.push(newTimer);
|
||||
}
|
||||
}
|
||||
|
||||
export function removeTimer(pluginData: GuildPluginData<MutesPluginType>, mute: Mute) {
|
||||
const existingMute = pluginData.state.timers.findIndex(
|
||||
(tm) => tm.options.key === mute.user_id && tm.options.guildId === mute.guild_id && !tm.done,
|
||||
);
|
||||
if (existingMute) {
|
||||
const tm = pluginData.state.timers[existingMute];
|
||||
tm.clear();
|
||||
tm.done = true;
|
||||
pluginData.state.timers.splice(existingMute, 1);
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadExpiringTimers(pluginData: GuildPluginData<MutesPluginType>) {
|
||||
const now = moment.utc().toDate().getTime();
|
||||
pluginData.state.timers = pluginData.state.timers.filter((tm) => !tm.done || !tm.timeout);
|
||||
const mutes = (await pluginData.state.mutes.getAllTemporaryMutes()).filter((m) => m.expires_at);
|
||||
const expiredMutes = mutes.filter((m) => now >= moment(m.expires_at!).toDate().getTime());
|
||||
const expiringMutes = mutes.filter(
|
||||
(m) => !expiredMutes.find((exp) => exp.user_id === m.user_id && exp.guild_id === m.guild_id),
|
||||
);
|
||||
|
||||
for (const mute of expiringMutes) {
|
||||
const expires = moment(mute.expires_at!).toDate().getTime();
|
||||
if (expires <= now) continue; // exclude expired mutes, just in case
|
||||
if (expires > now + LOAD_LESS_THAN_MIN_COUNT) continue; // exclude timers that are expiring in over 180 mins
|
||||
|
||||
addTimer(pluginData, mute);
|
||||
}
|
||||
|
||||
export async function clearExpiredMutes(pluginData: GuildPluginData<MutesPluginType>) {
|
||||
const expiredMutes = await pluginData.state.mutes.getExpiredMutes();
|
||||
for (const mute of expiredMutes) {
|
||||
await clearExpiredMute(pluginData, mute);
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearExpiredMute(pluginData: GuildPluginData<MutesPluginType>, mute: Mute) {
|
||||
const member = await resolveMember(pluginData.client, pluginData.guild, mute.user_id, true);
|
||||
|
||||
if (member) {
|
||||
|
@ -46,5 +99,4 @@ export async function clearExpiredMutes(pluginData: GuildPluginData<MutesPluginT
|
|||
});
|
||||
|
||||
pluginData.state.events.emit("unmute", mute.user_id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ import {
|
|||
import { muteLock } from "../../../utils/lockNameHelpers";
|
||||
import { CasesPlugin } from "../../Cases/CasesPlugin";
|
||||
import { MuteOptions, MutesPluginType } from "../types";
|
||||
import { addTimer, removeTimer } from "./clearExpiredMutes";
|
||||
import moment from "moment";
|
||||
|
||||
export async function muteUser(
|
||||
pluginData: GuildPluginData<MutesPluginType>,
|
||||
|
@ -139,8 +141,14 @@ export async function muteUser(
|
|||
rolesToRestore = Array.from(new Set([...existingMute.roles_to_restore, ...rolesToRestore]));
|
||||
}
|
||||
await pluginData.state.mutes.updateExpiryTime(user.id, muteTime, rolesToRestore);
|
||||
removeTimer(pluginData, existingMute);
|
||||
addTimer(pluginData, {
|
||||
...existingMute,
|
||||
expires_at: moment().utc().add(muteTime, "ms").format("YYYY-MM-DD HH:mm:ss"),
|
||||
});
|
||||
} else {
|
||||
await pluginData.state.mutes.addMute(user.id, muteTime, rolesToRestore);
|
||||
const mute = await pluginData.state.mutes.addMute(user.id, muteTime, rolesToRestore);
|
||||
addTimer(pluginData, mute);
|
||||
}
|
||||
|
||||
const template = existingMute
|
||||
|
|
|
@ -11,6 +11,8 @@ import { CaseArgs } from "../../Cases/types";
|
|||
import { MutesPluginType, UnmuteResult } from "../types";
|
||||
import { memberHasMutedRole } from "./memberHasMutedRole";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { addTimer, removeTimer } from "./clearExpiredMutes";
|
||||
import moment from "moment";
|
||||
|
||||
export async function unmuteUser(
|
||||
pluginData: GuildPluginData<MutesPluginType>,
|
||||
|
@ -28,9 +30,15 @@ export async function unmuteUser(
|
|||
if (unmuteTime) {
|
||||
// Schedule timed unmute (= just set the mute's duration)
|
||||
if (!existingMute) {
|
||||
await pluginData.state.mutes.addMute(userId, unmuteTime);
|
||||
const mute = await pluginData.state.mutes.addMute(userId, unmuteTime);
|
||||
addTimer(pluginData, mute);
|
||||
} else {
|
||||
await pluginData.state.mutes.updateExpiryTime(userId, unmuteTime);
|
||||
removeTimer(pluginData, existingMute);
|
||||
addTimer(pluginData, {
|
||||
...existingMute,
|
||||
expires_at: moment().utc().add(unmuteTime, "ms").format("YYYY-MM-DD HH:mm:ss"),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Unmute immediately
|
||||
|
@ -60,6 +68,7 @@ export async function unmuteUser(
|
|||
);
|
||||
}
|
||||
if (existingMute) {
|
||||
removeTimer(pluginData, existingMute);
|
||||
await pluginData.state.mutes.clear(userId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { GuildMember } from "discord.js";
|
|||
import { EventEmitter } from "events";
|
||||
import * as t from "io-ts";
|
||||
import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
|
||||
import { ExpiringTimer } from "src/utils/timers";
|
||||
import { Case } from "../../data/entities/Case";
|
||||
import { Mute } from "../../data/entities/Mute";
|
||||
import { GuildArchives } from "../../data/GuildArchives";
|
||||
|
@ -54,6 +55,8 @@ export interface MutesPluginType extends BasePluginType {
|
|||
|
||||
muteClearIntervalId: Timeout;
|
||||
|
||||
timers: ExpiringTimer[];
|
||||
|
||||
events: MutesEventEmitter;
|
||||
};
|
||||
}
|
||||
|
|
40
backend/src/utils/timers.ts
Normal file
40
backend/src/utils/timers.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { Snowflake } from "discord-api-types";
|
||||
|
||||
type TimerCallback = (key: string, expiry: number) => void;
|
||||
|
||||
type TimerOptions = {
|
||||
key: Snowflake;
|
||||
guildId?: Snowflake;
|
||||
expiry: number;
|
||||
plugin?: string;
|
||||
callback: TimerCallback;
|
||||
};
|
||||
|
||||
export class ExpiringTimer {
|
||||
done: boolean = false;
|
||||
options: TimerOptions;
|
||||
timeout?: NodeJS.Timeout;
|
||||
data?: any; // idk how to make this take generic <T> typings data
|
||||
isValid() {
|
||||
return !this.done;
|
||||
}
|
||||
private execute() {
|
||||
if (!this.isValid()) return;
|
||||
this.options.callback(this.options.key, this.options.expiry);
|
||||
this.done = true;
|
||||
}
|
||||
init() {
|
||||
if (this.timeout) this.clear();
|
||||
this.timeout = setTimeout(() => this.execute(), this.options.expiry);
|
||||
}
|
||||
clear() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = undefined;
|
||||
}
|
||||
}
|
||||
constructor(options: TimerOptions) {
|
||||
this.options = options;
|
||||
this.init();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue