refactor(src): split oversized modules

This commit is contained in:
Peter Steinberger
2026-01-14 01:08:15 +00:00
parent b2179de839
commit bcbfb357be
675 changed files with 91476 additions and 73453 deletions

View File

@@ -0,0 +1,234 @@
import { ChannelType, type Client } from "@buape/carbon";
import { Routes } from "discord-api-types/v10";
import { createReplyReferencePlanner } from "../../auto-reply/reply/reply-reference.js";
import type { ReplyToMode } from "../../config/config.js";
import { logVerbose } from "../../globals.js";
import { truncateUtf16Safe } from "../../utils.js";
import type { DiscordChannelConfigResolved } from "./allow-list.js";
import type { DiscordMessageEvent } from "./listeners.js";
import { resolveDiscordChannelInfo } from "./message-utils.js";
export type DiscordThreadChannel = {
id: string;
name?: string | null;
parentId?: string | null;
parent?: { id?: string; name?: string };
};
export type DiscordThreadStarter = {
text: string;
author: string;
timestamp?: number;
};
type DiscordThreadParentInfo = {
id?: string;
name?: string;
type?: ChannelType;
};
const DISCORD_THREAD_STARTER_CACHE = new Map<string, DiscordThreadStarter>();
function isDiscordThreadType(type: ChannelType | undefined): boolean {
return (
type === ChannelType.PublicThread ||
type === ChannelType.PrivateThread ||
type === ChannelType.AnnouncementThread
);
}
export function resolveDiscordThreadChannel(params: {
isGuildMessage: boolean;
message: DiscordMessageEvent["message"];
channelInfo: import("./message-utils.js").DiscordChannelInfo | null;
}): DiscordThreadChannel | null {
if (!params.isGuildMessage) return null;
const { message, channelInfo } = params;
const channel =
"channel" in message
? (message as { channel?: unknown }).channel
: undefined;
const isThreadChannel =
channel &&
typeof channel === "object" &&
"isThread" in channel &&
typeof (channel as { isThread?: unknown }).isThread === "function" &&
(channel as { isThread: () => boolean }).isThread();
if (isThreadChannel) return channel as unknown as DiscordThreadChannel;
if (!isDiscordThreadType(channelInfo?.type)) return null;
return {
id: message.channelId,
name: channelInfo?.name ?? undefined,
parentId: channelInfo?.parentId ?? undefined,
parent: undefined,
};
}
export async function resolveDiscordThreadParentInfo(params: {
client: Client;
threadChannel: DiscordThreadChannel;
channelInfo: import("./message-utils.js").DiscordChannelInfo | null;
}): Promise<DiscordThreadParentInfo> {
const { threadChannel, channelInfo, client } = params;
const parentId =
threadChannel.parentId ??
threadChannel.parent?.id ??
channelInfo?.parentId ??
undefined;
if (!parentId) return {};
let parentName = threadChannel.parent?.name;
const parentInfo = await resolveDiscordChannelInfo(client, parentId);
parentName = parentName ?? parentInfo?.name;
const parentType = parentInfo?.type;
return { id: parentId, name: parentName, type: parentType };
}
export async function resolveDiscordThreadStarter(params: {
channel: DiscordThreadChannel;
client: Client;
parentId?: string;
parentType?: ChannelType;
resolveTimestampMs: (value?: string | null) => number | undefined;
}): Promise<DiscordThreadStarter | null> {
const cacheKey = params.channel.id;
const cached = DISCORD_THREAD_STARTER_CACHE.get(cacheKey);
if (cached) return cached;
try {
const parentType = params.parentType;
const isForumParent =
parentType === ChannelType.GuildForum ||
parentType === ChannelType.GuildMedia;
const messageChannelId = isForumParent
? params.channel.id
: params.parentId;
if (!messageChannelId) return null;
const starter = (await params.client.rest.get(
Routes.channelMessage(messageChannelId, params.channel.id),
)) as {
content?: string | null;
embeds?: Array<{ description?: string | null }>;
member?: { nick?: string | null; displayName?: string | null };
author?: {
id?: string | null;
username?: string | null;
discriminator?: string | null;
};
timestamp?: string | null;
};
if (!starter) return null;
const text =
starter.content?.trim() ?? starter.embeds?.[0]?.description?.trim() ?? "";
if (!text) return null;
const author =
starter.member?.nick ??
starter.member?.displayName ??
(starter.author
? starter.author.discriminator && starter.author.discriminator !== "0"
? `${starter.author.username ?? "Unknown"}#${starter.author.discriminator}`
: (starter.author.username ?? starter.author.id ?? "Unknown")
: "Unknown");
const timestamp = params.resolveTimestampMs(starter.timestamp);
const payload: DiscordThreadStarter = {
text,
author,
timestamp: timestamp ?? undefined,
};
DISCORD_THREAD_STARTER_CACHE.set(cacheKey, payload);
return payload;
} catch {
return null;
}
}
export function resolveDiscordReplyTarget(opts: {
replyToMode: ReplyToMode;
replyToId?: string;
hasReplied: boolean;
}): string | undefined {
if (opts.replyToMode === "off") return undefined;
const replyToId = opts.replyToId?.trim();
if (!replyToId) return undefined;
if (opts.replyToMode === "all") return replyToId;
return opts.hasReplied ? undefined : replyToId;
}
export function sanitizeDiscordThreadName(
rawName: string,
fallbackId: string,
): string {
const cleanedName = rawName
.replace(/<@!?\d+>/g, "") // user mentions
.replace(/<@&\d+>/g, "") // role mentions
.replace(/<#\d+>/g, "") // channel mentions
.replace(/\s+/g, " ")
.trim();
const baseSource = cleanedName || `Thread ${fallbackId}`;
const base = truncateUtf16Safe(baseSource, 80);
return truncateUtf16Safe(base, 100) || `Thread ${fallbackId}`;
}
type DiscordReplyDeliveryPlan = {
deliverTarget: string;
replyTarget: string;
replyReference: ReturnType<typeof createReplyReferencePlanner>;
};
export async function maybeCreateDiscordAutoThread(params: {
client: Client;
message: DiscordMessageEvent["message"];
isGuildMessage: boolean;
channelConfig?: DiscordChannelConfigResolved | null;
threadChannel?: DiscordThreadChannel | null;
baseText: string;
combinedBody: string;
}): Promise<string | undefined> {
if (!params.isGuildMessage) return undefined;
if (!params.channelConfig?.autoThread) return undefined;
if (params.threadChannel) return undefined;
try {
const threadName = sanitizeDiscordThreadName(
params.baseText || params.combinedBody || "Thread",
params.message.id,
);
const created = (await params.client.rest.post(
`${Routes.channelMessage(params.message.channelId, params.message.id)}/threads`,
{
body: {
name: threadName,
auto_archive_duration: 60,
},
},
)) as { id?: string };
const createdId = created?.id ? String(created.id) : "";
return createdId || undefined;
} catch (err) {
logVerbose(
`discord: autoThread failed for ${params.message.channelId}/${params.message.id}: ${String(err)}`,
);
return undefined;
}
}
export function resolveDiscordReplyDeliveryPlan(params: {
replyTarget: string;
replyToMode: ReplyToMode;
messageId: string;
threadChannel?: DiscordThreadChannel | null;
createdThreadId?: string | null;
}): DiscordReplyDeliveryPlan {
const originalReplyTarget = params.replyTarget;
let deliverTarget = originalReplyTarget;
let replyTarget = originalReplyTarget;
if (params.createdThreadId) {
deliverTarget = `channel:${params.createdThreadId}`;
replyTarget = deliverTarget;
}
const allowReference = deliverTarget === originalReplyTarget;
const replyReference = createReplyReferencePlanner({
replyToMode: allowReference ? params.replyToMode : "off",
existingId: params.threadChannel ? params.messageId : undefined,
startId: params.messageId,
allowReference,
});
return { deliverTarget, replyTarget, replyReference };
}