diff --git a/src/commands/giveaway.ts b/src/commands/giveaway.ts index a2d502a..c632a33 100644 --- a/src/commands/giveaway.ts +++ b/src/commands/giveaway.ts @@ -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 = ``; + const endTimeStamp = ``; 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().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().addComponents( - enterGiveaway, - leaveGiveaway, - editGiveaway, - endGiveaway - ); -} diff --git a/src/commands/handlers/giveawayEditModal.ts b/src/commands/handlers/giveawayEditModal.ts index 9e19267..671cd2a 100644 --- a/src/commands/handlers/giveawayEditModal.ts +++ b/src/commands/handlers/giveawayEditModal.ts @@ -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.", diff --git a/src/commands/handlers/giveawayEnd.ts b/src/commands/handlers/giveawayEnd.ts index 8956d54..33052ca 100644 --- a/src/commands/handlers/giveawayEnd.ts +++ b/src/commands/handlers/giveawayEnd.ts @@ -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, diff --git a/src/utils/giveawayFunctions.ts b/src/utils/giveawayFunctions.ts new file mode 100644 index 0000000..4b51d00 --- /dev/null +++ b/src/utils/giveawayFunctions.ts @@ -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().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().addComponents( + enterGiveaway, + leaveGiveaway, + editGiveaway, + endGiveaway + ); +} diff --git a/src/utils/index.ts b/src/utils/index.ts index afb79af..4a096e4 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -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({