Skip to content

Commit 6183fc6

Browse files
committed
add initial help thread implementation
1 parent 1eee34f commit 6183fc6

File tree

4 files changed

+101
-0
lines changed

4 files changed

+101
-0
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ ASK_CATEGORY=
2020
ONGOING_CATEGORY=
2121
DORMANT_CATEGORY=
2222

23+
HELP_CATEGORY=
24+
2325
ASK_COOLDOWN_ROLE=
2426

2527
CHANNEL_NAMES=list,help,channel,names,here,seperated,by,commas

src/env.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export const categories = {
2222
dormant: process.env.DORMANT_CATEGORY!,
2323
};
2424

25+
export const helpCategory = process.env.HELP_CATEGORY!;
26+
2527
export const askCooldownRoleId = process.env.ASK_COOLDOWN_ROLE!;
2628

2729
export const trustedRoleId = process.env.TRUSTED_ROLE_ID!;
@@ -52,3 +54,7 @@ export const HOURGLASS_ORANGE = '#ffa647';
5254
export const BALLOT_BOX_BLUE = '#066696';
5355
// Picked from Discord's blockquote line
5456
export const BLOCKQUOTE_GREY = '#4f545c';
57+
58+
export const timeBeforeHelperPing = parseInt(
59+
process.env.TIME_BEFORE_HELPER_PING!,
60+
);

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getDB } from './db';
66
import { AutoroleModule } from './modules/autorole';
77
import { EtcModule } from './modules/etc';
88
import { HelpChanModule } from './modules/helpchan';
9+
import { HelpThreadModule } from './modules/helpthread';
910
import { PlaygroundModule } from './modules/playground';
1011
import { RepModule } from './modules/rep';
1112
import { TwoslashModule } from './modules/twoslash';
@@ -37,6 +38,7 @@ for (const mod of [
3738
AutoroleModule,
3839
EtcModule,
3940
HelpChanModule,
41+
HelpThreadModule,
4042
PlaygroundModule,
4143
RepModule,
4244
TwoslashModule,

src/modules/helpthread.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { command, Module, listener } from 'cookiecord';
2+
import { ThreadAutoArchiveDuration } from 'discord-api-types';
3+
import { Message, TextChannel, Channel, ThreadChannel } from 'discord.js';
4+
import { trustedRoleId, helpCategory, timeBeforeHelperPing } from '../env';
5+
import { isTrustedMember } from '../util/inhibitors';
6+
7+
const PINGED_HELPER_MESSAGE = '✅ Pinged helpers';
8+
9+
export class HelpThreadModule extends Module {
10+
@listener({ event: 'threadCreate' })
11+
async onNewThread(thread: ThreadChannel) {
12+
if (!this.isHelpThread(thread)) return;
13+
this.fixThreadExpiry(thread);
14+
}
15+
@listener({ event: 'threadUpdate' })
16+
async onThreadUpdated(thread: ThreadChannel) {
17+
if (!this.isHelpThread(thread)) return;
18+
this.fixThreadExpiry((await thread.fetch()) as ThreadChannel);
19+
}
20+
21+
private async fixThreadExpiry(thread: ThreadChannel) {
22+
if (thread.autoArchiveDuration !== ThreadAutoArchiveDuration.OneDay)
23+
await thread.setAutoArchiveDuration(
24+
ThreadAutoArchiveDuration.OneDay,
25+
);
26+
}
27+
28+
private isHelpThread(
29+
channel: Omit<Channel, 'partial'>,
30+
): channel is ThreadChannel & { parent: TextChannel } {
31+
return (
32+
channel instanceof ThreadChannel &&
33+
channel.parent instanceof TextChannel &&
34+
channel.parent.parentId == helpCategory
35+
);
36+
}
37+
38+
@command({
39+
description: 'Pings a helper in a help-thread',
40+
aliases: ['helpers'],
41+
})
42+
async helper(msg: Message) {
43+
if (!this.isHelpThread(msg.channel)) {
44+
return msg.channel.send(
45+
':warning: You may only ping helpers from a help thread',
46+
);
47+
}
48+
49+
const thread = msg.channel;
50+
51+
// Ensure the user has permission to ping helpers
52+
const isAsker = thread.ownerId === msg.author.id;
53+
const isTrusted =
54+
(await isTrustedMember(msg, this.client)) === undefined; // No error if trusted
55+
56+
if (!isAsker && !isTrusted) {
57+
return msg.channel.send(
58+
':warning: Only the asker can ping helpers',
59+
);
60+
}
61+
62+
const askTime = thread.createdTimestamp;
63+
// Find the last time helpers were called, to avoid multiple successive !helper pings.
64+
// It's possible that the last helper ping would not be within the most recent 20 messages,
65+
// while still being more recent than allowed, but in that case the person has likely already
66+
// recieved help, so them asking for further help is less likely to be spammy.
67+
const lastHelperPing = (
68+
await thread.messages.fetch({ limit: 20 })
69+
).find(
70+
msg =>
71+
msg.author.id === this.client.user!.id &&
72+
msg.content === PINGED_HELPER_MESSAGE,
73+
)?.createdTimestamp;
74+
const pingAllowedAfter =
75+
(lastHelperPing ?? askTime) + timeBeforeHelperPing;
76+
77+
// Ensure they've waited long enough
78+
// Trusted members (who aren't the asker) are allowed to disregard the timeout
79+
if (isAsker && Date.now() < pingAllowedAfter) {
80+
return msg.channel.send(
81+
`:warning: Please wait a bit longer. You can ping helpers <t:${Math.ceil(
82+
pingAllowedAfter / 1000,
83+
)}:R>.`,
84+
);
85+
}
86+
87+
// The beacons are lit, Gondor calls for aid
88+
thread.parent.send(`<@&${trustedRoleId}> ${msg.channel}`);
89+
thread.send(PINGED_HELPER_MESSAGE);
90+
}
91+
}

0 commit comments

Comments
 (0)