feat: giveaway improvements (#57)

This commit is contained in:
kingomes
2025-08-16 03:34:56 -05:00
committed by GitHub
parent 0464deed3c
commit 0ee0b2cea7
12 changed files with 668 additions and 159 deletions

View File

@@ -11,7 +11,9 @@
"imports": {
"#plugins": "./dist/src/plugins/index.js",
"#utils": "./dist/src/utils/index.js",
"#constants": "./dist/src/constants.js"
"#constants": "./dist/src/constants.js",
"#db": "./dist/src/utils/db.js",
"#commands/*": "./dist/src/commands/*"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",

View File

@@ -1,8 +1,14 @@
import { commandModule, CommandType, scheduledTask } from "@sern/handler";
import { commandModule, CommandType } from "@sern/handler";
import { ownerOnly, publish } from "#plugins";
import { ApplicationCommandOptionType, EmbedBuilder } from "discord.js";
import { db } from "../utils/db.js";
import { add, addDays, addHours, addMinutes, addSeconds } from "date-fns"
import {
ApplicationCommandOptionType,
ButtonBuilder,
ActionRowBuilder,
ButtonStyle,
EmbedBuilder,
} from "discord.js";
import { db } from "#db";
import { add } from "date-fns";
import { Timestamp } from "#utils";
export default commandModule({
@@ -14,149 +20,318 @@ export default commandModule({
name: "item",
description: "The item that will be given away",
type: ApplicationCommandOptionType.String,
required: true
required: true,
},
{
name: "time",
description: "The amount of time that the giveaway will be up",
type: ApplicationCommandOptionType.String,
required: true
}
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 item = ctx.options.getString("item");
const timeLeftString = ctx.options.getString("time", true);
let timeUnit1
let timeLeft1
let timeUnit2
let timeLeft2
let timeUnit1;
let timeLeft1;
let timeUnit2;
let timeLeft2;
const [part1, part2] = timeLeftString?.split("and")
timeUnit1 = part1?.split(" ")[1]
timeLeft1 = Number(part1?.split(" ")[0])
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])
}
if (part2) {
const timeLeftStringPart2 = part2.replace(part2.substring(0, 1), "");
timeUnit2 = timeLeftStringPart2?.split(" ")[1];
timeLeft2 = Number(timeLeftStringPart2?.split(" ")[0]);
}
const startTime = new Date()
const startTime = new Date();
let endTime: 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']
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, {
timeUnit1: timeLeft1,
timeUnit2: timeLeft2
})
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,
});
// This if chain uses date-fns to correctly calculate the time allocated to the giveaway based on what the
// user types (seconds, minutes, etc.)
const endTimeStamp: string = `<t:${Math.floor(endTime!.getTime() / 1000)}:f>`;
const endTimeStamp2 = new Timestamp(endTime.getTime()).timestamp;
// if the time unit before the "and" is "seconds" or one of the other entries in the secondNames array, add the time entered
// to the startTime and save that in the endTime
if (secondNames.includes(timeUnit1!)) {
endTime = endTime === startTime ? addSeconds(startTime, timeLeft1) : addSeconds(endTime, timeLeft1)
}
// if the time unit after the "and" is "seconds" or one of the other entries in the secondNames array, add the time entered
// to the startTime and save that in the endTime
if (secondNames.includes(timeUnit2!)) {
endTime = endTime === startTime ? addSeconds(startTime, timeLeft2!) : addSeconds(endTime, timeLeft2!)
}
// if the time unit before the "and" is "minutes" or one of the other entries in the minuteNames array, add the time entered
// to the startTime and save that in the endTime
if (minuteNames.includes(timeUnit1!)) {
endTime = endTime === startTime ? addMinutes(startTime, timeLeft1) : addMinutes(endTime, timeLeft1)
}
// if the time unit after the "and" is "minutes" or one of the other entries in the minuteNames array, add the time entered
// to the startTime and save that in the endTime
if (minuteNames.includes(timeUnit2!)) {
endTime = endTime === startTime ? addMinutes(startTime, timeLeft2!) : addMinutes(endTime, timeLeft2!)
}
// if the time unit before the "and" is "hours" or one of the other entries in the hourNames array, add the time entered
// to the startTime and save that in the endTime
if (hourNames.includes(timeUnit1!)) {
endTime = endTime === startTime ? addHours(startTime, timeLeft1) : addHours(endTime, timeLeft1)
}
// if the time unit after the "and" is "hours" or one of the other entries in the hourNames array, add the time entered
// to the startTime and save that in the endTime
if (hourNames.includes(timeUnit2!)) {
endTime = endTime === startTime ? addHours(startTime, timeLeft2!) : addHours(endTime, timeLeft2!)
}
// if the time unit before the "and" is "days" or one of the other entries in the dayNames array, add the time entered
// to the startTime and save that in the endTime
if (dayNames.includes(timeUnit1!)) {
endTime = endTime === startTime ? addDays(startTime, timeLeft1) : addDays(endTime, timeLeft1)
}
// if the time unit after the "and" is "days" or one of the other entries in the dayNames array, add the time entered
// to the startTime and save that in the endTime
if (dayNames.includes(timeUnit2!)) {
endTime = endTime === startTime ? addDays(startTime, timeLeft2!) : addDays(endTime, timeLeft2!)
}
const endTimeStamp: string = `<t:${Math.floor(endTime!.getTime() / 1000)}:f>`
const endTimeStamp2 = new Timestamp(endTime.getTime()).timestamp
let embed = new EmbedBuilder()
let embed = new EmbedBuilder()
.setTitle(`🥳 ${item} giveaway 🥳`)
.setDescription('React to enter the giveaway!')
.addFields(
{name: '\u200b', value: `Hosted by: <@${ctx.userId}>`},
{name: '\u200b', value: `Ends: ${new Timestamp(Number(endTimeStamp2)).getRelativeTime()} (${endTimeStamp})`}
)
.setDescription("Click the button to enter the giveaway!")
.addFields({
name: "\u200b",
value: `Hosted by: <@${ctx.userId}>
Entries: 0
Ends: ${new Timestamp(Number(endTimeStamp2)).getRelativeTime()} (${endTimeStamp})`,
});
await ctx.reply({
await ctx
.reply({
embeds: [embed],
}).then(embedMessage => {
db.prepare(`INSERT INTO giveaway_message(message_id, host_id) VALUES (?, ?)`).run(embedMessage.id, ctx.userId)
components: [setupRows()],
})
.then((embedMessage) => {
let giveawayEnded = false;
embedMessage.react("🎉")
const startTimeStamp = new Timestamp(startTime.getTime()).timestamp;
//checks if author reacted to itself
const selfReactionInterval = setInterval(() => {
const userReactions = embedMessage.reactions.cache.filter(reaction => reaction.users.cache.has(ctx.userId));
for (const reaction of userReactions.values()) {
reaction.users.remove(ctx.userId);
ctx.interaction.followUp({content: "As the host of the giveaway, you cannot enter it.", ephemeral: true})
}
}, 1000)
db.prepare(
`INSERT INTO giveaway_message(message_id, start_timestamp, end_time, host_id, item) VALUES (?, ?, ?, ?, ?)`
).run(embedMessage.id, startTimeStamp, endTime.getTime(), ctx.userId, item);
let intervalTime = endTime.getTime() - startTime.getTime()
// test entries
// db.prepare(`INSERT INTO entries(message_id, user_id) VALUES (?, ?, ?)`).run([embedMessage.id, 1])
// db.prepare(`INSERT INTO entries(message_id, user_id) VALUES (?, ?, ?)`).run([embedMessage.id, 2])
// db.prepare(`INSERT INTO entries(message_id, user_id) VALUES (?, ?, ?)`).run([embedMessage.id, 3])
// db.prepare(`INSERT INTO entries(message_id, user_id) VALUES (?, ?, ?)`).run([embedMessage.id, 4])
// db.prepare(`INSERT INTO entries(message_id, user_id) VALUES (?, ?, ?)`).run([embedMessage.id, 5])
setTimeout(() => {
const stmt = db.prepare(`SELECT * FROM entries WHERE message_id = ?`).all(embedMessage.id)
function endGiveaway() {
const giveawayData = db
.prepare(`SELECT item FROM giveaway_message WHERE message_id = ?`)
.get(embedMessage.id);
const item = giveawayData?.item ?? "Unknown item";
let winnerIndex = Math.floor(Math.random() * stmt.length)
const stmt = db
.prepare(`SELECT * FROM entries WHERE message_id = ?`)
.all(embedMessage.id);
if (stmt.length > 0 && stmt[winnerIndex].user_id !== ctx.userId) {
const winnerId = stmt[winnerIndex].user_id
const eligible = stmt.filter(
(entry: { user_id: string }) =>
entry.user_id !== embedMessage.author.id &&
entry.user_id !== ctx.user.id
);
embedMessage.edit({content: `Congratulations <@${winnerId}> on winning the ${item} giveaway!`, embeds: []})
}
else if (stmt.length > 1 && stmt[winnerIndex].user_id === ctx.userId) {
while (stmt[winnerIndex].user_id === ctx.userId) {
winnerIndex = Math.floor(Math.random() * stmt.length)
let winnerIndex = Math.floor(Math.random() * eligible.length);
if (eligible.length > 0 && eligible[winnerIndex].user_id !== ctx.userId) {
const winnerId = stmt[winnerIndex].user_id;
embedMessage.edit({
content: `Congratulations <@${winnerId}> on winning the ${item} giveaway! ${eligible.length} users entered`,
embeds: [],
components: [discardRows()],
});
giveawayEnded = true;
} else if (
eligible.length > 1 &&
eligible[winnerIndex].user_id === ctx.userId
) {
while (eligible[winnerIndex].user_id === ctx.userId) {
winnerIndex = Math.floor(Math.random() * eligible.length);
}
const winnerId = stmt[winnerIndex].user_id
const winnerId = eligible[winnerIndex].user_id;
embedMessage.edit({content: `Congratulations <@${winnerId}> on winning the ${item} giveaway!`, embeds: []})
embedMessage.edit({
content: `Congratulations <@${winnerId}> on winning the ${item} giveaway! ${eligible.length} users entered`,
embeds: [],
components: [discardRows()],
});
giveawayEnded = true;
} else if (
(eligible.length === 1 && eligible[winnerIndex].user_id === ctx.userId) ||
eligible.length === 0
) {
embedMessage.edit({
content: `Couldn't determine a winner: Not enough eligible users. ${eligible.length} users entered`,
embeds: [],
components: [discardRows()],
});
giveawayEnded = true;
}
}
let interval = setInterval(() => {
const giveaway = db
.prepare(
`SELECT end_time, ended FROM giveaway_message WHERE message_id = ?`
)
.get(embedMessage.id);
if (!giveaway || giveaway.ended) {
clearInterval(interval);
return;
}
else if ((stmt.length === 1 && stmt[winnerIndex].user_id === ctx.userId) || stmt.length === 0) {
embedMessage.edit({content: `Not enough eligible users`, embeds: []})
const now = Date.now();
if (now >= giveaway.end_time) {
endGiveaway();
db.prepare(`DELETE FROM giveaway_message WHERE message_id = ?`).run(
embedMessage.id
);
db.prepare(`DELETE FROM entries WHERE message_id = ?`).run(embedMessage.id);
clearInterval(interval);
}
db.prepare(`DELETE FROM giveaway_message WHERE message_id = ?`).run(embedMessage.id)
db.prepare(`DELETE FROM entries WHERE message_id = ?`).run(embedMessage.id)
clearInterval(selfReactionInterval)
}, intervalTime)
})
}
})
}, 1000);
});
},
});
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

@@ -0,0 +1,21 @@
import { ownerIDs } from "#constants";
import { commandModule, CommandType } from "@sern/handler";
import { db } from "#db";
export default commandModule({
type: CommandType.Button,
name: "discard",
async execute(ctx) {
if (!ownerIDs.includes(ctx.user.id))
return ctx.reply({
ephemeral: true,
content: `You cannot discard the giveaway because you are not one of the owners`,
});
db.prepare(`DELETE FROM giveaway_message WHERE message_id = ?`).run(ctx.message.id);
db.prepare(`DELETE FROM entries WHERE message_id = ?`).run(ctx.message.id);
ctx.message.delete();
await ctx.reply({ ephemeral: true, content: `Giveaway discarded!` });
},
});

View File

@@ -0,0 +1,36 @@
import { commandModule, CommandType } from "@sern/handler";
import { ownerIDs } from "#constants";
import { ActionRowBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
export default commandModule({
type: CommandType.Button,
name: "edit",
async execute(ctx) {
if (!ownerIDs.includes(ctx.user.id))
return ctx.reply({
ephemeral: true,
content: `You cannot edit the giveaway because you are not one of the owners`,
});
const modal = new ModalBuilder().setCustomId("giveawayEditModal").setTitle("Edit Giveaway");
const itemInput = new TextInputBuilder()
.setCustomId("item")
.setLabel("New Giveaway Item")
.setStyle(TextInputStyle.Short)
.setRequired(false);
const timeInput = new TextInputBuilder()
.setCustomId("time")
.setLabel("New Time")
.setStyle(TextInputStyle.Short)
.setRequired(false);
modal.addComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(itemInput),
new ActionRowBuilder<TextInputBuilder>().addComponents(timeInput)
);
await ctx.showModal(modal);
},
});

View File

@@ -0,0 +1,109 @@
import { commandModule, CommandType } from "@sern/handler";
import { ownerIDs } from "#constants";
import { db } from "#db";
import { Timestamp } from "#utils";
import { add } from "date-fns";
import { EmbedBuilder } from "discord.js";
export default commandModule({
type: CommandType.Modal,
name: "giveawayEditModal",
async execute(ctx) {
if (!ownerIDs.includes(ctx.user.id))
return ctx.reply({
ephemeral: true,
content: `You cannot edit the giveaway because you are not one of the owners`,
});
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 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,
});
if (endTime.getTime() - startTime.getTime() <= 0)
return ctx.reply({
content: "Please try again with a valid time.",
ephemeral: true,
});
const endTimeStamp: string = `<t:${Math.floor(endTime!.getTime() / 1000)}:f>`;
const endTimeStamp2 = new Timestamp(endTime.getTime()).timestamp;
db.prepare(`UPDATE giveaway_message SET item = ? WHERE message_id = ?`).run(
newItem,
ctx.message?.id
);
db.prepare(`UPDATE giveaway_message SET end_time = ? WHERE message_id = ?`).run(
endTime.getTime(),
ctx.message?.id
);
await ctx.reply({ content: "Giveaway updated!", ephemeral: true });
const message = await ctx.channel?.messages.fetch(ctx.message!.id);
const giveaway = db
.prepare(`SELECT item, end_time FROM giveaway_message WHERE message_id = ?`)
.get(ctx.message?.id);
const entryCount = db
.prepare(`SELECT COUNT(*) as count FROM entries WHERE message_id = ?`)
.get(ctx.message!.id).count;
const newEmbed = EmbedBuilder.from(message!.embeds[0])
.setTitle(`🥳 ${giveaway.item} giveaway 🥳`)
.spliceFields(0, 1, {
name: "\u200b",
value: `Hosted by: <@${
message!.interaction?.user.id
}>\nEntries: ${entryCount}\nEnds: ${new Timestamp(
Number(endTimeStamp2)
).getRelativeTime()} (${endTimeStamp})`,
});
await message!.edit({ embeds: [newEmbed] });
},
});

View File

@@ -0,0 +1,83 @@
import { commandModule, CommandType } from "@sern/handler";
import { db } from "#db";
import { ownerIDs } from "#constants";
import { discardRows } from "#commands/giveaway.js";
export default commandModule({
type: CommandType.Button,
name: "end",
async execute(ctx) {
if (!ownerIDs.includes(ctx.user.id))
return ctx.reply({
ephemeral: true,
content: `You cannot end the giveaway because you are not one of the owners`,
});
const message = db
.prepare(`SELECT * FROM giveaway_message WHERE message_id = ?`)
.get(ctx.message.id);
if (Date.now() > message.end_time) {
await ctx.reply({
ephemeral: true,
content: `This giveaway has already ended!`,
});
return;
}
await ctx.reply({
ephemeral: true,
content: `Giveaway ended by <@${ctx.user.id}>`,
});
let giveawayEnded = false;
let item = message.item;
const stmt = db.prepare(`SELECT * FROM entries WHERE message_id = ?`).all(ctx.message.id);
const eligible = stmt.filter(
(entry: { user_id: string }) =>
entry.user_id !== ctx.message.author.id && entry.user_id !== ctx.user.id
);
let winnerIndex = Math.floor(Math.random() * eligible.length);
if (eligible.length > 0 && eligible[winnerIndex].user_id !== ctx.user.id) {
const winnerId = stmt[winnerIndex].user_id;
ctx.message.edit({
content: `Congratulations <@${winnerId}> on winning the ${item} giveaway! ${eligible.length} users entered`,
embeds: [],
components: [discardRows()],
});
giveawayEnded = true;
} else if (eligible.length > 1 && eligible[winnerIndex].user_id === ctx.user.id) {
while (eligible[winnerIndex].user_id === ctx.user.id) {
winnerIndex = Math.floor(Math.random() * eligible.length);
}
const winnerId = eligible[winnerIndex].user_id;
ctx.message.edit({
content: `Congratulations <@${winnerId}> on winning the ${item} giveaway! ${eligible.length} users entered`,
embeds: [],
components: [discardRows()],
});
giveawayEnded = true;
} else if (
(eligible.length === 1 && eligible[winnerIndex].user_id === ctx.user.id) ||
eligible.length === 0
) {
ctx.message.edit({
content: `Couldn't determine a winner: Not enough eligible users. ${eligible.length} users entered`,
embeds: [],
components: [discardRows()],
});
giveawayEnded = true;
}
if (giveawayEnded) {
db.prepare(`UPDATE giveaway_message SET ended = 1 WHERE message_id = ?`).run(
ctx.message.id
);
}
},
});

View File

@@ -0,0 +1,61 @@
import { commandModule, CommandType } from "@sern/handler";
import { db } from "#db";
import { EmbedBuilder } from "discord.js";
import { Timestamp } from "#utils";
export default commandModule({
type: CommandType.Button,
name: "enter",
async execute(ctx) {
const messages = db.prepare(`SELECT * FROM giveaway_message`).all();
messages.map(async (message: { message_id: string; end_time: number; host_id: string }) => {
if (ctx.message.id === message.message_id && !ctx.user.bot) {
const host = db
.prepare(`SELECT host_id FROM giveaway_message WHERE message_id = ?`)
.get(message.message_id);
if (host && host.host_id === ctx.user.id) {
await ctx.reply({
ephemeral: true,
content: `You cannot enter the giveaway as the host!`,
});
return;
}
const checkUser = db
.prepare(
`SELECT COUNT(*) as count FROM entries WHERE message_id = ? AND user_id = ?`
)
.get(message.message_id, ctx.user.id);
if (checkUser.count === 0) {
db.prepare(`INSERT INTO entries(message_id, user_id) VALUES (?, ?)`).run([
message.message_id,
ctx.user.id,
]);
await ctx.reply({ ephemeral: true, content: `Giveaway entered!` });
} else await ctx.reply({ ephemeral: true, content: `You are already entered!` });
const entryCount = db
.prepare(`SELECT COUNT(*) as count FROM entries WHERE message_id = ?`)
.get(message.message_id).count;
const endTime = message.end_time;
const endTimeStamp: string = `<t:${Math.floor(endTime! / 1000)}:f>`;
const endTimeStamp2 = new Timestamp(endTime).timestamp;
const embed = EmbedBuilder.from(ctx.message.embeds[0]).spliceFields(0, 1, {
name: "\u200b",
value: `Hosted by: <@${host?.host_id ?? "unknown"}>
Entries: ${entryCount}
Ends: ${new Timestamp(
Number(endTimeStamp2)
).getRelativeTime()} (${endTimeStamp})`,
});
await ctx.message.edit({ embeds: [embed] });
}
});
},
});

View File

@@ -0,0 +1,56 @@
import { commandModule, CommandType } from "@sern/handler";
import { db } from "#db";
import { Timestamp } from "#utils";
import { EmbedBuilder } from "discord.js";
export default commandModule({
type: CommandType.Button,
name: "leave",
async execute(ctx) {
const deletedId = ctx.user.id;
const message = db
.prepare(`SELECT * FROM giveaway_message WHERE message_id = ?`)
.get(ctx.message.id);
const host = db
.prepare(`SELECT host_id FROM giveaway_message WHERE message_id = ?`)
.get(message.message_id);
const checkUser = db
.prepare(`SELECT COUNT(*) as count FROM entries WHERE message_id = ? AND user_id = ?`)
.get(message.message_id, ctx.user.id);
if (ctx.message.id === message.message_id && checkUser.count == 1) {
db.prepare(`DELETE FROM entries WHERE message_id = ? AND user_id = ?`).run(
message.message_id,
deletedId
);
await ctx.reply({ ephemeral: true, content: `Giveaway left` });
} else
await ctx.reply({
ephemeral: true,
content: `You cannot leave a giveaway you were not entered in`,
});
const entryCount = db
.prepare(`SELECT COUNT(*) as count FROM entries WHERE message_id = ?`)
.get(message.message_id).count;
const endTime = message.end_time;
const endTimeStamp: string = `<t:${Math.floor(endTime! / 1000)}:f>`;
const endTimeStamp2 = new Timestamp(endTime).timestamp;
const embed = EmbedBuilder.from(ctx.message.embeds[0]).spliceFields(0, 1, {
name: "\u200b",
value: `Hosted by: <@${host?.host_id ?? "unknown"}>
Entries: ${entryCount}
Ends: ${new Timestamp(
Number(endTimeStamp2)
).getRelativeTime()} (${endTimeStamp})`,
});
await ctx.message.edit({ embeds: [embed] });
},
});

View File

@@ -1,21 +0,0 @@
import { discordEvent } from "@sern/handler";
import { db } from "../utils/db.js"
export default discordEvent({
name: 'messageReactionAdd',
execute: async (reaction, potentialWinners) => {
const startTime = reaction.message.createdTimestamp
const messages = db.prepare(`SELECT * FROM giveaway_message`).all()
messages.map((message: { message_id: string, host_id: string }) => {
if (reaction.emoji.name === '🎉' && reaction.message.id === message.message_id && !potentialWinners.bot && message.host_id !== potentialWinners.id) {
const checkUser = db.prepare(`SELECT COUNT(*) as count FROM entries WHERE message_id = ? AND user_id = ?`).get(message.message_id, potentialWinners.id);
if (checkUser.count === 0) {
db.prepare(`INSERT INTO entries(message_id, timestamp, user_id) VALUES (?, ?, ?)`).run([message.message_id, startTime, potentialWinners.id])
}
}
})
}
})

View File

@@ -1,15 +0,0 @@
import { discordEvent } from "@sern/handler";
import { db } from "../utils/db.js"
export default discordEvent({
name: 'messageReactionRemove',
execute: async (reaction, deletedEntry) => {
const deletedId = deletedEntry.id
const message = db.prepare(`SELECT message_id FROM giveaway_message WHERE message_id = ?`).get(reaction.message.id)
if (reaction.emoji.name === '🎉' && reaction.message.id === message.message_id) {
db.prepare(`DELETE FROM entries WHERE message_id = ? AND user_id = ?`).run(message.message_id, deletedId)
}
}
})

View File

@@ -3,5 +3,5 @@ export const db = new Database('giveaway.db');
db.pragma('journal_mode = WAL');
db.exec(`CREATE TABLE IF NOT EXISTS entries(message_id, timestamp, user_id)`);
db.exec(`CREATE TABLE IF NOT EXISTS giveaway_message(message_id, host_id)`)
db.exec(`CREATE TABLE IF NOT EXISTS entries(message_id, user_id)`);
db.exec(`CREATE TABLE IF NOT EXISTS giveaway_message(message_id, start_timestamp, end_time, host_id, item, ended DEFAULT 0)`)

View File

@@ -16,7 +16,9 @@
"paths": {
"#plugins": ["src/plugins/index.js"],
"#utils": ["src/utils/index.js"],
"#constants": ["src/constants.js"]
"#constants": ["src/constants.js"],
"#db": ["src/utils/db.js"],
"#commands/*": ["src/commands/*"],
}
}
}