mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-07-07 11:07:19 +00:00
123 lines
4.1 KiB
TypeScript
123 lines
4.1 KiB
TypeScript
import { GuildBasedChannel, Message, OmitPartialGroupDMChannel, Snowflake, TextBasedChannel } from "discord.js";
|
|
import { GuildPluginData } from "knub";
|
|
import { SavedMessage } from "../../../data/entities/SavedMessage.js";
|
|
import { humanizeDurationShort } from "../../../humanizeDuration.js";
|
|
import { allowTimeout } from "../../../RegExpRunner.js";
|
|
import { DAYS, getInviteCodesInString } from "../../../utils.js";
|
|
import { snowflakeToTimestamp } from "../../../utils/snowflakeToTimestamp.js";
|
|
import { UtilityPluginType } from "../types.js";
|
|
|
|
const MAX_CLEAN_COUNT = 300;
|
|
const MAX_CLEAN_TIME = 1 * DAYS;
|
|
const MAX_CLEAN_API_REQUESTS = 20;
|
|
|
|
export interface FetchChannelMessagesToCleanOpts {
|
|
count: number;
|
|
beforeId: string;
|
|
upToId?: string;
|
|
authorId?: string;
|
|
includePins?: boolean;
|
|
onlyBotMessages?: boolean;
|
|
onlyWithInvites?: boolean;
|
|
matchContent?: RegExp;
|
|
}
|
|
|
|
export interface SuccessResult {
|
|
messages: SavedMessage[];
|
|
note: string;
|
|
}
|
|
|
|
export interface ErrorResult {
|
|
error: string;
|
|
}
|
|
|
|
export type FetchChannelMessagesToCleanResult = SuccessResult | ErrorResult;
|
|
|
|
export async function fetchChannelMessagesToClean(
|
|
pluginData: GuildPluginData<UtilityPluginType>,
|
|
targetChannel: GuildBasedChannel & TextBasedChannel,
|
|
opts: FetchChannelMessagesToCleanOpts,
|
|
): Promise<FetchChannelMessagesToCleanResult> {
|
|
if (opts.count > MAX_CLEAN_COUNT || opts.count <= 0) {
|
|
return { error: `Clean count must be between 1 and ${MAX_CLEAN_COUNT}` };
|
|
}
|
|
|
|
const result: FetchChannelMessagesToCleanResult = {
|
|
messages: [],
|
|
note: "",
|
|
};
|
|
|
|
const timestampCutoff = snowflakeToTimestamp(opts.beforeId) - MAX_CLEAN_TIME;
|
|
let foundId = false;
|
|
|
|
let pinIds: Set<Snowflake> = new Set();
|
|
if (!opts.includePins) {
|
|
pinIds = new Set((await targetChannel.messages.fetchPinned()).keys());
|
|
}
|
|
|
|
const rawMessagesToClean: Array<OmitPartialGroupDMChannel<Message<true>>> = [];
|
|
let beforeId = opts.beforeId;
|
|
let requests = 0;
|
|
while (rawMessagesToClean.length < opts.count) {
|
|
const potentialMessages = await targetChannel.messages.fetch({
|
|
before: beforeId,
|
|
limit: 100,
|
|
});
|
|
if (potentialMessages.size === 0) break;
|
|
|
|
requests++;
|
|
|
|
const filtered: Array<OmitPartialGroupDMChannel<Message<true>>> = [];
|
|
for (const message of potentialMessages.values()) {
|
|
const contentString = message.content || "";
|
|
if (opts.authorId && message.author.id !== opts.authorId) continue;
|
|
if (opts.onlyBotMessages && !message.author.bot) continue;
|
|
if (pinIds.has(message.id)) continue;
|
|
if (opts.onlyWithInvites && getInviteCodesInString(contentString).length === 0) continue;
|
|
if (opts.upToId && message.id < opts.upToId) {
|
|
foundId = true;
|
|
break;
|
|
}
|
|
if (message.createdTimestamp < timestampCutoff) continue;
|
|
if (
|
|
opts.matchContent &&
|
|
!(await pluginData.state.regexRunner.exec(opts.matchContent, contentString).catch(allowTimeout))
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
filtered.push(message);
|
|
}
|
|
const remaining = opts.count - rawMessagesToClean.length;
|
|
const withoutOverflow = filtered.slice(0, remaining);
|
|
rawMessagesToClean.push(...withoutOverflow);
|
|
|
|
beforeId = potentialMessages.lastKey()!;
|
|
|
|
if (foundId) {
|
|
break;
|
|
}
|
|
|
|
if (rawMessagesToClean.length < opts.count) {
|
|
if (potentialMessages.last()!.createdTimestamp < timestampCutoff) {
|
|
result.note = `stopped looking after reaching ${humanizeDurationShort(MAX_CLEAN_TIME)} old messages`;
|
|
break;
|
|
}
|
|
|
|
if (requests >= MAX_CLEAN_API_REQUESTS) {
|
|
result.note = `stopped looking after ${requests * 100} messages`;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Discord messages -> SavedMessages
|
|
const existingStored = await pluginData.state.savedMessages.getMultiple(rawMessagesToClean.map((m) => m.id));
|
|
const alreadyStored = existingStored.map((stored) => stored.id);
|
|
const messagesToStore = rawMessagesToClean.filter((potentialMsg) => !alreadyStored.includes(potentialMsg.id));
|
|
await pluginData.state.savedMessages.createFromMessages(messagesToStore);
|
|
|
|
result.messages = await pluginData.state.savedMessages.getMultiple(rawMessagesToClean.map((m) => m.id));
|
|
|
|
return result;
|
|
}
|