Skip to content

Commit 73d0c43

Browse files
committed
Add threads archive cron
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.
1 parent 1dc0981 commit 73d0c43

File tree

4 files changed

+146
-1
lines changed

4 files changed

+146
-1
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ dist
129129
.yarn/install-state.gz
130130
.pnp.*
131131

132-
brain.json
132+
data/
133133

134134
# direnv
135135
.envrc

cron/thread-lifecycle.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
const GUILD = process.env.GUILD
2+
3+
const { MessageActionRow, MessageButton } = require("discord.js")
4+
const moment = require("moment")
5+
6+
const { read } = require("../utils/storage")
7+
8+
function weekdaysBefore(theMoment, days) {
9+
let newMoment = theMoment.clone()
10+
while (days > 0) {
11+
if (newMoment.isoWeekday() < 6) {
12+
days -= 1
13+
}
14+
newMoment = newMoment.subtract(1, "days")
15+
}
16+
return newMoment
17+
}
18+
19+
function archiveThreadPrompt(client, thread) {
20+
if (thread.ownerId === client.user.id) {
21+
thread.setArchived(true)
22+
return
23+
}
24+
25+
const row = new MessageActionRow()
26+
.addComponents(
27+
new MessageButton()
28+
.setCustomId("archive-thread")
29+
.setLabel("Archive The Thread")
30+
.setStyle("DANGER")
31+
)
32+
.addComponents(
33+
new MessageButton()
34+
.setCustomId("long-running-thread")
35+
.setLabel("Long-Running Thread")
36+
.setStyle("SECONDARY")
37+
)
38+
39+
thread.send({
40+
content: `<@${thread.ownerId}>, it's been a bit since this thread has seen activity. Ready to archive it?`,
41+
components: [row],
42+
})
43+
}
44+
45+
module.exports = {
46+
schedule: "*/15 * * * *",
47+
timezone: "America/New_York",
48+
execute(client) {
49+
return async () => {
50+
const guild = await client.guilds.fetch(GUILD)
51+
const channels = await guild.channels.fetch()
52+
const longRunningThreadIds = (await read("long-running-thread-ids")) || {}
53+
const archiveThreshold = weekdaysBefore(moment(), 4)
54+
channels
55+
.filter((channel) => channel.isText() && channel.viewable)
56+
.forEach(async (channel) => {
57+
const threads = await channel.threads.fetch()
58+
threads.threads.forEach(async (thread) => {
59+
const messages = await thread.messages.fetch({ limit: 1 })
60+
const lastActivity = Math.max(
61+
(messages.first() && messages.first().createdTimestamp) || 0,
62+
thread.archiveTimestamp
63+
)
64+
if (moment(lastActivity).isAfter(archiveThreshold)) {
65+
return
66+
}
67+
68+
if (longRunningThreadIds[thread.id]) {
69+
await thread.setArchived(true)
70+
thread.setArchived(false)
71+
} else {
72+
archiveThreadPrompt(client, thread)
73+
}
74+
})
75+
})
76+
}
77+
},
78+
}

scripts/button-interactions.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const { read, write } = require("../utils/storage")
2+
3+
async function archiveThread(client, interaction) {
4+
const guild = await client.guilds.fetch(interaction.guildId)
5+
const channel = await guild.channels.fetch(interaction.channelId)
6+
await interaction.reply("Done!")
7+
channel.setArchived(true)
8+
}
9+
10+
async function markThreadLongRunning(interaction) {
11+
let longRunningThreadIds = (await read("long-running-thread-ids")) || {}
12+
longRunningThreadIds[interaction.channelId] = true
13+
await write("long-running-thread-ids", longRunningThreadIds)
14+
await interaction.reply("Alright. I'll keep the thread alive.")
15+
}
16+
17+
module.exports = {
18+
trigger: "interactionCreate",
19+
execute(client) {
20+
return async (interaction) => {
21+
if (!interaction.isButton()) return
22+
if (interaction.customId === "archive-thread") {
23+
archiveThread(client, interaction)
24+
}
25+
if (interaction.customId === "long-running-thread") {
26+
markThreadLongRunning(interaction)
27+
}
28+
}
29+
},
30+
}

utils/storage.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const EventEmitter = require("events")
2+
3+
let brainLock = false
4+
const emitter = new EventEmitter()
5+
const fs = require("fs")
6+
7+
const storageFilePath = "data/storage.json"
8+
9+
module.exports = {
10+
async read(key) {
11+
if (brainLock) {
12+
await new Promise((resolve) => emitter.once("unlocked", resolve))
13+
}
14+
brainLock = true
15+
16+
const data = await fs.promises.readFile(storageFilePath)
17+
const value = JSON.parse(data.toString())[key]
18+
19+
brainLock = false
20+
emitter.emit("unlocked")
21+
return value
22+
},
23+
async write(key, val) {
24+
if (brainLock) {
25+
await new Promise((resolve) => emitter.once("unlocked", resolve))
26+
}
27+
brainLock = true
28+
29+
const data = await fs.promises.readFile(storageFilePath)
30+
let brain = JSON.parse(data.toString())
31+
brain[key] = val
32+
await fs.promises.writeFile(storageFilePath, JSON.stringify(brain, null, 2))
33+
34+
brainLock = false
35+
emitter.emit("unlocked")
36+
},
37+
}

0 commit comments

Comments
 (0)