Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize to any kind of polls #76

Merged
merged 5 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { PrismaClient } from "@prisma/client";
import { InteractionHandlers, CommandProvider, Chore } from "./bot.d";

import * as utils from "./modules/utils";
import * as attendance from "./modules/attendance";
import * as polls from "./modules/polls";
import * as roleSelection from "./modules/roleSelection";
import * as sudo from "./modules/sudo";
import * as misc from "./modules/misc";
Expand Down Expand Up @@ -48,7 +48,7 @@ const client = new Discord.Client({
});

const commandProviders: CommandProvider[] = [
attendance.provideCommands,
polls.provideCommands,
roleSelection.provideCommands,
sudo.provideCommands,
misc.provideCommands,
Expand All @@ -62,7 +62,7 @@ const commandHandlers: InteractionHandlers<Discord.CommandInteraction> = {};
// two above will be dynamically loaded

const buttonHandlers: InteractionHandlers<Discord.ButtonInteraction> = {
attendance: attendance.handleAttendanceButton,
polls: polls.handlePollButton,
roleSelection: roleSelection.handleRoleSelectionButton,
};

Expand All @@ -72,18 +72,18 @@ const menuHandlers: InteractionHandlers<Discord.SelectMenuInteraction> = {

const startupChores: Chore[] = [
{
summary: "Schedule attendance polls",
summary: "Schedule polls",
fn: async () =>
await attendance.scheduleAttendancePolls(
await polls.schedulePolls(
client,
prisma,
await prisma.attendancePoll.findMany({
await prisma.poll.findMany({
where: {
type: "scheduled",
},
})
),
complete: "All attendance polls scheduled",
complete: "All polls scheduled",
},
{
summary: "Send role selection messages",
Expand Down
136 changes: 69 additions & 67 deletions src/modules/attendance.ts → src/modules/polls.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Handler for attendance polls
// Handler for polls

import {
ButtonInteraction,
Expand All @@ -14,28 +14,28 @@ import {
import * as Builders from "@discordjs/builders";
import cron from "node-cron";

import { PrismaClient, AttendancePoll } from "@prisma/client";
import { PrismaClient, Poll } from "@prisma/client";
import { CommandDescriptor } from "../bot.d";

const ATTENDANCE_POLL_MSG = "Attendance Poll";
const ATTENDANCE_POLL_ACTION_ROW = new MessageActionRow();
ATTENDANCE_POLL_ACTION_ROW.addComponents([
const POLL_MSG = "Poll";
const POLL_ACTION_ROW = new MessageActionRow();
POLL_ACTION_ROW.addComponents([
new MessageButton()
.setLabel("Yes")
.setStyle("SUCCESS")
.setCustomId("attendance:yes"),
.setCustomId("polls:yes"),
new MessageButton()
.setLabel("No")
.setStyle("DANGER")
.setCustomId("attendance:no"),
.setCustomId("polls:no"),
new MessageButton()
.setLabel("Clear")
.setStyle("SECONDARY")
.setCustomId("attendance:clear"),
.setCustomId("polls:clear"),
]);
const ATTENDANCE_NO_ONE = "*No one*";
const POLL_NO_ONE = "*No one*";

export const handleAttendanceButton = async (
export const handlePollButton = async (
interaction: ButtonInteraction
): Promise<void> => {
await interaction.deferReply({ ephemeral: true });
Expand All @@ -51,22 +51,22 @@ export const handleAttendanceButton = async (
fieldIndex = 1;
}

const newEmbed = getNewEmbed(
const newEmbed = getNewPollEmbed(
oldEmbeds[0] as MessageEmbed,
fieldIndex,
interaction.user.id
);

(interaction.message as Message).edit({
content: ATTENDANCE_POLL_MSG,
content: POLL_MSG,
embeds: [newEmbed],
components: [ATTENDANCE_POLL_ACTION_ROW],
components: [POLL_ACTION_ROW],
});

await interaction.editReply("Response recorded!");
};

export const getNewEmbed = (
export const getNewPollEmbed = (
oldEmbed: MessageEmbed,
fieldIndex: number,
userId: Snowflake
Expand All @@ -76,10 +76,9 @@ export const getNewEmbed = (
field.value
.split("\n")
.filter(
(user) =>
user !== ATTENDANCE_NO_ONE && user !== `<@${userId}>`
(user) => user !== POLL_NO_ONE && user !== `<@${userId}>`
)
.join("\n") || (fieldIndex === i ? "" : ATTENDANCE_NO_ONE);
.join("\n") || (fieldIndex === i ? "" : POLL_NO_ONE);
});
if (oldEmbed.fields[fieldIndex])
oldEmbed.fields[
Expand All @@ -89,8 +88,8 @@ export const getNewEmbed = (
return oldEmbed;
};

export const sendAttendanceEmbed = async (
embed: AttendancePoll,
export const sendPollEmbed = async (
embed: Poll,
luishfonseca marked this conversation as resolved.
Show resolved Hide resolved
channel: TextChannel
): Promise<void> => {
const pinnedMessages = await channel.messages.fetchPinned();
Expand All @@ -106,25 +105,25 @@ export const sendAttendanceEmbed = async (
);

const message = await channel.send({
content: ATTENDANCE_POLL_MSG,
content: POLL_MSG,
embeds: [
new MessageEmbed()
.setTitle(embed.title)
.addField("Attending", ATTENDANCE_NO_ONE, true)
.addField("Not Attending", ATTENDANCE_NO_ONE, true)
.addField("Yes", POLL_NO_ONE, true)
.addField("No", POLL_NO_ONE, true)
.setFooter(embed.id)
.setTimestamp(),
],
components: [ATTENDANCE_POLL_ACTION_ROW],
components: [POLL_ACTION_ROW],
});

await message.pin();
};

export const scheduleAttendancePolls = async (
export const schedulePolls = async (
client: Client,
prisma: PrismaClient,
polls: AttendancePoll[]
polls: Poll[]
): Promise<void> => {
await Promise.all(
polls.map(async (poll) => {
Expand All @@ -134,25 +133,22 @@ export const scheduleAttendancePolls = async (

if (!channel) {
console.error(
`Couldn't fetch channel ${poll.channelId} for attendance poll ${poll.id}`
`Couldn't fetch channel ${poll.channelId} for poll ${poll.id}`
);
return;
}

cron.schedule(poll.cron, async () => {
cron.schedule(poll.cron as string, async () => {
luishfonseca marked this conversation as resolved.
Show resolved Hide resolved
try {
// make sure it wasn't deleted / edited in the meantime
const p = await prisma.attendancePoll.findFirst({
const p = await prisma.poll.findFirst({
where: { id: poll.id },
});
if (p !== null) {
await sendAttendanceEmbed(p, channel as TextChannel);
await sendPollEmbed(p, channel as TextChannel);
}
} catch (e) {
console.error(
"Could not verify (& send) attendance poll:",
e.message
);
console.error("Could not verify (& send) poll:", e.message);
luishfonseca marked this conversation as resolved.
Show resolved Hide resolved
}
});
})
Expand All @@ -161,12 +157,12 @@ export const scheduleAttendancePolls = async (

export function provideCommands(): CommandDescriptor[] {
const cmd = new Builders.SlashCommandBuilder()
.setName("attendance")
.setDescription("Manage attendance polls");
.setName("poll")
.setDescription("Manage polls");
cmd.addSubcommand(
new Builders.SlashCommandSubcommandBuilder()
.setName("add")
.setDescription("Create a new scheduled attendance poll")
.setDescription("Create a new poll")
.addStringOption(
new Builders.SlashCommandStringOption()
.setName("id")
Expand All @@ -176,15 +172,7 @@ export function provideCommands(): CommandDescriptor[] {
.addStringOption(
new Builders.SlashCommandStringOption()
.setName("title")
.setDescription("Attendance poll title")
.setRequired(true)
)
.addStringOption(
new Builders.SlashCommandStringOption()
.setName("cron")
.setDescription(
"Cron schedule string; BE VERY CAREFUL THIS IS CORRECT!"
)
.setDescription("Poll title")
.setRequired(true)
)
.addChannelOption(
Expand All @@ -193,11 +181,19 @@ export function provideCommands(): CommandDescriptor[] {
.setDescription("Where polls will be sent")
.setRequired(true)
)
.addStringOption(
new Builders.SlashCommandStringOption()
.setName("schedule")
.setDescription(
"Cron schedule string; BE VERY CAREFUL THIS IS CORRECT! If none, send one-shot poll."
)
.setRequired(false)
)
);
cmd.addSubcommand(
new Builders.SlashCommandSubcommandBuilder()
.setName("remove")
.setDescription("Remove an existing attendance poll")
.setDescription("Remove an existing poll")
.addStringOption(
new Builders.SlashCommandStringOption()
.setName("id")
Expand All @@ -208,12 +204,12 @@ export function provideCommands(): CommandDescriptor[] {
cmd.addSubcommand(
new Builders.SlashCommandSubcommandBuilder()
.setName("list")
.setDescription("List existing attendance polls")
.setDescription("List existing polls")
);
cmd.addSubcommand(
new Builders.SlashCommandSubcommandBuilder()
.setName("info")
.setDescription("Get information for an existing attendance poll")
.setDescription("Get information for an existing poll")
.addStringOption(
new Builders.SlashCommandStringOption()
.setName("id")
Expand All @@ -233,28 +229,30 @@ export async function handleCommand(
try {
const id = interaction.options.getString("id", true);
const title = interaction.options.getString("title", true);
const cron = interaction.options.getString("cron", true);
const channel = interaction.options.getChannel("channel", true);
const cron = interaction.options.getString(
"schedule",
false
) as string | null;
luishfonseca marked this conversation as resolved.
Show resolved Hide resolved

// TODO: don't take this at face value
luishfonseca marked this conversation as resolved.
Show resolved Hide resolved
// ^ how important is this? in principle admins won't mess up
luishfonseca marked this conversation as resolved.
Show resolved Hide resolved

const poll = await prisma.attendancePoll.create({
const poll = await prisma.poll.create({
data: {
id,
type: "scheduled",
type: cron ? "scheduled" : "one-shot",
title,
cron,
channelId: channel.id,
},
});

await scheduleAttendancePolls(interaction.client, prisma, [
poll,
]);
if (cron) {
await schedulePolls(interaction.client, prisma, [poll]);
} else {
await sendPollEmbed(poll, channel as TextChannel);
}

await interaction.editReply(
"✅ Successfully added and scheduled."
`✅ Successfully added${cron ? " and scheduled" : ""}.`
);
} catch (e) {
await interaction.editReply(
Expand All @@ -268,7 +266,7 @@ export async function handleCommand(
try {
const id = interaction.options.getString("id", true);

await prisma.attendancePoll.delete({ where: { id } });
await prisma.poll.delete({ where: { id } });

await interaction.editReply("✅ Successfully removed.");
} catch (e) {
Expand All @@ -279,18 +277,18 @@ export async function handleCommand(
}
case "list": {
try {
const polls = await prisma.attendancePoll.findMany();
const polls = await prisma.poll.findMany();
await interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("Attendance Polls")
.setTitle("Polls")
.setDescription(
polls.length
? "Below is a list of all attendance polls with their title and ID"
: "No attendance polls found"
? "Below is a list of all polls with their title and ID"
: "No polls found"
)
.addFields(
polls.map((p) => ({
polls.map((p: Poll) => ({
luishfonseca marked this conversation as resolved.
Show resolved Hide resolved
name: p.title,
value: p.id,
inline: true,
Expand All @@ -308,7 +306,7 @@ export async function handleCommand(
try {
const id = interaction.options.getString("id", true);

const poll = await prisma.attendancePoll.findFirst({
const poll = await prisma.poll.findFirst({
where: { id },
});

Expand All @@ -320,11 +318,15 @@ export async function handleCommand(
await interaction.editReply({
embeds: [
new MessageEmbed()
.setTitle("Attendance Poll Information")
.setTitle("Poll Information")
.addField("ID", poll.id, true)
.addField("Type", poll.type, true)
.addField("Title", poll.title, true)
.addField("Cron Schedule", poll.cron, true)
.addField(
"Schedule",
poll.cron ? poll.cron : "N/A",
true
)
.addField(
"Channel",
`<#${poll.channelId}>`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PRAGMA foreign_keys=off;
ALTER TABLE "attendance_polls" RENAME TO "polls";
PRAGMA foreign_keys=on;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_polls" (
"id" TEXT NOT NULL PRIMARY KEY,
"type" TEXT NOT NULL,
"title" TEXT NOT NULL,
"cron" TEXT,
"channel_id" TEXT NOT NULL
);
INSERT INTO "new_polls" ("channel_id", "cron", "id", "title", "type") SELECT "channel_id", "cron", "id", "title", "type" FROM "polls";
DROP TABLE "polls";
ALTER TABLE "new_polls" RENAME TO "polls";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;
Loading