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,77 @@
import type { Command } from "commander";
import { messageCommand } from "../../../commands/message.js";
import { danger, setVerbose } from "../../../globals.js";
import { defaultRuntime } from "../../../runtime.js";
import { createDefaultDeps } from "../../deps.js";
export type MessageCliHelpers = {
withMessageBase: (command: Command) => Command;
withMessageTarget: (command: Command) => Command;
withRequiredMessageTarget: (command: Command) => Command;
runMessageAction: (
action: string,
opts: Record<string, unknown>,
) => Promise<void>;
};
export function createMessageCliHelpers(
message: Command,
messageChannelOptions: string,
): MessageCliHelpers {
const withMessageBase = (command: Command) =>
command
.option("--channel <channel>", `Channel: ${messageChannelOptions}`)
.option("--account <id>", "Channel account id (accountId)")
.option("--json", "Output result as JSON", false)
.option("--dry-run", "Print payload and skip sending", false)
.option("--verbose", "Verbose logging", false);
const withMessageTarget = (command: Command) =>
command.option(
"-t, --to <dest>",
"Recipient/channel: E.164 for WhatsApp/Signal, Telegram chat id/@username, Discord/Slack channel/user, or iMessage handle/chat_id",
);
const withRequiredMessageTarget = (command: Command) =>
command.requiredOption(
"-t, --to <dest>",
"Recipient/channel: E.164 for WhatsApp/Signal, Telegram chat id/@username, Discord/Slack channel/user, or iMessage handle/chat_id",
);
const runMessageAction = async (
action: string,
opts: Record<string, unknown>,
) => {
setVerbose(Boolean(opts.verbose));
const deps = createDefaultDeps();
try {
await messageCommand(
{
...(() => {
const { account, ...rest } = opts;
return {
...rest,
accountId: typeof account === "string" ? account : undefined,
};
})(),
action,
},
deps,
defaultRuntime,
);
} catch (err) {
defaultRuntime.error(danger(String(err)));
defaultRuntime.exit(1);
}
};
// `message` is only used for `message.help({ error: true })`, keep the
// command-specific helpers grouped here.
void message;
return {
withMessageBase,
withMessageTarget,
withRequiredMessageTarget,
runMessageAction,
};
}

View File

@@ -0,0 +1,166 @@
import type { Command } from "commander";
import type { MessageCliHelpers } from "./helpers.js";
export function registerMessageDiscordAdminCommands(
message: Command,
helpers: MessageCliHelpers,
) {
const role = message.command("role").description("Role actions");
helpers
.withMessageBase(
role
.command("info")
.description("List roles")
.requiredOption("--guild-id <id>", "Guild id"),
)
.action(async (opts) => {
await helpers.runMessageAction("role-info", opts);
});
helpers
.withMessageBase(
role
.command("add")
.description("Add role to a member")
.requiredOption("--guild-id <id>", "Guild id")
.requiredOption("--user-id <id>", "User id")
.requiredOption("--role-id <id>", "Role id"),
)
.action(async (opts) => {
await helpers.runMessageAction("role-add", opts);
});
helpers
.withMessageBase(
role
.command("remove")
.description("Remove role from a member")
.requiredOption("--guild-id <id>", "Guild id")
.requiredOption("--user-id <id>", "User id")
.requiredOption("--role-id <id>", "Role id"),
)
.action(async (opts) => {
await helpers.runMessageAction("role-remove", opts);
});
const channel = message.command("channel").description("Channel actions");
helpers
.withMessageBase(
channel
.command("info")
.description("Fetch channel info")
.requiredOption("--channel-id <id>", "Channel id"),
)
.action(async (opts) => {
await helpers.runMessageAction("channel-info", opts);
});
helpers
.withMessageBase(
channel
.command("list")
.description("List channels")
.requiredOption("--guild-id <id>", "Guild id"),
)
.action(async (opts) => {
await helpers.runMessageAction("channel-list", opts);
});
const member = message.command("member").description("Member actions");
helpers
.withMessageBase(
member
.command("info")
.description("Fetch member info")
.requiredOption("--user-id <id>", "User id"),
)
.option("--guild-id <id>", "Guild id (Discord)")
.action(async (opts) => {
await helpers.runMessageAction("member-info", opts);
});
const voice = message.command("voice").description("Voice actions");
helpers
.withMessageBase(
voice
.command("status")
.description("Fetch voice status")
.requiredOption("--guild-id <id>", "Guild id")
.requiredOption("--user-id <id>", "User id"),
)
.action(async (opts) => {
await helpers.runMessageAction("voice-status", opts);
});
const event = message.command("event").description("Event actions");
helpers
.withMessageBase(
event
.command("list")
.description("List scheduled events")
.requiredOption("--guild-id <id>", "Guild id"),
)
.action(async (opts) => {
await helpers.runMessageAction("event-list", opts);
});
helpers
.withMessageBase(
event
.command("create")
.description("Create a scheduled event")
.requiredOption("--guild-id <id>", "Guild id")
.requiredOption("--event-name <name>", "Event name")
.requiredOption("--start-time <iso>", "Event start time"),
)
.option("--end-time <iso>", "Event end time")
.option("--desc <text>", "Event description")
.option("--channel-id <id>", "Channel id")
.option("--location <text>", "Event location")
.option("--event-type <stage|external|voice>", "Event type")
.action(async (opts) => {
await helpers.runMessageAction("event-create", opts);
});
helpers
.withMessageBase(
message
.command("timeout")
.description("Timeout a member")
.requiredOption("--guild-id <id>", "Guild id")
.requiredOption("--user-id <id>", "User id"),
)
.option("--duration-min <n>", "Timeout duration minutes")
.option("--until <iso>", "Timeout until")
.option("--reason <text>", "Moderation reason")
.action(async (opts) => {
await helpers.runMessageAction("timeout", opts);
});
helpers
.withMessageBase(
message
.command("kick")
.description("Kick a member")
.requiredOption("--guild-id <id>", "Guild id")
.requiredOption("--user-id <id>", "User id"),
)
.option("--reason <text>", "Moderation reason")
.action(async (opts) => {
await helpers.runMessageAction("kick", opts);
});
helpers
.withMessageBase(
message
.command("ban")
.description("Ban a member")
.requiredOption("--guild-id <id>", "Guild id")
.requiredOption("--user-id <id>", "User id"),
)
.option("--reason <text>", "Moderation reason")
.option("--delete-days <n>", "Ban delete message days")
.action(async (opts) => {
await helpers.runMessageAction("ban", opts);
});
}

View File

@@ -0,0 +1,70 @@
import type { Command } from "commander";
import { collectOption } from "../helpers.js";
import type { MessageCliHelpers } from "./helpers.js";
export function registerMessageEmojiCommands(
message: Command,
helpers: MessageCliHelpers,
) {
const emoji = message.command("emoji").description("Emoji actions");
helpers
.withMessageBase(emoji.command("list").description("List emojis"))
.option("--guild-id <id>", "Guild id (Discord)")
.action(async (opts) => {
await helpers.runMessageAction("emoji-list", opts);
});
helpers
.withMessageBase(
emoji
.command("upload")
.description("Upload an emoji")
.requiredOption("--guild-id <id>", "Guild id"),
)
.requiredOption("--emoji-name <name>", "Emoji name")
.requiredOption("--media <path-or-url>", "Emoji media (path or URL)")
.option(
"--role-ids <id>",
"Role id (repeat)",
collectOption,
[] as string[],
)
.action(async (opts) => {
await helpers.runMessageAction("emoji-upload", opts);
});
}
export function registerMessageStickerCommands(
message: Command,
helpers: MessageCliHelpers,
) {
const sticker = message.command("sticker").description("Sticker actions");
helpers
.withMessageBase(
helpers.withRequiredMessageTarget(
sticker.command("send").description("Send stickers"),
),
)
.requiredOption("--sticker-id <id>", "Sticker id (repeat)", collectOption)
.option("-m, --message <text>", "Optional message body")
.action(async (opts) => {
await helpers.runMessageAction("sticker", opts);
});
helpers
.withMessageBase(
sticker
.command("upload")
.description("Upload a sticker")
.requiredOption("--guild-id <id>", "Guild id"),
)
.requiredOption("--sticker-name <name>", "Sticker name")
.requiredOption("--sticker-desc <text>", "Sticker description")
.requiredOption("--sticker-tags <tags>", "Sticker tags")
.requiredOption("--media <path-or-url>", "Sticker media (path or URL)")
.action(async (opts) => {
await helpers.runMessageAction("sticker-upload", opts);
});
}

View File

@@ -0,0 +1,49 @@
import type { Command } from "commander";
import { collectOption } from "../helpers.js";
import type { MessageCliHelpers } from "./helpers.js";
export function registerMessagePermissionsCommand(
message: Command,
helpers: MessageCliHelpers,
) {
helpers
.withMessageBase(
helpers.withMessageTarget(
message.command("permissions").description("Fetch channel permissions"),
),
)
.option("--channel-id <id>", "Channel id (defaults to --to)")
.action(async (opts) => {
await helpers.runMessageAction("permissions", opts);
});
}
export function registerMessageSearchCommand(
message: Command,
helpers: MessageCliHelpers,
) {
helpers
.withMessageBase(
message.command("search").description("Search Discord messages"),
)
.requiredOption("--guild-id <id>", "Guild id")
.requiredOption("--query <text>", "Search query")
.option("--channel-id <id>", "Channel id")
.option(
"--channel-ids <id>",
"Channel id (repeat)",
collectOption,
[] as string[],
)
.option("--author-id <id>", "Author id")
.option(
"--author-ids <id>",
"Author id (repeat)",
collectOption,
[] as string[],
)
.option("--limit <n>", "Result limit")
.action(async (opts) => {
await helpers.runMessageAction("search", opts);
});
}

View File

@@ -0,0 +1,53 @@
import type { Command } from "commander";
import type { MessageCliHelpers } from "./helpers.js";
export function registerMessagePinCommands(
message: Command,
helpers: MessageCliHelpers,
) {
const withPinsTarget = (command: Command) =>
command.option(
"--channel-id <id>",
"Channel id (defaults to --to; required for WhatsApp)",
);
const pins = [
helpers
.withMessageBase(
withPinsTarget(
helpers.withMessageTarget(
message.command("pin").description("Pin a message"),
),
),
)
.requiredOption("--message-id <id>", "Message id")
.action(async (opts) => {
await helpers.runMessageAction("pin", opts);
}),
helpers
.withMessageBase(
withPinsTarget(
helpers.withMessageTarget(
message.command("unpin").description("Unpin a message"),
),
),
)
.requiredOption("--message-id <id>", "Message id")
.action(async (opts) => {
await helpers.runMessageAction("unpin", opts);
}),
helpers
.withMessageBase(
helpers.withMessageTarget(
message.command("pins").description("List pinned messages"),
),
)
.option("--channel-id <id>", "Channel id (defaults to --to)")
.option("--limit <n>", "Result limit")
.action(async (opts) => {
await helpers.runMessageAction("list-pins", opts);
}),
] as const;
void pins;
}

View File

@@ -0,0 +1,28 @@
import type { Command } from "commander";
import { collectOption } from "../helpers.js";
import type { MessageCliHelpers } from "./helpers.js";
export function registerMessagePollCommand(
message: Command,
helpers: MessageCliHelpers,
) {
helpers
.withMessageBase(
helpers.withRequiredMessageTarget(
message.command("poll").description("Send a poll"),
),
)
.requiredOption("--poll-question <text>", "Poll question")
.option(
"--poll-option <choice>",
"Poll option (repeat 2-12 times)",
collectOption,
[] as string[],
)
.option("--poll-multi", "Allow multiple selections", false)
.option("--poll-duration-hours <n>", "Poll duration (Discord)")
.option("-m, --message <text>", "Optional message body")
.action(async (opts) => {
await helpers.runMessageAction("poll", opts);
});
}

View File

@@ -0,0 +1,36 @@
import type { Command } from "commander";
import type { MessageCliHelpers } from "./helpers.js";
export function registerMessageReactionsCommands(
message: Command,
helpers: MessageCliHelpers,
) {
helpers
.withMessageBase(
helpers.withMessageTarget(
message.command("react").description("Add or remove a reaction"),
),
)
.requiredOption("--message-id <id>", "Message id")
.option("--emoji <emoji>", "Emoji for reactions")
.option("--remove", "Remove reaction", false)
.option("--participant <id>", "WhatsApp reaction participant")
.option("--from-me", "WhatsApp reaction fromMe", false)
.option("--channel-id <id>", "Channel id (defaults to --to)")
.action(async (opts) => {
await helpers.runMessageAction("react", opts);
});
helpers
.withMessageBase(
helpers.withMessageTarget(
message.command("reactions").description("List reactions on a message"),
),
)
.requiredOption("--message-id <id>", "Message id")
.option("--limit <n>", "Result limit")
.option("--channel-id <id>", "Channel id (defaults to --to)")
.action(async (opts) => {
await helpers.runMessageAction("reactions", opts);
});
}

View File

@@ -0,0 +1,53 @@
import type { Command } from "commander";
import type { MessageCliHelpers } from "./helpers.js";
export function registerMessageReadEditDeleteCommands(
message: Command,
helpers: MessageCliHelpers,
) {
helpers
.withMessageBase(
helpers.withMessageTarget(
message.command("read").description("Read recent messages"),
),
)
.option("--limit <n>", "Result limit")
.option("--before <id>", "Read/search before id")
.option("--after <id>", "Read/search after id")
.option("--around <id>", "Read around id")
.option("--channel-id <id>", "Channel id (defaults to --to)")
.option("--include-thread", "Include thread replies (Discord)", false)
.action(async (opts) => {
await helpers.runMessageAction("read", opts);
});
helpers
.withMessageBase(
helpers.withMessageTarget(
message
.command("edit")
.description("Edit a message")
.requiredOption("--message-id <id>", "Message id")
.requiredOption("-m, --message <text>", "Message body"),
),
)
.option("--channel-id <id>", "Channel id (defaults to --to)")
.option("--thread-id <id>", "Thread id (Telegram forum thread)")
.action(async (opts) => {
await helpers.runMessageAction("edit", opts);
});
helpers
.withMessageBase(
helpers.withMessageTarget(
message
.command("delete")
.description("Delete a message")
.requiredOption("--message-id <id>", "Message id"),
),
)
.option("--channel-id <id>", "Channel id (defaults to --to)")
.action(async (opts) => {
await helpers.runMessageAction("delete", opts);
});
}

View File

@@ -0,0 +1,36 @@
import type { Command } from "commander";
import type { MessageCliHelpers } from "./helpers.js";
export function registerMessageSendCommand(
message: Command,
helpers: MessageCliHelpers,
) {
helpers
.withMessageBase(
helpers
.withRequiredMessageTarget(
message
.command("send")
.description("Send a message")
.requiredOption("-m, --message <text>", "Message body"),
)
.option(
"--media <path-or-url>",
"Attach media (image/audio/video/document). Accepts local paths or URLs.",
)
.option(
"--buttons <json>",
"Telegram inline keyboard buttons as JSON (array of button rows)",
)
.option("--reply-to <id>", "Reply-to message id")
.option("--thread-id <id>", "Thread id (Telegram forum thread)")
.option(
"--gif-playback",
"Treat video media as GIF playback (WhatsApp only).",
false,
),
)
.action(async (opts) => {
await helpers.runMessageAction("send", opts);
});
}

View File

@@ -0,0 +1,58 @@
import type { Command } from "commander";
import type { MessageCliHelpers } from "./helpers.js";
export function registerMessageThreadCommands(
message: Command,
helpers: MessageCliHelpers,
) {
const thread = message.command("thread").description("Thread actions");
helpers
.withMessageBase(
helpers.withMessageTarget(
thread
.command("create")
.description("Create a thread")
.requiredOption("--thread-name <name>", "Thread name"),
),
)
.option("--channel-id <id>", "Channel id (defaults to --to)")
.option("--message-id <id>", "Message id (optional)")
.option("--auto-archive-min <n>", "Thread auto-archive minutes")
.action(async (opts) => {
await helpers.runMessageAction("thread-create", opts);
});
helpers
.withMessageBase(
thread
.command("list")
.description("List threads")
.requiredOption("--guild-id <id>", "Guild id"),
)
.option("--channel-id <id>", "Channel id")
.option("--include-archived", "Include archived threads", false)
.option("--before <id>", "Read/search before id")
.option("--limit <n>", "Result limit")
.action(async (opts) => {
await helpers.runMessageAction("thread-list", opts);
});
helpers
.withMessageBase(
helpers.withRequiredMessageTarget(
thread
.command("reply")
.description("Reply in a thread")
.requiredOption("-m, --message <text>", "Message body"),
),
)
.option(
"--media <path-or-url>",
"Attach media (image/audio/video/document). Accepts local paths or URLs.",
)
.option("--reply-to <id>", "Reply-to message id")
.action(async (opts) => {
await helpers.runMessageAction("thread-reply", opts);
});
}