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:
parent
c84d1a0be1
commit
c7751a9da1
55 changed files with 883 additions and 366 deletions
|
@ -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();
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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!");
|
||||
},
|
|
@ -16,7 +16,7 @@ export interface PostPluginType extends BasePluginType {
|
|||
scheduledPosts: GuildScheduledPosts;
|
||||
logs: GuildLogs;
|
||||
|
||||
scheduledPostLoopTimeout: NodeJS.Timeout;
|
||||
unregisterGuildEventListener: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
78
backend/src/plugins/Post/util/postScheduledPost.ts
Normal file
78
backend/src/plugins/Post/util/postScheduledPost.ts
Normal 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})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue