-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Discord archives threads after specific number of days (max 7 days). But our threads may need more time to be alive. This is a cron job that will monitor threads and ask user to archive it or extend it to be long living thread that won't get archived.
- Loading branch information
Showing
4 changed files
with
146 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,7 +129,7 @@ dist | |
.yarn/install-state.gz | ||
.pnp.* | ||
|
||
brain.json | ||
data/ | ||
|
||
# direnv | ||
.envrc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
const GUILD = process.env.GUILD | ||
|
||
const { MessageActionRow, MessageButton } = require("discord.js") | ||
const moment = require("moment") | ||
|
||
const { read } = require("../utils/storage") | ||
|
||
function weekdaysBefore(theMoment, days) { | ||
let newMoment = theMoment.clone() | ||
while (days > 0) { | ||
if (newMoment.isoWeekday() < 6) { | ||
days -= 1 | ||
} | ||
newMoment = newMoment.subtract(1, "days") | ||
} | ||
return newMoment | ||
} | ||
|
||
function archiveThreadPrompt(client, thread) { | ||
if (thread.ownerId === client.user.id) { | ||
thread.setArchived(true) | ||
return | ||
} | ||
|
||
const row = new MessageActionRow() | ||
.addComponents( | ||
new MessageButton() | ||
.setCustomId("archive-thread") | ||
.setLabel("Archive The Thread") | ||
.setStyle("DANGER") | ||
) | ||
.addComponents( | ||
new MessageButton() | ||
.setCustomId("long-running-thread") | ||
.setLabel("Long-Running Thread") | ||
.setStyle("SECONDARY") | ||
) | ||
|
||
thread.send({ | ||
content: `<@${thread.ownerId}>, it's been a bit since this thread has seen activity. Ready to archive it?`, | ||
components: [row], | ||
}) | ||
} | ||
|
||
module.exports = { | ||
schedule: "*/15 * * * *", | ||
timezone: "America/New_York", | ||
execute(client) { | ||
return async () => { | ||
const guild = await client.guilds.fetch(GUILD) | ||
const channels = await guild.channels.fetch() | ||
const longRunningThreadIds = (await read("long-running-thread-ids")) || {} | ||
const archiveThreshold = weekdaysBefore(moment(), 4) | ||
channels | ||
.filter((channel) => channel.isText() && channel.viewable) | ||
.forEach(async (channel) => { | ||
const threads = await channel.threads.fetch() | ||
threads.threads.forEach(async (thread) => { | ||
const messages = await thread.messages.fetch({ limit: 1 }) | ||
const lastActivity = Math.max( | ||
(messages.first() && messages.first().createdTimestamp) || 0, | ||
thread.archiveTimestamp | ||
) | ||
if (moment(lastActivity).isAfter(archiveThreshold)) { | ||
return | ||
} | ||
|
||
if (longRunningThreadIds[thread.id]) { | ||
await thread.setArchived(true) | ||
thread.setArchived(false) | ||
} else { | ||
archiveThreadPrompt(client, thread) | ||
} | ||
}) | ||
}) | ||
} | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
const { read, write } = require("../utils/storage") | ||
|
||
async function archiveThread(client, interaction) { | ||
const guild = await client.guilds.fetch(interaction.guildId) | ||
const channel = await guild.channels.fetch(interaction.channelId) | ||
await interaction.reply("Done!") | ||
channel.setArchived(true) | ||
} | ||
|
||
async function markThreadLongRunning(interaction) { | ||
let longRunningThreadIds = (await read("long-running-thread-ids")) || {} | ||
longRunningThreadIds[interaction.channelId] = true | ||
await write("long-running-thread-ids", longRunningThreadIds) | ||
await interaction.reply("Alright. I'll keep the thread alive.") | ||
} | ||
|
||
module.exports = { | ||
trigger: "interactionCreate", | ||
execute(client) { | ||
return async (interaction) => { | ||
if (!interaction.isButton()) return | ||
if (interaction.customId === "archive-thread") { | ||
archiveThread(client, interaction) | ||
} | ||
if (interaction.customId === "long-running-thread") { | ||
markThreadLongRunning(interaction) | ||
} | ||
} | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
const EventEmitter = require("events") | ||
|
||
let brainLock = false | ||
const emitter = new EventEmitter() | ||
const fs = require("fs") | ||
|
||
const storageFilePath = "data/storage.json" | ||
|
||
module.exports = { | ||
async read(key) { | ||
if (brainLock) { | ||
await new Promise((resolve) => emitter.once("unlocked", resolve)) | ||
} | ||
brainLock = true | ||
|
||
const data = await fs.promises.readFile(storageFilePath) | ||
const value = JSON.parse(data.toString())[key] | ||
|
||
brainLock = false | ||
emitter.emit("unlocked") | ||
return value | ||
}, | ||
async write(key, val) { | ||
if (brainLock) { | ||
await new Promise((resolve) => emitter.once("unlocked", resolve)) | ||
} | ||
brainLock = true | ||
|
||
const data = await fs.promises.readFile(storageFilePath) | ||
let brain = JSON.parse(data.toString()) | ||
brain[key] = val | ||
await fs.promises.writeFile(storageFilePath, JSON.stringify(brain, null, 2)) | ||
|
||
brainLock = false | ||
emitter.emit("unlocked") | ||
}, | ||
} |