generated from nhcarrigan/template
This commit is contained in:
@@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* @copyright NHCarrigan
|
||||||
|
* @license Naomi's Public License
|
||||||
|
* @author Naomi Carrigan
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { MessageFlags } from "discord.js";
|
||||||
|
import { checkGuildEntitlement } from "../modules/checkEntitlement.js";
|
||||||
|
import { sendUnentitledResponse } from "../modules/sendUnentitledResponse.js";
|
||||||
|
import { errorHandler } from "../utils/errorHandler.js";
|
||||||
|
import type { Button } from "../interfaces/button.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the `/leaderboard` button.
|
||||||
|
* @param pavelle - Pavelle's instance.
|
||||||
|
* @param interaction - The interaction payload from Discord.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line max-lines-per-function -- Lazy.
|
||||||
|
export const leaderboard: Button = async(pavelle, interaction) => {
|
||||||
|
try {
|
||||||
|
const isEntitled = await checkGuildEntitlement(pavelle, interaction.guild);
|
||||||
|
if (!isEntitled) {
|
||||||
|
await sendUnentitledResponse(interaction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const members = await pavelle.db.users.findMany({
|
||||||
|
where: {
|
||||||
|
serverId: interaction.guild.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sorted = members.sort((a, b) => {
|
||||||
|
return b.points - a.points;
|
||||||
|
});
|
||||||
|
|
||||||
|
const topTen = sorted.slice(0, 10).map((member, index) => {
|
||||||
|
return `- **#${(index + 1).toString()}:** <@${member.userId}> - ${member.points.toString()} point(s).`;
|
||||||
|
}).
|
||||||
|
join("\n");
|
||||||
|
const yourScore = sorted.find((member) => {
|
||||||
|
return member.userId === interaction.member.id;
|
||||||
|
});
|
||||||
|
const yourRank = yourScore
|
||||||
|
? `You are rank #${(sorted.indexOf(yourScore) + 1).toString()} with ${yourScore.points.toString()} points.`
|
||||||
|
: "You are not ranked. Try throwing some stuff!";
|
||||||
|
await interaction.editReply({
|
||||||
|
allowedMentions: {
|
||||||
|
parse: [],
|
||||||
|
},
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- Discord API convention.
|
||||||
|
accent_color: null,
|
||||||
|
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
content: "# Leaderboard",
|
||||||
|
type: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
divider: true,
|
||||||
|
spacing: 1,
|
||||||
|
type: 14,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "## Top 10 Members",
|
||||||
|
type: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: topTen,
|
||||||
|
type: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: `-# ${yourRank}`,
|
||||||
|
type: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
spoiler: false,
|
||||||
|
type: 17,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
flags: [ MessageFlags.IsComponentsV2 ],
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const id = await errorHandler(error, "leaderboard command");
|
||||||
|
await interaction.editReply({
|
||||||
|
content:
|
||||||
|
`An error occurred while processing your request. Please try again later, or join our support server and provide this ID: ${id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* @copyright NHCarrigan
|
||||||
|
* @license Naomi's Public License
|
||||||
|
* @author Naomi Carrigan
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { MessageFlags } from "discord.js";
|
||||||
|
import { checkGuildEntitlement } from "../modules/checkEntitlement.js";
|
||||||
|
import { generateScore } from "../modules/generateScore.js";
|
||||||
|
import { getCachedCount } from "../modules/getCachedCount.js";
|
||||||
|
import { getConfig } from "../modules/getConfig.js";
|
||||||
|
import { getThrowComponents } from "../modules/getThrowComponents.js";
|
||||||
|
import { sendUnentitledResponse } from "../modules/sendUnentitledResponse.js";
|
||||||
|
import { errorHandler } from "../utils/errorHandler.js";
|
||||||
|
import { getRandomValue } from "../utils/getRandomValue.js";
|
||||||
|
import type { Button } from "../interfaces/button.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the `/throw` button interaction.
|
||||||
|
* @param pavelle - Pavelle's Discord instance.
|
||||||
|
* @param interaction - The command interaction payload from Discord.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line max-lines-per-function -- Big logic, but lotsa components
|
||||||
|
export const throwButton: Button = async(pavelle, interaction) => {
|
||||||
|
try {
|
||||||
|
const { member, guild } = interaction;
|
||||||
|
const count = getCachedCount(pavelle, `${guild.id}-${member.id}`);
|
||||||
|
if (count <= 0) {
|
||||||
|
await interaction.editReply({
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- Discord API convention.
|
||||||
|
accent_color: null,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
content:
|
||||||
|
// eslint-disable-next-line stylistic/max-len -- long string.
|
||||||
|
"Oopsie! You are out of throws! Your count resets at the top of every hour.",
|
||||||
|
type: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
spoiler: false,
|
||||||
|
type: 17,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
flags: [ MessageFlags.IsComponentsV2 ],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isEntitled = await checkGuildEntitlement(pavelle, guild);
|
||||||
|
if (!isEntitled) {
|
||||||
|
await sendUnentitledResponse(interaction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pavelle.throwCache.set(`${guild.id}-${member.id}`, count - 1);
|
||||||
|
await guild.members.fetch().catch(() => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
const target = getRandomValue([ ...guild.members.cache.values() ]);
|
||||||
|
const score = generateScore();
|
||||||
|
const { theme, spoiler } = await getConfig(pavelle, guild.id);
|
||||||
|
const updated = await pavelle.db.users.upsert({
|
||||||
|
create: {
|
||||||
|
points: score,
|
||||||
|
serverId: guild.id,
|
||||||
|
userId: member.id,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
points: {
|
||||||
|
increment: score,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- Prisma index convention.
|
||||||
|
serverId_userId: {
|
||||||
|
serverId: guild.id,
|
||||||
|
userId: member.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const components = getThrowComponents(
|
||||||
|
pavelle,
|
||||||
|
member.id,
|
||||||
|
target.id,
|
||||||
|
guild.id,
|
||||||
|
theme,
|
||||||
|
score,
|
||||||
|
spoiler,
|
||||||
|
updated.points,
|
||||||
|
);
|
||||||
|
await interaction.editReply({
|
||||||
|
allowedMentions: {
|
||||||
|
parse: [ ],
|
||||||
|
},
|
||||||
|
components: components,
|
||||||
|
flags: [ MessageFlags.IsComponentsV2 ],
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const id = await errorHandler(error, "throw command");
|
||||||
|
await interaction.editReply({
|
||||||
|
content:
|
||||||
|
`An error occurred while processing your request. Please try again later, or join our support server and provide this ID: ${id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { MessageFlags } from "discord.js";
|
import { MessageFlags } from "discord.js";
|
||||||
|
import { checkGuildEntitlement } from "../modules/checkEntitlement.js";
|
||||||
|
import { sendUnentitledResponse } from "../modules/sendUnentitledResponse.js";
|
||||||
import { errorHandler } from "../utils/errorHandler.js";
|
import { errorHandler } from "../utils/errorHandler.js";
|
||||||
import type { Command } from "../interfaces/command.js";
|
import type { Command } from "../interfaces/command.js";
|
||||||
|
|
||||||
@@ -16,6 +18,11 @@ import type { Command } from "../interfaces/command.js";
|
|||||||
// eslint-disable-next-line max-lines-per-function -- Lazy.
|
// eslint-disable-next-line max-lines-per-function -- Lazy.
|
||||||
export const leaderboard: Command = async(pavelle, interaction) => {
|
export const leaderboard: Command = async(pavelle, interaction) => {
|
||||||
try {
|
try {
|
||||||
|
const isEntitled = await checkGuildEntitlement(pavelle, interaction.guild);
|
||||||
|
if (!isEntitled) {
|
||||||
|
await sendUnentitledResponse(interaction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const members = await pavelle.db.users.findMany({
|
const members = await pavelle.db.users.findMany({
|
||||||
where: {
|
where: {
|
||||||
serverId: interaction.guild.id,
|
serverId: interaction.guild.id,
|
||||||
|
|||||||
+20
-3
@@ -4,22 +4,39 @@
|
|||||||
* @author Naomi Carrigan
|
* @author Naomi Carrigan
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { leaderboard as leaderboardButton } from "../buttons/leaderboard.js";
|
||||||
|
import { throwButton } from "../buttons/throwButton.js";
|
||||||
import { about } from "../commands/about.js";
|
import { about } from "../commands/about.js";
|
||||||
import { config } from "../commands/config.js";
|
import { config } from "../commands/config.js";
|
||||||
import { leaderboard } from "../commands/leaderboard.js";
|
import { leaderboard } from "../commands/leaderboard.js";
|
||||||
import { throwCmd } from "../commands/throwCmd.js";
|
import { throwCmd } from "../commands/throwCmd.js";
|
||||||
|
import type { Button } from "../interfaces/button.js";
|
||||||
import type { Command } from "../interfaces/command.js";
|
import type { Command } from "../interfaces/command.js";
|
||||||
|
|
||||||
const defaultHandler: Command = async(_lynira, interaction) => {
|
const defaultCommandHandler: Command = async(_lynira, interaction) => {
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
content: "This command is not implemented yet.",
|
content: "This command is not implemented yet.",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handlers: { _default: Command } & Record<string, Command> = {
|
const defaultButtonHandler: Button = async(_pavelle, interaction) => {
|
||||||
_default: defaultHandler,
|
await interaction.editReply({
|
||||||
|
content: "This button is not implemented yet.",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const commandHandlers: { _default: Command } & Record<string, Command> = {
|
||||||
|
_default: defaultCommandHandler,
|
||||||
about: about,
|
about: about,
|
||||||
config: config,
|
config: config,
|
||||||
leaderboard: leaderboard,
|
leaderboard: leaderboard,
|
||||||
throw: throwCmd,
|
throw: throwCmd,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buttonHandlers: { _default: Button } & Record<string, Button> = {
|
||||||
|
_default: defaultButtonHandler,
|
||||||
|
leaderboard: leaderboardButton,
|
||||||
|
throw: throwButton,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { commandHandlers, buttonHandlers };
|
||||||
|
|||||||
+6
-3
@@ -11,6 +11,7 @@ import {
|
|||||||
Events,
|
Events,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import { scheduleJob } from "node-schedule";
|
import { scheduleJob } from "node-schedule";
|
||||||
|
import { processButton } from "./modules/processButton.js";
|
||||||
import { processCommand } from "./modules/processCommand.js";
|
import { processCommand } from "./modules/processCommand.js";
|
||||||
import { instantiateServer } from "./server/serve.js";
|
import { instantiateServer } from "./server/serve.js";
|
||||||
import { logger } from "./utils/logger.js";
|
import { logger } from "./utils/logger.js";
|
||||||
@@ -32,13 +33,15 @@ pavelle.discord.once(Events.ClientReady, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
pavelle.discord.on(Events.InteractionCreate, (interaction) => {
|
pavelle.discord.on(Events.InteractionCreate, (interaction) => {
|
||||||
if (!interaction.isChatInputCommand()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!interaction.inCachedGuild()) {
|
if (!interaction.inCachedGuild()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (interaction.isChatInputCommand()) {
|
||||||
void processCommand(pavelle, interaction);
|
void processCommand(pavelle, interaction);
|
||||||
|
}
|
||||||
|
if (interaction.isButton()) {
|
||||||
|
void processButton(pavelle, interaction);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pavelle.discord.on(Events.Error, (error) => {
|
pavelle.discord.on(Events.Error, (error) => {
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @copyright nhcarrigan
|
||||||
|
* @license Naomi's Public License
|
||||||
|
* @author Naomi Carrigan
|
||||||
|
*/
|
||||||
|
import type { Pavelle } from "./pavelle.js";
|
||||||
|
import type { ButtonInteraction } from "discord.js";
|
||||||
|
|
||||||
|
export type Button = (
|
||||||
|
pavelle: Pavelle,
|
||||||
|
interaction: ButtonInteraction<"cached">
|
||||||
|
)=> Promise<void>;
|
||||||
@@ -144,6 +144,27 @@ export const getThrowComponents = (
|
|||||||
content: `-# You now have ${total.toString()} point(s).\n-# You have ${count.toString()} remaining throws for the hour.`,
|
content: `-# You now have ${total.toString()} point(s).\n-# You have ${count.toString()} remaining throws for the hour.`,
|
||||||
type: 10,
|
type: 10,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- Discord API convention.
|
||||||
|
custom_id: "throw",
|
||||||
|
disabled: false,
|
||||||
|
label: "Throw another!",
|
||||||
|
style: 3,
|
||||||
|
type: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- Discord API convention.
|
||||||
|
custom_id: "leaderboard",
|
||||||
|
disabled: false,
|
||||||
|
label: "Check leaderboard",
|
||||||
|
style: 1,
|
||||||
|
type: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 1,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
spoiler: false,
|
spoiler: false,
|
||||||
type: 17,
|
type: 17,
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @copyright nhcarrigan
|
||||||
|
* @license Naomi's Public License
|
||||||
|
* @author Naomi Carrigan
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { buttonHandlers as handlers } from "../config/handlers.js";
|
||||||
|
import type { Button } from "../interfaces/button.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a button interaction.
|
||||||
|
* @param pavelle - The Pavelle instance.
|
||||||
|
* @param interaction - The interaction to process.
|
||||||
|
*/
|
||||||
|
export const processButton: Button = async(pavelle, interaction) => {
|
||||||
|
await interaction.deferReply();
|
||||||
|
const commandName = interaction.customId;
|
||||||
|
// eslint-disable-next-line no-underscore-dangle -- Accessing private property for command handler.
|
||||||
|
await (handlers[commandName] ?? handlers._default)(pavelle, interaction);
|
||||||
|
};
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* @author Naomi Carrigan
|
* @author Naomi Carrigan
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { handlers } from "../config/handlers.js";
|
import { commandHandlers as handlers } from "../config/handlers.js";
|
||||||
import type { Command } from "../interfaces/command.js";
|
import type { Command } from "../interfaces/command.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
MessageFlags,
|
MessageFlags,
|
||||||
TextDisplayBuilder,
|
TextDisplayBuilder,
|
||||||
type ChatInputCommandInteraction,
|
type ChatInputCommandInteraction,
|
||||||
|
type ButtonInteraction,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,7 +19,7 @@ import {
|
|||||||
* @param interaction - The interaction object from Discord.
|
* @param interaction - The interaction object from Discord.
|
||||||
*/
|
*/
|
||||||
export const sendUnentitledResponse = async(
|
export const sendUnentitledResponse = async(
|
||||||
interaction: ChatInputCommandInteraction,
|
interaction: ChatInputCommandInteraction | ButtonInteraction,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const components = [
|
const components = [
|
||||||
new TextDisplayBuilder().setContent(
|
new TextDisplayBuilder().setContent(
|
||||||
|
|||||||
Reference in New Issue
Block a user