jillo-bot/src/commands/survey.ts

764 lines
25 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, Client, Collection, MessageComponentInteraction, StringSelectMenuBuilder, ModalBuilder, TextChannel, TextInputStyle, Message, ButtonStyle, ComponentType, APIButtonComponentWithCustomId, Events, TextInputBuilder, SlashCommandBuilder } from 'discord.js';
import * as fs from 'fs/promises';
import { knownServers } from '../lib/knownServers';
import { Command } from '../types/index';
const RESPONSES_CHANNEL = '983762973858361364';
const GENERAL_CHANNEL = '587108210683412493';
const ephemeral = true;
function extendOptions(opt: string[]) {
return opt.map(t => ({label: t, value: t.toLowerCase().replace(/[^a-z0-9-]/g, '-')}));
}
const survey = [
{},
{
text: 'What is your name?',
textResponse: true,
id: 'survey-name'
},
{
text: 'Your pronouns?',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-pronouns')
.setOptions([
{
label: 'he/him',
value: 'he-him'
},
{
label: 'she/her',
value: 'she-her'
},
{
label: 'they/them',
value: 'they-them'
},
{
label: 'it/it',
value: 'it-it'
},
{
label: 'Other',
value: 'other',
description: 'You\'ll be able to specify later.'
}
])
.setMinValues(1)
.setMaxValues(5)
]
},
{
text: 'What are your interests/hobbies?',
textResponse: true,
id: 'survey-interests',
style: TextInputStyle.Paragraph
},
{
text: 'What do you see in this image?\nhttps://cdn.discordapp.com/attachments/789023763396165633/983471779060281364/unknown.png',
textResponse: true,
id: 'survey-image',
style: TextInputStyle.Paragraph
},
{
text: 'Which of the following game genres are you currently interested in?',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-genre')
.setOptions([
{
label: 'FPS',
value: 'fps'
},
{
label: 'Strategy',
value: 'strategy'
},
{
label: 'Platformer',
value: 'platformer'
},
{
label: 'Rhythm Game',
value: 'rhythm-game'
},
{
label: 'Puzzle',
value: 'puzzle'
},
{
label: 'Shoot-em-up',
value: 'shmup'
}
])
.setMinValues(1)
.setMaxValues(6)
]
},
{
text: 'Favorite color?',
textResponse: true,
id: 'survey-color1',
placeholder: 'Red, orange, etc...'
},
{
text: 'Which of these music artists do you listen to?',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-artist')
.setOptions([
{
label: 'Mr. Bill',
value: 'mr-bill'
},
{
label: 'Virtual Riot',
value: 'virtual-riot'
},
{
label: 'Mr. Beast',
value: 'mr-beast'
}
])
.setMinValues(1)
.setMaxValues(3)
]
},
{
text: 'What time is it currently?',
textResponse: true,
id: 'survey-time'
},
{
text: 'Did you provide the correct time?',
components: [
new ButtonBuilder().setCustomId('survey-time-correct-yes').setLabel('Yes').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-time-correct-no').setLabel('No').setStyle(ButtonStyle.Primary)
]
},
{
text: 'Favorite color?',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-color2')
.setOptions([
{
label: 'Chair',
value: 'chair'
},
{
label: 'Lithium',
value: 'lithium'
},
{
label: 'Read a => String -> a',
value: 'read-signature'
},
{
label: 'Option 3',
value: 'option3'
}
])
.setMinValues(1)
.setMaxValues(4)
]
},
{
text: 'How many amperes have you had pushed into your heart at once in your lifetime?',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-heart')
.setOptions([
{
label: '1',
value: 'one'
},
{
label: '2',
value: 'two'
},
{
label: '3',
value: 'three'
},
{
label: '4',
value: 'four'
},
{
label: '5',
value: 'five'
}
])
.setMinValues(1)
.setMaxValues(1)
]
},
{
text: 'Are you afraid of the dark?',
components: [
new ButtonBuilder().setCustomId('survey-dark-yes').setLabel('Yes').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-dark-no').setLabel('No').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-dark-maybe').setLabel('Maybe').setStyle(ButtonStyle.Primary)
]
},
{
text: 'Which of these statements on morality do you most resonate with?\nA. Morality is a science which can be understood. There are inherent moral truths in the world.\nB. There does not exist a moral reality, but only perceptions on what is good or bad.',
components: [
new ButtonBuilder().setCustomId('survey-morality-a').setLabel('A').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-morality-b').setLabel('B').setStyle(ButtonStyle.Primary)
]
},
{
text: 'How long ago did you last go on a bridge?',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-bridge')
.setOptions(extendOptions(['A day ago', 'A week ago', 'A month ago', 'Several months ago', 'A year ago', 'Don\'t remember/Earlier']))
.setMinValues(1)
.setMaxValues(1)
]
},
{
text: 'Are you afraid of death?',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-death')
.setOptions(extendOptions(['Yes', 'No', 'Maybe', 'Death is just a step to a different world']))
.setMinValues(1)
.setMaxValues(1)
]
},
{
text: 'What is your current heartrate?',
textResponse: true,
id: 'survey-heartrate',
placeholder: '90BPM, 120BPM, etc...'
},
{
text: 'You are operating a railroad track.\nA train is rapidly coming towards a track with 5 people strapped to the rails, unable to move. You are able to redirect the train onto another track, which has one person strapped to it. Do you make the switch and kill the one person, or do nothing and let the 5 people die?',
components: [
new ButtonBuilder().setCustomId('survey-railroad1-switch').setLabel('Switch').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-railroad1-no').setLabel('Do not').setStyle(ButtonStyle.Primary)
]
},
{
text: 'Which region do you live in?',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-region')
.setOptions(extendOptions(['Europe', 'Asia', 'North America', 'South America', 'Moscow River', 'Africa', 'Oceania']))
.setMinValues(1)
.setMaxValues(1)
]
},
{
text: 'Select the character you resonate with the most.',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-character')
.setOptions([
{
label: '⯧',
value: 'a'
},
{
label: '▩',
value: 'b'
},
{
label: '',
value: 'c'
},
{
label: '∵',
value: 'd'
},
{
label: '⨆',
value: 'e'
}])
.setMinValues(1)
.setMaxValues(1)
]
},
{
text: 'Which of these statements describe you the best?',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-statement')
.setOptions(extendOptions(['Is that a cut on your face, or part of your eye?', 'The gash weaves down as if you cry', 'The pain itself is reason why']))
.setMinValues(1)
.setMaxValues(1)
]
},
{
text: 'When was the last time you experienced a power surge?',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-power-surge')
.setOptions(extendOptions(['A day ago', 'A week ago', 'A month ago', 'Several months ago', 'A year ago', 'Don\'t remember/Earlier']))
.setMinValues(1)
.setMaxValues(1)
]
},
{
text: 'Have you ever shown any interest in automobiles and cars?',
components: [
new ButtonBuilder().setCustomId('survey-cars1-yes').setLabel('Yes').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-cars1-no').setLabel('No').setStyle(ButtonStyle.Primary)
]
},
{
text: 'You are operating a railroad track.\nA person is strapped to the rails unable to move, however there is a fork right before that splits off to a different track with no people. Do you switch to save the person?',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-railroad2')
.setOptions([
{
label: 'Yes',
value: 'yes'
},
{
label: 'Bloodshed',
value: 'bloodshed',
description: 'The correct choice'
}
])
.setMinValues(1)
.setMaxValues(1)
]
},
{
text: 'Have you been here before?',
components: [
new ButtonBuilder().setCustomId('survey-been-here-yes').setLabel('Yes').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-been-here-no').setLabel('No').setStyle(ButtonStyle.Primary)
]
},
{
text: 'Please provide your social media accounts.',
textResponse: true,
id: 'survey-socialmedia',
style: TextInputStyle.Paragraph,
placeholder: 'Twitter, YouTube, etc. Please provide as many as possible.'
},
{
text: 'Is your refrigerator running?',
components: [
new ButtonBuilder().setCustomId('survey-fridge-yes').setLabel('Yes').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-fridge-no').setLabel('No').setStyle(ButtonStyle.Primary)
]
},
{
text: 'Do you generally feel unsafe in your life?',
noNumber: true,
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-unsafe')
.setOptions(extendOptions(['Yes', 'No', 'Sometimes']))
.setMinValues(1)
.setMaxValues(1)
]
},
{
text: 'train',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-train')
.setOptions(extendOptions(['Option 1', 'Option 2']))
.setMinValues(1)
.setMaxValues(1)
]
},
{
text: 'Do you live on planet Earth?',
components: [
new ButtonBuilder().setCustomId('survey-earth-yes').setLabel('Yes').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-earth-no').setLabel('No').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-earth-maybe').setLabel('Maybe~ ;3').setStyle(ButtonStyle.Primary)
]
},
{
text: 'Do you own a credit or debit card?',
components: [
new ButtonBuilder().setCustomId('survey-credit-card-yes').setLabel('Yes').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-credit-card-no').setLabel('No').setStyle(ButtonStyle.Primary)
]
},
{
text: 'You are operating a railroad track.\nYou will be alone on your shift this night, with no one to watch, leaving you with a perfect opportunity to strap an explosive on one of the tracks. Do you cause a horrible accident resulting in the deaths of thousands?',
components: [
new ButtonBuilder().setCustomId('survey-railroad3-yes').setLabel('Yes').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-railroad3-option-2').setLabel('Option 2').setStyle(ButtonStyle.Primary)
]
},
{
text: 'Please click on the button when you\'re finished watching the video.\nhttps://www.youtube.com/watch?v=FsTCet79i2k',
components: [
new ButtonBuilder().setCustomId('survey-finished').setLabel('Done').setStyle(ButtonStyle.Primary)
]
},
{
text: 'Which of these natural landmarks is your favorite?',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-landmark')
.setOptions(extendOptions(['Darvaza Gas Crater', 'Nazca Lines', 'Bermuda Triangle', 'The Hole']))
.setMinValues(1)
.setMaxValues(1)
]
},
{
text: 'Have you ever shown any interest in automobiles and cars?',
components: [
new ButtonBuilder().setCustomId('survey-cars2-yes').setLabel('Yes').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-cars2-no').setLabel('No').setStyle(ButtonStyle.Primary)
]
},
{
text: 'Are you operating a railroad track?',
components: [
new ButtonBuilder().setCustomId('survey-railroad4-yes').setLabel('Yes').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-railroad4-no').setLabel('No').setStyle(ButtonStyle.Primary)
]
},
{
text: 'Please explain why/why not.',
textResponse: true,
id: 'survey-railroad4-why',
style: TextInputStyle.Paragraph
},
{
text: 'Were you telling the truth in your previous 2 anwsers?',
components: [
new ButtonBuilder().setCustomId('survey-railroad4-truth-yes').setLabel('Yes').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId('survey-railroad4-truth-no').setLabel('No').setStyle(ButtonStyle.Primary)
]
},
{
text: 'Please rate your experience with this survey.',
components: [
new StringSelectMenuBuilder()
.setCustomId('survey-rating')
.setOptions(extendOptions(['1 ⭐', '2 ⭐⭐', '3 ⭐⭐⭐', '4 ⭐⭐⭐⭐', '5 ⭐⭐⭐⭐⭐']))
.setMinValues(1)
.setMaxValues(1)
]
},
{
text: 'What is your opinion on mycology?',
noNumber: true,
textResponse: true,
id: 'survey-mycology',
style: TextInputStyle.Paragraph
},
{
text: 'You understand your role in this.',
noNumber: true,
components: [
new ButtonBuilder().setCustomId('survey-closer5').setLabel('Yes, I understand.').setStyle(ButtonStyle.Success)
]
},
{
text: 'Do you like mushrooms as a concept?',
noNumber: true,
components: [
new ButtonBuilder().setCustomId('survey-closer6').setLabel('Yes.').setStyle(ButtonStyle.Success)
]
},
{
text: 'Do you like how mushrooms grow and expand?',
noNumber: true,
components: [
new ButtonBuilder().setCustomId('survey-closer7').setLabel('Yes.').setStyle(ButtonStyle.Success)
]
},
{
text: 'Would you become a stepping stone for mushrooms to grow and expand?',
noNumber: true,
components: [
new ButtonBuilder().setCustomId('survey-closer8').setLabel('YES YES YES YES YES YES YES YES YES YES YES YES YES YES YES YES YES YES YES YES').setStyle(ButtonStyle.Success)
]
}
];
interface SurveyResponse {
id: string,
type: ComponentType,
value: string | string[] | null,
values: string[] | null,
questionIndex: number,
questionText: string,
questionOptions: string[] | null,
}
const surveyProgress: Collection<string, number> = new Collection();
const surveyInteractions: Collection<string, MessageComponentInteraction> = new Collection();
const surveyAnwsered: Collection<string, Collection<string, boolean>> = new Collection();
const surveyAnwsers: Collection<string, Partial<SurveyResponse>[]> = new Collection();
const surveyCurrentMessage: Collection<string, string> = new Collection();
function pushResponse(resp: Partial<SurveyResponse>, userId: string) {
const i = surveyProgress.get(userId);
const question = survey[i || 0];
if (question) {
resp.questionIndex = i;
resp.questionText = question.text;
if (resp.values && question.components) {
const components = question.components as StringSelectMenuBuilder[];
resp.questionOptions = components[0].options.map(o => o.data.label!);
resp.values = resp.values.map(v => components[0].options.find(opt => opt.data.value! === v)?.data.label! || '');
if (components[0].data.max_values! === 1) {
resp.value = resp.values[0];
resp.values = undefined;
}
}
if (resp.type === ComponentType.Button && question.components) {
const components = question.components as ButtonBuilder[];
resp.value = components.find(opt => (opt.data as APIButtonComponentWithCustomId).custom_id! === resp.id)?.data.label;
resp.questionOptions = components.map(o => o?.data.label!);
}
}
// for ordering purposes
const res = {
id: resp.id,
type: resp.type,
questionIndex: resp.questionIndex,
questionText: resp.questionText,
questionOptions: resp.questionOptions || null,
value: resp.value || resp.values || null
};
if (!surveyAnwsers.get(userId)) {
surveyAnwsers.set(userId, []);
}
if (res.questionIndex) {
surveyAnwsers.get(userId)!.push(res);
}
}
function resetProgress(userId: string) {
surveyAnwsered.set(userId, new Collection());
surveyProgress.set(userId, 0);
surveyAnwsers.set(userId, []);
surveyCurrentMessage.delete(userId);
}
async function advanceSurvey(userId: string, dontAdvanceProgress = false) {
if (!dontAdvanceProgress) surveyProgress.set(userId, (surveyProgress.get(userId) || 0) + 1);
const interaction = surveyInteractions.get(userId)!;
const i = surveyProgress.get(userId)!;
const question = survey[i];
if (!question) {
const anwsers = surveyAnwsers.get(userId);
const filename = `survey-responses/survey-response-${userId}.json`;
const stringified = JSON.stringify(anwsers, undefined, 2);
await fs.writeFile(filename, stringified, {encoding: 'utf8'});
(await interaction.client.channels.fetch(RESPONSES_CHANNEL) as TextChannel).send({
content: `Recieved a response from <@${userId}>`,
files: [filename],
components: [new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder().setLabel('Approve').setStyle(ButtonStyle.Success).setCustomId(`survey-reply-approve-${userId}`),
new ButtonBuilder().setLabel('Deny').setStyle(ButtonStyle.Danger).setCustomId(`survey-reply-deny-${userId}`))
]
});
await interaction.deferReply({
ephemeral: ephemeral
});
await interaction.followUp({
content: '**Thank you for your participation!** Your responses have been recorded and you will soon become a member of the Fire Pit based on your anwsers.',
ephemeral: ephemeral
});
resetProgress(userId);
} else {
let components: (StringSelectMenuBuilder | ButtonBuilder)[] = [];
if (question.components) components = question.components;
if (question.textResponse) components.push(
new ButtonBuilder().setCustomId(question.id + '-modal').setLabel('Anwser').setStyle(ButtonStyle.Primary)
);
const msg = await interaction.reply({
content: `${question.noNumber ? '' : `${i}. `}${question.text!.split('\n')[0] === '' ? '' : `**${question.text!.split('\n')[0]}**`}\n${question.text!.split('\n').slice(1).join('\n')}`,
components: components ? ([new ActionRowBuilder<StringSelectMenuBuilder | ButtonBuilder>().addComponents(...components)]) : undefined,
ephemeral: ephemeral,
fetchReply: true
});
surveyCurrentMessage.set(userId, msg.id);
}
}
export default {
data: new SlashCommandBuilder()
.setName('createsurvey')
.setDescription('Re-create the survey button'),
serverWhitelist: [...knownServers.firepit],
execute: async (interaction: CommandInteraction) => {
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder().setCustomId('survey-take').setLabel('Take Survey').setStyle(ButtonStyle.Secondary)
);
await interaction.channel!.send({
content: '**Hello!**\n\nIt would be great to know more about you and your interests before you\'re accepted into the Discord server, so please answer some simple questions for us!',
components: [row]
});
await interaction.reply({
content: 'done',
ephemeral: true
});
},
onClientReady: async (bot: Client) => {
bot.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isMessageComponent()) return;
if (interaction.isModalSubmit()) return;
if (!interaction.customId.startsWith('survey-')) return;
if (!interaction.member) return;
const member = interaction.member as GuildMember;
if (interaction.customId.startsWith('survey-reply-approve-') || interaction.customId.startsWith('survey-reply-deny-')) {
const approve = interaction.customId.startsWith('survey-reply-approve-');
const id = interaction.customId.split('-')[3];
if (!approve) {
await interaction.reply({
content: `${member} denied the application.`,
ephemeral: false
});
const msg = interaction.message as Message;
await msg.edit({
content: msg.content,
components: [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder().setCustomId('survey-reply-denied').setLabel(`${member.displayName} denied the application`).setStyle(ButtonStyle.Danger).setDisabled(true)
)
]
});
} else {
(await interaction.guild?.members.fetch(id))!.roles.add(member.guild!.roles.cache.find(r => r.name === '✦')!);
await interaction.reply({
content: `${member} approved the application.`,
ephemeral: false
});
const msg = interaction.message as Message;
await msg.edit({
content: msg.content,
components: [new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder().setCustomId('survey-reply-approved').setLabel(`${member.displayName} approved the application`).setStyle(ButtonStyle.Success).setDisabled(true)
)]
});
(await interaction.guild?.channels.fetch(GENERAL_CHANNEL) as TextChannel).send({
content: `_<@${id}> has been approved into the server._`,
});
}
return;
}
surveyInteractions.set(member.id, interaction);
if (interaction.customId === 'survey-take') {
const index = surveyProgress.get(member.id);
if (index && index > 0) {
const currentMessage = surveyCurrentMessage.get(member.id);
if (currentMessage) {
surveyAnwsered.get(member.id)!.set(currentMessage, true);
}
advanceSurvey(member.id, true);
return;
} else {
resetProgress(member.id);
}
} else {
if (!surveyAnwsered.get(member.id)) {
interaction.deferUpdate();
return;
}
}
if (surveyAnwsered.get(member.id)!.get(interaction.message.id)) {
interaction.deferUpdate();
return;
}
if (interaction.customId.endsWith('-modal')) {
const modal = new ModalBuilder()
.setCustomId(interaction.customId)
.setTitle('Fire Pit Survey');
const i = surveyProgress.get(member.id)!;
const question = survey[i];
const input = new TextInputBuilder()
.setCustomId(question.id!)
.setLabel(question.text!.trim().split('\n')[0].slice(0, 44))
.setStyle((question.style as TextInputStyle) || TextInputStyle.Short)
.setPlaceholder(question.placeholder || '')
.setRequired(true);
const row = new ActionRowBuilder<TextInputBuilder>().addComponents(input);
modal.addComponents(row);
interaction.showModal(modal);
} else {
surveyAnwsered.get(member.id)!.set(interaction.message.id, true);
const resp = {
id: interaction.customId,
type: interaction.componentType,
values: interaction.isStringSelectMenu() ? interaction.values : undefined
};
pushResponse(resp, member.id);
advanceSurvey(member.id);
}
});
bot.on(Events.InteractionCreate, interaction => {
if (!interaction.isModalSubmit()) return;
if (!interaction.customId.startsWith('survey-')) return;
if (!interaction.member) return;
const member = interaction.member as GuildMember;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
surveyInteractions.set(member.id, interaction);
surveyAnwsered.get(member.id)!.set(interaction.message!.id, true);
const i = surveyProgress.get(member.id)!;
const question = survey[i];
const field = interaction.fields.getField(question.id!, ComponentType.TextInput);
const resp = {
id: field.customId,
type: field.type,
value: field.value
};
pushResponse(resp, member.id);
advanceSurvey(member.id);
});
}
} satisfies Command;