3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-05-11 04:45:02 +00:00

Centralize periodic checks for mutes, tempbans, vcalerts, reminders, and scheduled posts

This should result in a significant performance improvement.
The new method is also more precise than the old one, allowing
the aforementioned checks to be performed with second-precision.
This commit is contained in:
Dragory 2021-09-25 21:33:59 +03:00
parent c84d1a0be1
commit c7751a9da1
No known key found for this signature in database
GPG key ID: 5F387BA66DF8AAC1
55 changed files with 883 additions and 366 deletions

View file

@ -8,12 +8,13 @@ import { EditCmd } from "./commands/EditCmd";
import { EditEmbedCmd } from "./commands/EditEmbedCmd";
import { PostCmd } from "./commands/PostCmd";
import { PostEmbedCmd } from "./commands/PostEmbedCmd";
import { ScheduledPostsDeleteCmd } from "./commands/SchedluedPostsDeleteCmd";
import { ScheduledPostsDeleteCmd } from "./commands/ScheduledPostsDeleteCmd";
import { ScheduledPostsListCmd } from "./commands/ScheduledPostsListCmd";
import { ScheduledPostsShowCmd } from "./commands/ScheduledPostsShowCmd";
import { ConfigSchema, PostPluginType } from "./types";
import { scheduledPostLoop } from "./util/scheduledPostLoop";
import { LogsPlugin } from "../Logs/LogsPlugin";
import { onGuildEvent } from "../../data/GuildEvents";
import { postScheduledPost } from "./util/postScheduledPost";
const defaultOptions: PluginOptions<PostPluginType> = {
config: {
@ -60,10 +61,12 @@ export const PostPlugin = zeppelinGuildPlugin<PostPluginType>()({
},
afterLoad(pluginData) {
scheduledPostLoop(pluginData);
pluginData.state.unregisterGuildEventListener = onGuildEvent(pluginData.guild.id, "scheduledPost", (post) =>
postScheduledPost(pluginData, post),
);
},
beforeUnload(pluginData) {
clearTimeout(pluginData.state.scheduledPostLoopTimeout);
pluginData.state.unregisterGuildEventListener();
},
});

View file

@ -2,6 +2,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { sorter } from "../../../utils";
import { postCmd } from "../types";
import { clearUpcomingScheduledPost } from "../../../data/loops/upcomingScheduledPostsLoop";
export const ScheduledPostsDeleteCmd = postCmd({
trigger: ["scheduled_posts delete", "scheduled_posts d"],
@ -20,6 +21,7 @@ export const ScheduledPostsDeleteCmd = postCmd({
return;
}
clearUpcomingScheduledPost(post);
await pluginData.state.scheduledPosts.delete(post.id);
sendSuccessMessage(pluginData, msg.channel, "Scheduled post deleted!");
},

View file

@ -16,7 +16,7 @@ export interface PostPluginType extends BasePluginType {
scheduledPosts: GuildScheduledPosts;
logs: GuildLogs;
scheduledPostLoopTimeout: NodeJS.Timeout;
unregisterGuildEventListener: () => void;
};
}

View file

@ -11,6 +11,7 @@ import { PostPluginType } from "../types";
import { parseScheduleTime } from "./parseScheduleTime";
import { postMessage } from "./postMessage";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { registerUpcomingScheduledPost } from "../../../data/loops/upcomingScheduledPostsLoop";
const MIN_REPEAT_TIME = 5 * MINUTES;
const MAX_REPEAT_TIME = Math.pow(2, 32);
@ -141,7 +142,7 @@ export async function actualPostCmd(
return;
}
await pluginData.state.scheduledPosts.create({
const post = await pluginData.state.scheduledPosts.create({
author_id: msg.author.id,
author_name: msg.author.tag,
channel_id: targetChannel.id,
@ -153,6 +154,7 @@ export async function actualPostCmd(
repeat_until: repeatUntil ? repeatUntil.clone().tz("Etc/UTC").format(DBDateFormat) : null,
repeat_times: repeatTimes ?? null,
});
registerUpcomingScheduledPost(post);
if (opts.repeat) {
pluginData.getPlugin(LogsPlugin).logScheduledRepeatedMessage({

View file

@ -0,0 +1,78 @@
import { Snowflake, TextChannel, User } from "discord.js";
import { GuildPluginData } from "knub";
import moment from "moment-timezone";
import { logger } from "../../../logger";
import { DBDateFormat, verboseChannelMention, verboseUserMention } from "../../../utils";
import { PostPluginType } from "../types";
import { postMessage } from "./postMessage";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { ScheduledPost } from "../../../data/entities/ScheduledPost";
import { registerUpcomingScheduledPost } from "../../../data/loops/upcomingScheduledPostsLoop";
export async function postScheduledPost(pluginData: GuildPluginData<PostPluginType>, post: ScheduledPost) {
// First, update the scheduled post or delete it from the database *before* we try posting it.
// This ensures strange errors don't cause reposts.
let shouldClear = true;
if (post.repeat_interval) {
const nextPostAt = moment.utc().add(post.repeat_interval, "ms");
if (post.repeat_until) {
const repeatUntil = moment.utc(post.repeat_until, DBDateFormat);
if (nextPostAt.isSameOrBefore(repeatUntil)) {
await pluginData.state.scheduledPosts.update(post.id, {
post_at: nextPostAt.format(DBDateFormat),
});
shouldClear = false;
}
} else if (post.repeat_times) {
if (post.repeat_times > 1) {
await pluginData.state.scheduledPosts.update(post.id, {
post_at: nextPostAt.format(DBDateFormat),
repeat_times: post.repeat_times - 1,
});
shouldClear = false;
}
}
}
if (shouldClear) {
await pluginData.state.scheduledPosts.delete(post.id);
} else {
const upToDatePost = (await pluginData.state.scheduledPosts.find(post.id))!;
registerUpcomingScheduledPost(upToDatePost);
}
// Post the message
const channel = pluginData.guild.channels.cache.get(post.channel_id as Snowflake);
if (channel?.isText() || channel?.isThread()) {
const [username, discriminator] = post.author_name.split("#");
const author: User = (await pluginData.client.users.fetch(post.author_id as Snowflake)) || {
id: post.author_id,
username,
discriminator,
};
try {
const postedMessage = await postMessage(
pluginData,
channel,
post.content,
post.attachments,
post.enable_mentions,
);
pluginData.getPlugin(LogsPlugin).logPostedScheduledMessage({
author,
channel,
messageId: postedMessage.id,
});
} catch {
pluginData.getPlugin(LogsPlugin).logBotAlert({
body: `Failed to post scheduled message by ${verboseUserMention(author)} to ${verboseChannelMention(channel)}`,
});
logger.warn(
`Failed to post scheduled message to #${channel.name} (${channel.id}) on ${pluginData.guild.name} (${pluginData.guild.id})`,
);
}
}
}

View file

@ -1,84 +0,0 @@
import { Snowflake, TextChannel, User } from "discord.js";
import { GuildPluginData } from "knub";
import moment from "moment-timezone";
import { channelToTemplateSafeChannel, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { LogType } from "../../../data/LogType";
import { logger } from "../../../logger";
import { DBDateFormat, SECONDS, verboseChannelMention, verboseUserMention } from "../../../utils";
import { PostPluginType } from "../types";
import { postMessage } from "./postMessage";
import { LogsPlugin } from "../../Logs/LogsPlugin";
const SCHEDULED_POST_CHECK_INTERVAL = 5 * SECONDS;
export async function scheduledPostLoop(pluginData: GuildPluginData<PostPluginType>) {
const duePosts = await pluginData.state.scheduledPosts.getDueScheduledPosts();
for (const post of duePosts) {
const channel = pluginData.guild.channels.cache.get(post.channel_id as Snowflake);
if (channel instanceof TextChannel) {
const [username, discriminator] = post.author_name.split("#");
const author: User = (await pluginData.client.users.fetch(post.author_id as Snowflake)) || {
id: post.author_id,
username,
discriminator,
};
try {
const postedMessage = await postMessage(
pluginData,
channel,
post.content,
post.attachments,
post.enable_mentions,
);
pluginData.getPlugin(LogsPlugin).logPostedScheduledMessage({
author,
channel,
messageId: postedMessage.id,
});
} catch {
pluginData.getPlugin(LogsPlugin).logBotAlert({
body: `Failed to post scheduled message by ${verboseUserMention(author)} to ${verboseChannelMention(
channel,
)}`,
});
logger.warn(
`Failed to post scheduled message to #${channel.name} (${channel.id}) on ${pluginData.guild.name} (${pluginData.guild.id})`,
);
}
}
let shouldClear = true;
if (post.repeat_interval) {
const nextPostAt = moment.utc().add(post.repeat_interval, "ms");
if (post.repeat_until) {
const repeatUntil = moment.utc(post.repeat_until, DBDateFormat);
if (nextPostAt.isSameOrBefore(repeatUntil)) {
await pluginData.state.scheduledPosts.update(post.id, {
post_at: nextPostAt.format(DBDateFormat),
});
shouldClear = false;
}
} else if (post.repeat_times) {
if (post.repeat_times > 1) {
await pluginData.state.scheduledPosts.update(post.id, {
post_at: nextPostAt.format(DBDateFormat),
repeat_times: post.repeat_times - 1,
});
shouldClear = false;
}
}
}
if (shouldClear) {
await pluginData.state.scheduledPosts.delete(post.id);
}
}
pluginData.state.scheduledPostLoopTimeout = setTimeout(
() => scheduledPostLoop(pluginData),
SCHEDULED_POST_CHECK_INTERVAL,
);
}