diff --git a/README.md b/README.md index 547b473..be232fd 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ It lets us: - Mute people from the Slack - Mute people from Channels only - Make Channels read only & Whitelist people -- Start slow mode (WIP) +- Start slow mode in a channel ## Filesystem - ```interactions``` is where the code for deleting messages, slow mode (WIP), muting people, and making channels read only; It also joins every new channel diff --git a/actions/index.js b/actions/index.js index b582e8b..a7ceab5 100644 --- a/actions/index.js +++ b/actions/index.js @@ -1,25 +1,25 @@ const path = require("path"); -async function handleAction({ event, client, body, say }) { +async function handleAction({ ack, event, client, body, say, action, respond, logger }) { try { const firstAction = body.actions[0]; const viewId = body.view.callback_id const actionId = firstAction.action_id; const blockId = firstAction.block_id; - - console.log("it's working") + console.log("action triggered:", actionId) const actionFile = path.resolve(__dirname, `${actionId}.js`); // Dynamically require action handlers const actionHandler = require(actionFile); if (actionHandler) { - await actionHandler({ event, client, body, say }); + await actionHandler({ ack, event, client, body, say, action, respond, logger }); } else { console.warn(`No handler found for action: ${actionId}`); } } catch (error) { console.error(`Error handling action ${body.actions[0].action_id}:`, error); + if (ack) await ack(); } } diff --git a/actions/slowmode_disable_button.js b/actions/slowmode_disable_button.js new file mode 100644 index 0000000..b9a10f2 --- /dev/null +++ b/actions/slowmode_disable_button.js @@ -0,0 +1,68 @@ +const { getPrisma } = require('../utils/prismaConnector'); +require('dotenv').config(); + +async function slowmode_disable_button(args) { + const { ack, body, client } = args; + const prisma = getPrisma(); + + try { + await ack(); + + const data = JSON.parse(body.actions[0].value); + const { channel, threadTs } = data; + const admin_id = body.user.id; + const userInfo = await client.users.info({ user: admin_id }); + if (!userInfo.user.is_admin) { + return await client.chat.postEphemeral({ + channel: channel, + user: admin_id, + text: "You must be an admin" + }); + } + + const existingSlowmode = await prisma.Slowmode.findUnique({ + where: { + channel_threadTs: { + channel: channel, + threadTs: threadTs || "" + } + } + }); + + if (!existingSlowmode || !existingSlowmode.locked) { + return await client.chat.postEphemeral({ + channel: channel, + user: admin_id, + text: `No active slowmode in <#${channel}>` + }); + } else { + await prisma.Slowmode.update({ + where: { + channel_threadTs: { + channel: channel, + threadTs: threadTs || "" + } + }, + data: { + locked: false, + updatedAt: new Date(), + admin: admin_id + } + }); + + await client.chat.postMessage({ + channel: process.env.MIRRORCHANNEL, + text: `<@${admin_id}> turned off Slowmode in <#${channel}>` + }); + + await client.chat.postMessage({ + channel: channel, + text: "Slowmode has been turned off in this channel." + }) + } + } catch(e) { + console.error(e); + } +} + +module.exports = slowmode_disable_button; diff --git a/actions/slowmode_thread_disable_button.js b/actions/slowmode_thread_disable_button.js new file mode 100644 index 0000000..3817676 --- /dev/null +++ b/actions/slowmode_thread_disable_button.js @@ -0,0 +1,72 @@ +// this is virtually the same as slowmode_disable_button.js +const { getPrisma } = require('../utils/prismaConnector'); +require('dotenv').config(); + +async function slowmode_disable_button(args) { + const { ack, body, client } = args; + const prisma = getPrisma(); + + try { + await ack(); + + const data = JSON.parse(body.actions[0].value); + const { channel, threadTs } = data; + const actionThreadTs = body.actions[0].value && typeof body.actions[0].value === 'string' ? JSON.parse(body.actions[0].value).threadTs : threadTs; + const userInfo = await client.users.info({ user: body.user.id }); + if (!userInfo.user.is_admin) { + return await client.chat.postEphemeral({ + channel: channel, + thread_ts: threadTs, + user: body.user.id, + text: "You must be an admin" + }); + } + + const existingSlowmode = await prisma.Slowmode.findUnique({ + where: { + channel_threadTs: { + channel: channel, + threadTs: actionThreadTs + } + } + }); + + if (!existingSlowmode || !existingSlowmode.locked) { + return await client.chat.postEphemeral({ + channel: channel, + thread_ts: actionThreadTs, + user: body.user.id, + text: `No active slowmode in this thread.` + }); + } else { + await prisma.Slowmode.update({ + where: { + channel_threadTs: { + channel: channel, + threadTs: actionThreadTs + } + }, + data: { + locked: false, + updatedAt: new Date(), + admin: body.user.id + } + }); + + await client.chat.postMessage({ + channel: process.env.MIRRORCHANNEL, + text: `<@${body.user.id}> turned off Slowmode in https://hackclub.slack.com/archives/${channel}/p${actionThreadTs.toString().replace(".", "")}` + }); + + await client.chat.postMessage({ + channel: channel, + thread_ts: actionThreadTs, + text: "Slowmode has been turned off in this thread." + }) + } + } catch(e) { + console.error(e); + } +} + +module.exports = slowmode_disable_button; diff --git a/commands/slowmode.js b/commands/slowmode.js index b0b2eda..64488af 100644 --- a/commands/slowmode.js +++ b/commands/slowmode.js @@ -1,5 +1,6 @@ const chrono = require('chrono-node'); const { getPrisma } = require('../utils/prismaConnector'); +const getChannelManagers = require("../utils/isChannelManger"); async function slowmode(args) { @@ -9,27 +10,174 @@ async function slowmode(args) { const commands = text.split(" "); const userInfo = await client.users.info({ user: user_id }); const isAdmin = userInfo.user.is_admin; - const channel = commands[0].split('|')[0].replace("<#", ""); - let count = Number(commands[1]); - let time = Number(commands[2]); - - if (!isAdmin) return; - - - const createSlowMode = await prisma.Slowmode.create({ - data: { - channel: channel, - locked: true, - time: time, - messageCount: count, - } - }) + const channelManagers = await getChannelManagers(channel_id); + + let channel = channel_id; + if (commands[0] && commands[0].includes('#')) { + channel = commands[0].split('|')[0].replace("<#", "").replace(">", ""); + } + + const errors = [] + // editor's note: i don't think it would be appropriate allowing channel managers to enable slowmode (for now...) - up to discussion. + if (!isAdmin) errors.push("Only admins can run this command."); + if (!channel) errors.push("You need to give a channel to make it read only"); + + if (errors.length > 0) + return await client.chat.postEphemeral({ + channel: `${channel_id}`, + user: `${user_id}`, + text: errors.join("\n") + }); - // TODO: send message in firehouse logs - // TODO: cancel slowmode + const existingSlowmode = await prisma.Slowmode.findFirst({ + where: { channel: channel } + }); - - - } + // TODO: Slowmode for specific threads similar to threadlocker + + const isUpdate = existingSlowmode && existingSlowmode.locked; + const defaultTime = (existingSlowmode?.time || 5).toString(); + const defaultExpiry = existingSlowmode?.expiresAt + ? Math.floor(existingSlowmode.expiresAt.getTime() / 1000) + : undefined; + const defaultWhitelist = existingSlowmode?.whitelistedUsers || []; + + // using a modal-based approach similar to definite threadlocker + const slowmodeModal = { + type: "modal", + callback_id: "slowmode_modal", + private_metadata: JSON.stringify({ + channel_id: channel, + admin_id: user_id, + command_channel: channel_id + }), + title: { + type: "plain_text", + text: isUpdate ? "Update Slowmode" : "Configure Slowmode" + }, + submit: { + type: "plain_text", + text: "Enable" + }, + close: { + type: "plain_text", + text: "Cancel" + }, + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text: `Configure slowmode for <#${channel}>` + } + }, + { + type: "input", + block_id: "slowmode_time_block", + element: { + type: "number_input", + is_decimal_allowed: false, + action_id: "slowmode_time_input", + initial_value: defaultTime, + min_value: "1" + }, + label: { + type: "plain_text", + text: "Slowmode interval (seconds)" + }, + hint: { + type: "plain_text", + text: "Users can send one message every X seconds" + } + }, + { + type: "input", + block_id: "slowmode_duration_block", + optional: true, + element: { + type: "datetimepicker", + action_id: "slowmode_duration_input", + ...(defaultExpiry && { initial_date_time: defaultExpiry }) + }, + label: { + type: "plain_text", + text: "Slowmode until" + }, + hint: { + type: "plain_text", + text: "Leave blank for indefinite" + } + }, + { + type: "input", + block_id: "slowmode_reason_block", + optional: true, + element: { + type: "plain_text_input", + action_id: "slowmode_reason_input", + multiline: false, + placeholder: { + type: "plain_text", + text: "Optional reason" + } + }, + label: { + type: "plain_text", + text: "Reason" + } + }, + { + type: "input", + block_id: "slowmode_whitelist_block", + optional: true, + element: defaultWhitelist.length > 0 ? { + type: "multi_users_select", + action_id: "slowmode_whitelist_input", + initial_users: defaultWhitelist, + placeholder: { + type: "plain_text", + text: "Select users (admins and channel managers are exempt by default)" + } + } : { + type: "multi_users_select", + action_id: "slowmode_whitelist_input", + placeholder: { + type: "plain_text", + text: "Select users (admins and channel managers are exempt by default)" + } + }, + label: { + type: "plain_text", + text: "Whitelisted users" + }, + hint: { + type: "plain_text", + text: "These users will be immune to slowmode" + } + }, + { + type: "actions", + block_id: "slowmode_disable_block", + elements: [ + { + type: "button", + text: { + type: "plain_text", + text: "Turn off Slowmode" + }, + style: "danger", + action_id: "slowmode_disable_button", + value: JSON.stringify({ channel: channel, threadTs: "" }) + } + ] + } + ] + }; + + await client.views.open({ + trigger_id: payload.trigger_id, + view: slowmodeModal + }) +} module.exports = slowmode; \ No newline at end of file diff --git a/events/message.js b/events/message.js new file mode 100644 index 0000000..a37a36c --- /dev/null +++ b/events/message.js @@ -0,0 +1,5 @@ +async function handleMessage(args) { + return; +} + +module.exports = handleMessage; \ No newline at end of file diff --git a/index.js b/index.js index 10cb0ab..81a6757 100644 --- a/index.js +++ b/index.js @@ -80,6 +80,9 @@ app.event(/.*/, handleEvent); // Catch all events dynamically app.action(/.*/, handleAction) // Catch all actions dynamically app.view(/.*/, handleViews) +app.shortcut('slowmode_thread', async (args) => { + await require('./shortcuts/slowmode_thread')(args); +}) app.command(/.*?/, async (args) => { diff --git a/interactions/startSlowMode.js b/interactions/startSlowMode.js index b5b7287..f185951 100644 --- a/interactions/startSlowMode.js +++ b/interactions/startSlowMode.js @@ -1,59 +1,137 @@ const { getPrisma } = require("../utils/prismaConnector") +const getChannelManagers = require("../utils/isChannelManger"); require("dotenv").config(); async function startSlowMode(args) { const { client, payload } = args - const { user, ts, text, channel, subtype } = payload + const { user, ts, text, channel, subtype, thread_ts } = payload + + if (subtype) return; + const prisma = getPrisma(); + let getSlowmode = null; + + if (thread_ts) { + getSlowmode = await prisma.Slowmode.findFirst({ + where: { + channel: channel, + threadTs: thread_ts, + locked: true + } + }); + } + + if (!getSlowmode) { + getSlowmode = await prisma.Slowmode.findFirst({ + where: { + channel: channel, + threadTs: "", + locked: true + } + }); + } - const getSlowmode = await prisma.Slowmode.findFirst({ - where: { - channel: channel, - } - }) if (!getSlowmode) return; - await client.chat.postMessage({ - channel: channel, - text: "Slow mode is in progress!" - }) + if (getSlowmode.expiresAt && new Date() > getSlowmode.expiresAt) { + await Promise.all([ + prisma.Slowmode.update({ + where: { + id: getSlowmode.id + }, + data: { + locked: false + } + }), + prisma.SlowUsers.deleteMany({ + where: { channel: channel } + }) + ]); + const locationText = getSlowmode.threadTs + ? `https://hackclub.slack.com/archives/${channel}/p${thread_ts.toString().replace(".", "")}` + : `<#${channel}>` - const createUser = await prisma.SlowUsers.upsert({ - where: { - channel_user: { // Use the composite unique constraint - channel: channel, - user: user, - }, - }, - create: { - channel: channel, - user: user, - count: 0, - }, - update: { - count: { increment: 1 }, - } - }); + // TODO: add a cron job for SlowUsers cleanup and automatic expiry messages + await client.chat.postMessage({ + channel: process.env.MIRRORCHANNEL, + text: `Slowmode auto-disabled in ${locationText} (expired)` + }); + + return; + } + const userInfo = await client.users.info({ user: user }); + const isManager = (await getChannelManagers(channel)).includes(user); + const isAdmin = userInfo.user.is_admin; + const isWhitelisted = getSlowmode.whitelistedUsers?.includes(user) || false; + const isExempt = isAdmin || isManager || isWhitelisted; + if (isExempt) return; const userData = await prisma.SlowUsers.findFirst({ where: { channel: channel, + threadTs: getSlowmode.threadTs, user: user, }, }); - + const now = Date.now(); - console.log(userData) - await client.chat.postMessage({ + if (!userData) { + await prisma.SlowUsers.create({ + data: { + channel: channel, + threadTs: getSlowmode.threadTs, + user: user, + count: Math.floor(now / 1000), + } + }); + return; + } + + const timeSinceLastMessage = (Math.floor(now / 1000) - userData.count); + + if (timeSinceLastMessage < getSlowmode.time) { + const timeRemaining = Math.ceil(getSlowmode.time - timeSinceLastMessage); + try { + await client.chat.delete({ + channel: channel, + ts: ts, + token: process.env.SLACK_USER_TOKEN + }); + } catch(e) { + console.error(`An error occured: ${e}`); + } + + await client.chat.postEphemeral({ channel: channel, - text: "test" - }) + user: user, + thread_ts: thread_ts, + text: `Slowmode active: you can send another message in ${timeRemaining} seconds.\n\nYour message was:\n${text}` + }); + } else { + await prisma.SlowUsers.upsert({ + where: { + channel_threadTs_user: { + channel: channel, + threadTs: getSlowmode.threadTs, + user: user, + }, + }, + create: { + channel: channel, + threadTs: getSlowmode.threadTs, + user: user, + count: Math.floor(now / 1000) + }, + update: { + count: Math.floor(now / 1000), + } + }); + } } - module.exports = startSlowMode; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ce700b3..8ff59c2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -3,56 +3,59 @@ generator client { } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") shadowDatabaseUrl = env("SHADOW_DATABASE_URL") - } model User { - id Int @id @default(autoincrement()) - channel String? - user String? - reason String? - admin String? - time_nlp String? - time_banned DateTime? + id Int @id @default(autoincrement()) + channel String? + user String? + reason String? + admin String? + time_nlp String? + time_banned DateTime? number_of_messages String? - } - - +} model Channel { - id String @id + id String @id readOnly Boolean - allowlist String[] + allowlist String[] } model Bans { - id Int @id @default(autoincrement()) - user String? + id Int @id @default(autoincrement()) + user String? reason String? - admin String? - time DateTime? + admin String? + time DateTime? } model Slowmode { - id Int @id @default(autoincrement()) - channel String? - locked Boolean? @default(false) - messageCount Int? - time Int? + id Int @id @default(autoincrement()) + channel String + locked Boolean? @default(false) + time Int? + admin String? + createdAt DateTime @default(now()) + expiresAt DateTime? + reason String? + threadTs String @default("") + updatedAt DateTime @default(now()) @updatedAt + whitelistedUsers String[] @default([]) + + @@unique([channel, threadTs], name: "channel_threadTs") } model SlowUsers { - id Int @id @default(autoincrement()) - channel String? - user String? - count Int? - banned Boolean? @default(false) // Fixed typo here from 'bannned' to 'banned' - whitelist Boolean? @default(false) - - @@unique([channel, user]) + id Int @id @default(autoincrement()) + channel String + user String + count Int? + whitelist Boolean? @default(false) + threadTs String @default("") + + @@unique([channel, threadTs, user], name: "channel_threadTs_user") } - - diff --git a/shortcuts/slowmode_thread.js b/shortcuts/slowmode_thread.js new file mode 100644 index 0000000..a3a1d3a --- /dev/null +++ b/shortcuts/slowmode_thread.js @@ -0,0 +1,179 @@ +const { getPrisma } = require('../utils/prismaConnector'); + +async function slowmode_thread(args) { + const { ack, body, client } = args; + const { user, channel, message, trigger_id } = body; + + await ack(); + + const prisma = getPrisma(); + const threadTs = message.thread_ts || message.ts; + const userInfo = await client.users.info({ user: user.id }); + const isAdmin = userInfo.user.is_admin; + + if (!isAdmin) { + return await client.chat.postEphemeral({ + channel: `${channel.id}`, + thread_ts: threadTs, + user: user.id, + text: "Only admins can run this command." + }); + } + + if (!threadTs) { + return; + } + + const existingSlowmode = await prisma.Slowmode.findFirst({ + where: { + channel: channel.id, + threadTs: threadTs + } + }); + + const isUpdate = existingSlowmode && existingSlowmode.locked; + const defaultTime = (existingSlowmode?.time || 5).toString(); + const defaultExpiry = existingSlowmode?.expiresAt + ? Math.floor(existingSlowmode.expiresAt.getTime() / 1000) + : undefined; + const defaultWhitelist = existingSlowmode?.whitelistedUsers || []; + + const slowmodeModal = { + type: "modal", + callback_id: "slowmode_thread_modal", + private_metadata: JSON.stringify({ + channel_id: channel.id, + admin_id: user.id, + command_channel: channel.id, + thread_ts: threadTs, + }), + title: { + type: "plain_text", + text: isUpdate ? "Update Slowmode" : "Configure Slowmode" + }, + submit: { + type: "plain_text", + text: "Enable" + }, + close: { + type: "plain_text", + text: "Cancel" + }, + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text: `Configure slowmode for this thread` + } + }, + { + type: "input", + block_id: "slowmode_time_block", + element: { + type: "number_input", + is_decimal_allowed: false, + action_id: "slowmode_time_input", + initial_value: defaultTime, + min_value: "1" + }, + label: { + type: "plain_text", + text: "Slowmode interval (seconds)" + }, + hint: { + type: "plain_text", + text: "Users can send one message every X seconds" + } + }, + { + type: "input", + block_id: "slowmode_duration_block", + optional: true, + element: { + type: "datetimepicker", + action_id: "slowmode_duration_input", + ...(defaultExpiry && { initial_date_time: defaultExpiry }) + }, + label: { + type: "plain_text", + text: "Slowmode until" + }, + hint: { + type: "plain_text", + text: "Leave blank for indefinite" + } + }, + { + type: "input", + block_id: "slowmode_reason_block", + optional: true, + element: { + type: "plain_text_input", + action_id: "slowmode_reason_input", + multiline: false, + placeholder: { + type: "plain_text", + text: "Optional reason" + } + }, + label: { + type: "plain_text", + text: "Reason" + } + }, + { + type: "input", + block_id: "slowmode_whitelist_block", + optional: true, + element: defaultWhitelist.length > 0 ? { + type: "multi_users_select", + action_id: "slowmode_whitelist_input", + initial_users: defaultWhitelist, + placeholder: { + type: "plain_text", + text: "Select users (admins and channel managers are exempt by default)" + } + } : { + type: "multi_users_select", + action_id: "slowmode_whitelist_input", + placeholder: { + type: "plain_text", + text: "Select users (admins and channel managers are exempt by default)" + } + }, + label: { + type: "plain_text", + text: "Whitelisted users" + }, + hint: { + type: "plain_text", + text: "These users will be immune to slowmode" + } + }, + { + type: "actions", + block_id: "slowmode_disable_block", + elements: [ + { + type: "button", + text: { + type: "plain_text", + text: "Turn off Slowmode" + }, + style: "danger", + action_id: "slowmode_thread_disable_button", + value: JSON.stringify({ channel: channel.id, threadTs: threadTs }) + } + ] + } + ] + }; + + await client.views.open({ + trigger_id: trigger_id, + view: slowmodeModal + }) +} + +module.exports = slowmode_thread; \ No newline at end of file diff --git a/views/slowmode_modal.js b/views/slowmode_modal.js new file mode 100644 index 0000000..93ce4e9 --- /dev/null +++ b/views/slowmode_modal.js @@ -0,0 +1,134 @@ +const { getPrisma } = require('../utils/prismaConnector'); +require('dotenv').config(); + +async function slowmode_modal(args) { + const { ack, body, client } = args; + const prisma = getPrisma(); + + try { + const view = body.view; + const metadata = JSON.parse(view.private_metadata); + const { channel_id, admin_id, command_channel } = metadata; + const submittedValues = view.state.values; + const slowmodeTime = parseInt(submittedValues.slowmode_time_block.slowmode_time_input.value); + const slowmodeDuration = submittedValues.slowmode_duration_block.slowmode_duration_input.selected_date_time; + const reason = submittedValues.slowmode_reason_block.slowmode_reason_input.value || ""; + const whitelistedUsers = submittedValues.slowmode_whitelist_block.slowmode_whitelist_input.selected_users || []; + const errors = {}; + + let expiresAt = null; + if (slowmodeDuration) { + expiresAt = new Date(slowmodeDuration * 1000); + if (expiresAt <= new Date()) { + errors.slowmode_duration_block = "Time cannot be in the past."; + } + } + if (slowmodeTime < 1) { + errors.slowmode_time_block = "Invalid slowmode interval"; + } + + if (Object.keys(errors).length > 0) { + return await ack({ + response_action: "errors", + errors: errors + }); + } + + await ack(); + + const slowmode = await prisma.Slowmode.upsert({ + where: { + channel_threadTs: { + channel: channel_id, + threadTs: "" + } + }, + create: { + channel: channel_id, + threadTs: "", + locked: true, + time: slowmodeTime, + expiresAt: expiresAt, + reason: reason, + admin: admin_id, + whitelistedUsers: whitelistedUsers + }, + update: { + locked: true, + time: slowmodeTime, + expiresAt: expiresAt, + reason: reason, + admin: admin_id, + whitelistedUsers: whitelistedUsers, + updatedAt: new Date() + } + }); + + for (const userId of whitelistedUsers) { + await prisma.SlowUsers.upsert({ + where: { + channel_threadTs_user: { + channel: channel_id, + threadTs: "", + user: userId + } + }, + create: { + channel: channel_id, + threadTs: "", + user: userId, + whitelist: true, + count: 0 + }, + update: { + whitelist: true, + } + }); + } + + const allWhitelistedSlowUsers = await prisma.SlowUsers.findMany({ + where: { + channel: channel_id, + threadTs: "", + whitelist: true + } + }); + + for (const slowUser of allWhitelistedSlowUsers) { + if (!whitelistedUsers.includes(slowUser.user)) { + await prisma.SlowUsers.update({ + where: { + channel_threadTs_user: { + channel: channel_id, + threadTs: "", + user: slowUser.user + } + }, + data: { + whitelist: false + } + }); + } + } + + const expiryText = expiresAt + ? `until ${expiresAt.toLocaleString('en-US', { timeZone: 'America/New_York', timeStyle: "short", dateStyle: "long" })} EST` + : "indefinitely" + + const reasonText = reason ? `${reason}` : "(none provided)"; + + await client.chat.postMessage({ + channel: process.env.MIRRORCHANNEL, + text: `<@${admin_id}> enabled a ${slowmodeTime} second Slowmode in <#${channel_id}> for ${reasonText} ${expiryText}` + }); + + await client.chat.postMessage({ + channel: channel_id, + text: `A ${slowmodeTime} second Slowmode has been enabled in this channel ${expiryText}` + }); + } catch(e) { + console.error(e); + } +} + +module.exports = slowmode_modal; diff --git a/views/slowmode_thread_modal.js b/views/slowmode_thread_modal.js new file mode 100644 index 0000000..558f883 --- /dev/null +++ b/views/slowmode_thread_modal.js @@ -0,0 +1,136 @@ +// this is virtually the same as slowmode_modal.js +const { getPrisma } = require('../utils/prismaConnector'); +require('dotenv').config(); + +async function slowmode_thread_modal(args) { + const { ack, body, client } = args; + const prisma = getPrisma(); + + try { + const view = body.view; + const metadata = JSON.parse(view.private_metadata); + const { channel_id, admin_id, command_channel, thread_ts } = metadata; + const submittedValues = view.state.values; + const slowmodeTime = parseInt(submittedValues.slowmode_time_block.slowmode_time_input.value); + const slowmodeDuration = submittedValues.slowmode_duration_block.slowmode_duration_input.selected_date_time; + const reason = submittedValues.slowmode_reason_block.slowmode_reason_input.value || ""; + const whitelistedUsers = submittedValues.slowmode_whitelist_block.slowmode_whitelist_input.selected_users || []; + const errors = {}; + + let expiresAt = null; + if (slowmodeDuration) { + expiresAt = new Date(slowmodeDuration * 1000); + if (expiresAt <= new Date()) { + errors.slowmode_duration_block = "Time cannot be in the past."; + } + } + if (slowmodeTime < 1) { + errors.slowmode_time_block = "Invalid slowmode interval"; + } + + if (Object.keys(errors).length > 0) { + return await ack({ + response_action: "errors", + errors: errors + }); + } + + await ack(); + + const slowmode = await prisma.Slowmode.upsert({ + where: { + channel_threadTs: { + channel: channel_id, + threadTs: thread_ts + } + }, + create: { + channel: channel_id, + threadTs: thread_ts, + locked: true, + time: slowmodeTime, + expiresAt: expiresAt, + reason: reason, + admin: admin_id, + whitelistedUsers: whitelistedUsers + }, + update: { + locked: true, + time: slowmodeTime, + expiresAt: expiresAt, + reason: reason, + admin: admin_id, + whitelistedUsers: whitelistedUsers, + updatedAt: new Date() + } + }); + + for (const userId of whitelistedUsers) { + await prisma.SlowUsers.upsert({ + where: { + channel_threadTs_user: { + channel: channel_id, + threadTs: thread_ts, + user: userId + } + }, + create: { + channel: channel_id, + threadTs: thread_ts, + user: userId, + whitelist: true, + count: 0 + }, + update: { + whitelist: true, + } + }); + } + + const allWhitelistedSlowUsers = await prisma.SlowUsers.findMany({ + where: { + channel: channel_id, + threadTs: thread_ts, + whitelist: true + } + }); + + for (const slowUser of allWhitelistedSlowUsers) { + if (!whitelistedUsers.includes(slowUser.user)) { + await prisma.SlowUsers.update({ + where: { + channel_threadTs_user: { + channel: channel_id, + threadTs: thread_ts, + user: slowUser.user + } + }, + data: { + whitelist: false + } + }); + } + } + + const expiryText = expiresAt + ? `until ${expiresAt.toLocaleString('en-US', { timeZone: 'America/New_York', timeStyle: "short", dateStyle: "long" })} EST` + : "indefinitely" + + const reasonText = reason ? `${reason}` : "(none provided)"; + + await client.chat.postMessage({ + channel: process.env.MIRRORCHANNEL, + text: `<@${admin_id}> enabled a ${slowmodeTime} second Slowmode in https://hackclub.slack.com/archives/${channel_id}/p${thread_ts.toString().replace(".", "")} for ${reasonText} ${expiryText}` + }); + + await client.chat.postMessage({ + channel: channel_id, + thread_ts, + text: `A ${slowmodeTime} second Slowmode has been enabled in this thread ${expiryText}` + }); + } catch(e) { + console.error(e); + } +} + +module.exports = slowmode_thread_modal;