refactor: change time format in giveaway command (#58)

This commit is contained in:
Peter-MJ-Parker
2025-08-18 09:12:45 -05:00
committed by GitHub
parent 0ee0b2cea7
commit 7c920c9214
5 changed files with 111 additions and 246 deletions

View File

@@ -1,15 +1,9 @@
import { commandModule, CommandType } from "@sern/handler";
import { ownerOnly, publish } from "#plugins";
import {
ApplicationCommandOptionType,
ButtonBuilder,
ActionRowBuilder,
ButtonStyle,
EmbedBuilder,
} from "discord.js";
import { ApplicationCommandOptionType, EmbedBuilder } from "discord.js";
import { db } from "#db";
import { add } from "date-fns";
import { Timestamp } from "#utils";
import { discardRows, parseTimeInput, setupRows, Timestamp } from "#utils";
export default commandModule({
type: CommandType.Slash,
@@ -24,166 +18,28 @@ export default commandModule({
},
{
name: "time",
description: "The amount of time that the giveaway will be up",
description:
"Time format: DD:HH:MM:SS or just minutes ('30' for 30 minutes, '1:30:00:00' for 1 day 30 minutes)",
type: ApplicationCommandOptionType.String,
required: true,
autocomplete: true,
command: {
async execute(ctx) {
const focus = ctx.options.getFocused();
const timeUnits = [
"seconds",
"second",
"sec",
"secs",
"minutes",
"minute",
"min",
"mins",
"hours",
"hour",
"hr",
"hrs",
"days",
"day",
];
if (!focus) return ctx.respond([]);
const andUnitMatch = focus.match(/and\s*(\d+)\s*(\w*)$/i);
if (andUnitMatch) {
const num = andUnitMatch[1];
const partialUnit = andUnitMatch[2];
const filtered = timeUnits.filter((unit) =>
unit.toLowerCase().startsWith(partialUnit.toLowerCase())
);
return ctx.respond(
filtered.map((unit) => ({
name: `${num}${unit.slice(partialUnit.length)}`,
value: `${num}${unit.slice(partialUnit.length)}`,
}))
);
}
const andMatch = focus.match(/and\s*(\d+)\s*$/i);
if (andMatch) {
const num = andMatch[1];
return ctx.respond(
timeUnits.map((unit) => ({
name: `${num} ${unit}`,
value: `${num} ${unit}`,
}))
);
}
if (/^\d+\s*$/.test(focus)) {
return ctx.respond(
timeUnits.map((unit) => ({
name: `${focus} ${unit}`,
value: `${focus} ${unit}`,
}))
);
}
const match = focus.match(/^(\d+)\s*(.*)$/);
if (match) {
const [, num, partialUnit] = match;
const filtered = timeUnits.filter((unit) =>
unit.toLowerCase().startsWith(partialUnit.toLowerCase())
);
let suggestions = filtered.map((unit) => ({
name: `${num} ${unit}`,
value: `${num} ${unit}`,
}));
if (
filtered.length === 1 &&
partialUnit.length > 0 &&
filtered[0] === partialUnit.toLowerCase()
) {
suggestions.push({
name: `${num} ${filtered[0]} and `,
value: `${num} ${filtered[0]} and `,
});
} else if (
filtered.length === 1 &&
partialUnit.length > 0 &&
filtered[0].startsWith(partialUnit.toLowerCase())
) {
suggestions.push({
name: `${num} ${filtered[0]} and `,
value: `${num} ${filtered[0]} and `,
});
}
return ctx.respond(suggestions);
}
const filtered = timeUnits.filter((unit) =>
unit.toLowerCase().includes(focus.toLowerCase())
);
return ctx.respond(
filtered.map((unit) => ({
name: unit,
value: unit,
}))
);
},
},
},
],
execute: async (ctx, { deps }) => {
const item = ctx.options.getString("item");
const timeLeftString = ctx.options.getString("time", true);
const timeInput = ctx.options.getString("time", true);
let timeUnit1;
let timeLeft1;
let timeUnit2;
let timeLeft2;
const [part1, part2] = timeLeftString?.split("and");
timeUnit1 = part1?.split(" ")[1];
timeLeft1 = Number(part1?.split(" ")[0]);
if (part2) {
const timeLeftStringPart2 = part2.replace(part2.substring(0, 1), "");
timeUnit2 = timeLeftStringPart2?.split(" ")[1];
timeLeft2 = Number(timeLeftStringPart2?.split(" ")[0]);
let timeComponents = parseTimeInput(timeInput);
if (typeof timeComponents === "string") {
return ctx.reply({
content: `${timeComponents}`,
ephemeral: true,
});
}
const startTime = new Date();
const endTime = add(startTime, timeComponents);
let endTime: Date;
const secondNames = ["seconds", "second", "sec", "secs"];
const minuteNames = ["minutes", "minute", "min", "mins"];
const hourNames = ["hours", "hour", "hr", "hrs"];
const dayNames = ["days", "day"];
endTime = add(startTime, {
seconds: secondNames.includes(timeUnit1!)
? timeLeft1
: secondNames.includes(timeUnit2!)
? timeLeft2
: 0,
minutes: minuteNames.includes(timeUnit1!)
? timeLeft1
: minuteNames.includes(timeUnit2!)
? timeLeft2
: 0,
hours: hourNames.includes(timeUnit1!)
? timeLeft1
: hourNames.includes(timeUnit2!)
? timeLeft2
: 0,
days: dayNames.includes(timeUnit1!)
? timeLeft1
: dayNames.includes(timeUnit2!)
? timeLeft2
: 0,
});
const endTimeStamp: string = `<t:${Math.floor(endTime!.getTime() / 1000)}:f>`;
const endTimeStamp = `<t:${Math.floor(endTime.getTime() / 1000)}:f>`;
const endTimeStamp2 = new Timestamp(endTime.getTime()).timestamp;
let embed = new EmbedBuilder()
@@ -192,8 +48,8 @@ export default commandModule({
.addFields({
name: "\u200b",
value: `Hosted by: <@${ctx.userId}>
Entries: 0
Ends: ${new Timestamp(Number(endTimeStamp2)).getRelativeTime()} (${endTimeStamp})`,
Entries: 0
Ends: ${new Timestamp(Number(endTimeStamp2)).getRelativeTime()} (${endTimeStamp})`,
});
await ctx
@@ -295,43 +151,3 @@ export default commandModule({
});
},
});
export function discardRows() {
const discardGiveaway = new ButtonBuilder({
customId: "discard",
label: "Discard",
style: ButtonStyle.Primary,
});
return new ActionRowBuilder<ButtonBuilder>().addComponents(discardGiveaway);
}
export function setupRows() {
const enterGiveaway = new ButtonBuilder({
customId: "enter",
label: "Enter Giveaway",
style: ButtonStyle.Success,
});
const leaveGiveaway = new ButtonBuilder({
customId: "leave",
label: "Leave Giveaway",
style: ButtonStyle.Danger,
});
const editGiveaway = new ButtonBuilder({
customId: "edit",
label: "Edit Giveaway",
style: ButtonStyle.Primary,
});
const endGiveaway = new ButtonBuilder({
customId: "end",
label: "End Giveaway",
style: ButtonStyle.Secondary,
});
return new ActionRowBuilder<ButtonBuilder>().addComponents(
enterGiveaway,
leaveGiveaway,
editGiveaway,
endGiveaway
);
}

View File

@@ -1,7 +1,7 @@
import { commandModule, CommandType } from "@sern/handler";
import { ownerIDs } from "#constants";
import { db } from "#db";
import { Timestamp } from "#utils";
import { parseTimeInput, Timestamp } from "#utils";
import { add } from "date-fns";
import { EmbedBuilder } from "discord.js";
@@ -17,53 +17,16 @@ export default commandModule({
const newItem = ctx.fields.getTextInputValue("item");
const newTime = ctx.fields.getTextInputValue("time");
let timeUnit1;
let timeLeft1;
let timeUnit2;
let timeLeft2;
const [part1, part2] = newTime?.split("and");
timeUnit1 = part1?.split(" ")[1];
timeLeft1 = Number(part1?.split(" ")[0]);
if (part2) {
const timeLeftStringPart2 = part2.replace(part2.substring(0, 1), "");
timeUnit2 = timeLeftStringPart2?.split(" ")[1];
timeLeft2 = Number(timeLeftStringPart2?.split(" ")[0]);
}
const parsedTime = parseTimeInput(newTime);
if (typeof parsedTime === "string")
return ctx.reply({
content: parsedTime,
ephemeral: true,
});
const startTime = new Date();
let endTime: Date;
const secondNames = ["seconds", "second", "sec", "secs"];
const minuteNames = ["minutes", "minute", "min", "mins"];
const hourNames = ["hours", "hour", "hr", "hrs"];
const dayNames = ["days", "day"];
endTime = add(startTime, {
seconds: secondNames.includes(timeUnit1!)
? timeLeft1
: secondNames.includes(timeUnit2!)
? timeLeft2
: 0,
minutes: minuteNames.includes(timeUnit1!)
? timeLeft1
: minuteNames.includes(timeUnit2!)
? timeLeft2
: 0,
hours: hourNames.includes(timeUnit1!)
? timeLeft1
: hourNames.includes(timeUnit2!)
? timeLeft2
: 0,
days: dayNames.includes(timeUnit1!)
? timeLeft1
: dayNames.includes(timeUnit2!)
? timeLeft2
: 0,
});
let endTime: Date = add(startTime, parsedTime);
if (endTime.getTime() - startTime.getTime() <= 0)
return ctx.reply({
content: "Please try again with a valid time.",

View File

@@ -1,7 +1,7 @@
import { commandModule, CommandType } from "@sern/handler";
import { db } from "#db";
import { ownerIDs } from "#constants";
import { discardRows } from "#commands/giveaway.js";
import { discardRows } from "#utils";
export default commandModule({
type: CommandType.Button,

View File

@@ -0,0 +1,86 @@
import { ButtonBuilder, ActionRowBuilder, ButtonStyle } from "discord.js";
export function parseTimeInput(
input: string
): { days: number; hours: number; minutes: number; seconds: number } | string {
if (!input.includes(":")) {
const minutes = parseInt(input);
if (isNaN(minutes) || minutes <= 0) {
return "Invalid time format. Use a positive number for minutes or DD:HH:MM:SS format.";
}
return { days: 0, hours: 0, minutes, seconds: 0 };
}
const parts = input.split(":").map((part) => parseInt(part));
if (parts.some((part) => isNaN(part) || part < 0)) {
return "Invalid time format. All time components must be non-negative numbers.";
}
let days = 0,
hours = 0,
minutes = 0,
seconds = 0;
if (parts.length === 4) {
// DD:HH:MM:SS
[days, hours, minutes, seconds] = parts;
} else if (parts.length === 3) {
// HH:MM:SS (assume no days)
[hours, minutes, seconds] = parts;
} else if (parts.length === 2) {
// MM:SS (assume no days or hours)
[minutes, seconds] = parts;
} else {
return "Invalid time format. Use DD:HH:MM:SS, HH:MM:SS, MM:SS, or just minutes.";
}
if (hours >= 24) return "Hours must be less than 24.";
if (minutes >= 60) return "Minutes must be less than 60.";
if (seconds >= 60) return "Seconds must be less than 60.";
if (days === 0 && hours === 0 && minutes === 0 && seconds === 0) {
return "Giveaway duration must be greater than 0.";
}
return { days, hours, minutes, seconds };
}
export function discardRows() {
const discardGiveaway = new ButtonBuilder({
customId: "discard",
label: "Discard",
style: ButtonStyle.Primary,
});
return new ActionRowBuilder<ButtonBuilder>().addComponents(discardGiveaway);
}
export function setupRows() {
const enterGiveaway = new ButtonBuilder({
customId: "enter",
label: "Enter Giveaway",
style: ButtonStyle.Success,
});
const leaveGiveaway = new ButtonBuilder({
customId: "leave",
label: "Leave Giveaway",
style: ButtonStyle.Danger,
});
const editGiveaway = new ButtonBuilder({
customId: "edit",
label: "Edit Giveaway",
style: ButtonStyle.Primary,
});
const endGiveaway = new ButtonBuilder({
customId: "end",
label: "End Giveaway",
style: ButtonStyle.Secondary,
});
return new ActionRowBuilder<ButtonBuilder>().addComponents(
enterGiveaway,
leaveGiveaway,
editGiveaway,
endGiveaway
);
}

View File

@@ -8,6 +8,7 @@ export * from "./Timestamp.js";
export * from "./pagination.js";
export * from "./Logger.js";
export * from "./composable/slashCommand.js";
export * from "./giveawayFunctions.js";
export const require = createRequire(import.meta.url);
@@ -18,7 +19,6 @@ export function cutText(text: string) {
return text;
}
export async function upload(code: string, name?: string) {
const response = await fetch("https://sourceb.in/api/bins", {
body: JSON.stringify({