feat: initial prototype (#3)
Node.js CI / Lint and Test (push) Successful in 43s

### Explanation

_No response_

### Issue

_No response_

### Attestations

- [x] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)
- [x] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
- [x] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/).

### Dependencies

- [x] I have pinned the dependencies to a specific patch version.

### Style

- [x] I have run the linter and resolved any errors.
- [x] My pull request uses an appropriate title, matching the conventional commit standards.
- [x] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request.

### Tests

- [ ] My contribution adds new code, and I have added tests to cover it.
- [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes.
- [ ] All new and existing tests pass locally with my changes.
- [ ] Code coverage remains at or above the configured threshold.

### Documentation

_No response_

### Versioning

_No response_

Reviewed-on: #3
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit was merged in pull request #3.
This commit is contained in:
2025-07-19 19:51:50 -07:00
committed by Naomi Carrigan
parent 78b4da6235
commit c84b984cb2
24 changed files with 5421 additions and 14 deletions
+89
View File
@@ -0,0 +1,89 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import {
TextDisplayBuilder,
SeparatorBuilder,
SeparatorSpacingSize,
ContainerBuilder,
ButtonBuilder,
ButtonStyle,
ActionRowBuilder,
MessageFlags,
} from "discord.js";
import { errorHandler } from "../utils/errorHandler.js";
import type { Command } from "../interfaces/command.js";
/**
* Handles the `/about` command interaction.
* @param _sorielle - Sorielle's Discord instance (unused).
* @param interaction - The command interaction payload from Discord.
*/
// eslint-disable-next-line max-lines-per-function -- It's mostly components.
export const about: Command = async(_sorielle, interaction) => {
try {
const components = [
new ContainerBuilder().
addTextDisplayComponents(
new TextDisplayBuilder().setContent("# About Sorielle"),
).
addTextDisplayComponents(
new TextDisplayBuilder().setContent(
"Hi there~! I am Sorielle, a bot that shares ASCII art.",
),
).
addSeparatorComponents(
new SeparatorBuilder().
setSpacing(SeparatorSpacingSize.Small).
setDivider(true),
).
addTextDisplayComponents(
new TextDisplayBuilder().setContent("## What can I do?"),
).
addTextDisplayComponents(
new TextDisplayBuilder().setContent(
// eslint-disable-next-line stylistic/max-len -- Big boi string.
"To get started, you will need to purchase the server subscription from my Discord store. Then you, or any of your server admins, can configure a venting channel and a timeout. Once your settings are configured, any message that gets sent in your specified venting channel will be automatically deleted after the timeout period.",
),
).
addSeparatorComponents(
new SeparatorBuilder().
setSpacing(SeparatorSpacingSize.Small).
setDivider(true),
).
addTextDisplayComponents(
new TextDisplayBuilder().setContent("## What if I need help?"),
).
addTextDisplayComponents(
new TextDisplayBuilder().setContent(
// eslint-disable-next-line stylistic/max-len -- Big boi string.
"My deepest apologies if I have made a mistake! Please reach out to us in our Discord server or on the forum, and we will do our best to assist you.",
),
),
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder().
setStyle(ButtonStyle.Link).
setLabel("Discord Server").
setURL("https://chat.nhcarrigan.com"),
new ButtonBuilder().
setStyle(ButtonStyle.Link).
setLabel("Forum").
setURL("https://forum.nhcarrigan.com"),
),
];
await interaction.reply({
components: components,
flags: MessageFlags.IsComponentsV2,
});
} catch (error) {
await errorHandler(error, "about command");
await interaction.reply({
content:
// eslint-disable-next-line stylistic/max-len -- Big boi string.
"An error occurred while processing your request. Please try again later.",
});
}
};
+100
View File
@@ -0,0 +1,100 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import {
TextDisplayBuilder,
ContainerBuilder,
ButtonBuilder,
ButtonStyle,
ActionRowBuilder,
MessageFlags,
ChannelType,
PermissionFlagsBits,
} from "discord.js";
import { sendUnentitledResponse } from "../modules/sendUnentitledResponse.js";
import { checkGuildEntitlement } from "../utils/checkEntitlement.js";
import { errorHandler } from "../utils/errorHandler.js";
import type { Command } from "../interfaces/command.js";
/**
* Handles the `/config` command interaction.
* @param _sorielle - Sorielle's Discord instance (unused).
* @param interaction - The command interaction payload from Discord.
*/
/**
* Handles the `/config` command interaction.
* @param sorielle - Sorielle's Discord instance.
* @param interaction - The command interaction payload from Discord.
*/
// eslint-disable-next-line max-lines-per-function -- It's mostly components.
export const config: Command = async(sorielle, interaction) => {
try {
const isEntitled = await checkGuildEntitlement(sorielle, interaction.guild);
if (!isEntitled) {
await sendUnentitledResponse(interaction);
return;
}
await interaction.deferReply({
flags: [ MessageFlags.Ephemeral ],
});
const channel = interaction.options.getChannel("channel", true, [
ChannelType.GuildText,
]);
const permissions = interaction.guild.members.me?.permissionsIn(channel);
const hasAllPermissions
= permissions !== undefined
&& permissions.has(PermissionFlagsBits.ManageMessages)
&& permissions.has(PermissionFlagsBits.ViewChannel)
&& permissions.has(PermissionFlagsBits.ReadMessageHistory);
if (!hasAllPermissions) {
await interaction.editReply({
content:
// eslint-disable-next-line stylistic/max-len -- Big boi string.
"I do not have permission to manage messages in that channel. Please ensure I have the `Manage Messages`, `View Channel`, and `Read Message History` permissions.",
});
return;
}
const seconds = interaction.options.getInteger("timeout", true);
const components = [
new ContainerBuilder().addTextDisplayComponents(
new TextDisplayBuilder().setContent(
`Messages in <#${
channel.id
}> will be deleted after ${seconds.toLocaleString("en-GB")} seconds.`,
),
),
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder().
setStyle(ButtonStyle.Link).
setLabel("Discord Server").
setURL("https://chat.nhcarrigan.com"),
new ButtonBuilder().
setStyle(ButtonStyle.Link).
setLabel("Forum").
setURL("https://forum.nhcarrigan.com"),
),
];
await interaction.editReply({
components: components,
flags: [ MessageFlags.IsComponentsV2 ],
});
} catch (error) {
await errorHandler(error, "config command");
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- We want to ensure the interaction is replied to.
interaction.replied
? await interaction.editReply({
content:
// eslint-disable-next-line stylistic/max-len -- Big boi string.
"An error occurred while processing your request. Please try again later.",
})
: await interaction.reply({
content:
// eslint-disable-next-line stylistic/max-len -- Big boi string.
"An error occurred while processing your request. Please try again later.",
flags: [ MessageFlags.Ephemeral ],
});
}
};