Skip to content

Commit e51be39

Browse files
committed
Fetch channel references for all uses in all components
1 parent 73fbd13 commit e51be39

45 files changed

Lines changed: 668 additions & 424 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/bot-utilities.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { strict as assert } from "assert";
22

33
import * as Discord from "discord.js";
44

5-
import { named_id, Wheatley } from "./wheatley.js";
5+
import { named_id, Wheatley, channels_map, resolved_channels, typed_channel_id } from "./wheatley.js";
66
import { decode_snowflake, is_media_link_embed, make_url, get_thread_owner } from "./utils/discord.js";
77
import { unwrap } from "./utils/misc.js";
88
import { colors } from "./common.js";
@@ -371,6 +371,15 @@ export class BotUtilities {
371371
return channel;
372372
}
373373

374+
async get_voice_channel(channel_info: named_id) {
375+
const channel = await this.get_channel_internal(channel_info);
376+
assert(
377+
channel instanceof Discord.VoiceChannel,
378+
`Channel ${channel?.name} (${channel_info.id}) not of the expected type`,
379+
);
380+
return channel;
381+
}
382+
374383
async get_forum_channel(channel_info: named_id) {
375384
const channel = await this.get_channel_internal(channel_info);
376385

@@ -404,6 +413,43 @@ export class BotUtilities {
404413
return category;
405414
}
406415

416+
async resolve_channels<const K extends readonly (keyof channels_map)[]>(
417+
...keys: K
418+
): Promise<resolved_channels<K[number]>> {
419+
const result: Record<string, Discord.Channel> = {};
420+
for (const key of keys) {
421+
const channel_info = this.wheatley.channels[key];
422+
switch (channel_info.type) {
423+
case "text":
424+
result[key] = await this.get_channel(channel_info);
425+
break;
426+
case "forum":
427+
result[key] = await this.get_forum_channel(channel_info);
428+
break;
429+
case "voice":
430+
result[key] = await this.get_voice_channel(channel_info);
431+
break;
432+
case "thread":
433+
result[key] = await this.get_thread_channel(channel_info);
434+
break;
435+
}
436+
}
437+
return result as resolved_channels<K[number]>;
438+
}
439+
440+
async resolve_channel(channel_info: typed_channel_id): Promise<Discord.Channel> {
441+
switch (channel_info.type) {
442+
case "text":
443+
return this.get_channel(channel_info);
444+
case "forum":
445+
return this.get_forum_channel(channel_info);
446+
case "voice":
447+
return this.get_voice_channel(channel_info);
448+
case "thread":
449+
return this.get_thread_channel(channel_info);
450+
}
451+
}
452+
407453
async can_user_control_thread(user: Discord.User, thread: Discord.ThreadChannel) {
408454
try {
409455
const owner_id = await get_thread_owner(thread);

src/channel-map.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { strict as assert } from "assert";
2+
3+
import { BotUtilities } from "./bot-utilities.js";
4+
import { typed_channel_id, channel_type_map, Wheatley } from "./wheatley.js";
5+
6+
type keyed_channel_id = typed_channel_id & { key: string };
7+
8+
export function channel_map<const T extends readonly keyed_channel_id[]>(
9+
wheatley: Wheatley,
10+
...channel_ids: T
11+
): { resolve(): Promise<void> } & { [E in T[number] as E["key"]]: channel_type_map[E["type"]] } {
12+
const target: Record<string, unknown> = {};
13+
let resolved = false;
14+
const resolve = async () => {
15+
const utilities = new BotUtilities(wheatley);
16+
for (const channel_id of channel_ids) {
17+
target[channel_id.key] = await utilities.resolve_channel(channel_id);
18+
}
19+
resolved = true;
20+
};
21+
return new Proxy(target, {
22+
get(obj, prop) {
23+
if (prop === "resolve") {
24+
return resolve;
25+
}
26+
if (typeof prop === "string") {
27+
assert(resolved, `Channel binding accessed before resolution (key: ${prop})`);
28+
}
29+
return Reflect.get(obj, prop);
30+
},
31+
}) as { resolve(): Promise<void> } & { [E in T[number] as E["key"]]: channel_type_map[E["type"]] };
32+
}

src/modules/tccpp/components/anti-screenshot.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { delay } from "../../../utils/misc.js";
66
import { M } from "../../../utils/debugging-and-logging.js";
77
import { colors } from "../../../common.js";
88
import { BotComponent } from "../../../bot-component.js";
9-
import { Wheatley } from "../../../wheatley.js";
109
import { CommandSetBuilder } from "../../../command-abstractions/command-set-builder.js";
1110
import { ButtonInteractionBuilder, BotButton } from "../../../command-abstractions/button.js";
11+
import { channel_map } from "../../../channel-map.js";
1212

1313
const DISMISS_TIME = 30 * 1000;
1414

@@ -27,12 +27,12 @@ function message_might_have_code(message: string) {
2727
}
2828

2929
export default class AntiScreenshot extends BotComponent {
30-
private staff_message_log!: Discord.TextChannel;
30+
private channels = channel_map(this.wheatley, this.wheatley.channels.staff_message_log);
3131

3232
private acknowledge_button!: BotButton<[]>;
3333

3434
override async setup(commands: CommandSetBuilder) {
35-
this.staff_message_log = await this.utilities.get_channel(this.wheatley.channels.staff_message_log);
35+
await this.channels.resolve();
3636

3737
this.acknowledge_button = commands.add(
3838
new ButtonInteractionBuilder("anti_screenshot_acknowledge").set_handler(
@@ -96,7 +96,7 @@ export default class AntiScreenshot extends BotComponent {
9696
name: interaction.user.tag,
9797
iconURL: interaction.user.avatarURL()!,
9898
});
99-
await this.staff_message_log.send({
99+
await this.channels.staff_message_log.send({
100100
content: "Anti-screenshot message dismissed",
101101
embeds: [log_embed],
102102
});
@@ -146,7 +146,7 @@ export default class AntiScreenshot extends BotComponent {
146146
iconURL: starter_message.author.avatarURL()!,
147147
})
148148
.setDescription(starter_message.content || "<empty>");
149-
await this.staff_message_log.send({
149+
await this.channels.staff_message_log.send({
150150
content: "Anti-screenshot message sent",
151151
embeds: [log_embed],
152152
files: starter_message.attachments.map(a => a),

src/modules/tccpp/components/anti-self-star.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ import * as Discord from "discord.js";
22
import { strict as assert } from "assert";
33
import { departialize } from "../../../utils/discord.js";
44
import { BotComponent } from "../../../bot-component.js";
5-
import { Wheatley } from "../../../wheatley.js";
65
import { has_media } from "./autoreact.js";
76
import { M } from "../../../utils/debugging-and-logging.js";
87
import { SelfClearingSet } from "../../../utils/containers.js";
98
import { MINUTE } from "../../../common.js";
9+
import { channel_map } from "../../../channel-map.js";
1010

1111
export default class AntiSelfStar extends BotComponent {
12+
private channels = channel_map(this.wheatley, this.wheatley.channels.memes);
13+
1214
laughed_at = new SelfClearingSet<string>(5 * MINUTE, MINUTE);
1315

16+
override async setup() {
17+
await this.channels.resolve();
18+
}
19+
1420
override async on_reaction_add(
1521
reaction: Discord.MessageReaction | Discord.PartialMessageReaction,
1622
user: Discord.User | Discord.PartialUser,
@@ -26,11 +32,7 @@ export default class AntiSelfStar extends BotComponent {
2632
if (reaction.emoji.name !== "⭐") {
2733
return;
2834
}
29-
if (
30-
message.channelId == this.wheatley.channels.memes.id &&
31-
user.id == message.author.id &&
32-
has_media(message)
33-
) {
35+
if (message.channelId == this.channels.memes.id && user.id == message.author.id && has_media(message)) {
3436
await this.handle_self_star(await departialize(message));
3537
}
3638
}

src/modules/tccpp/components/auto-reply.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as Discord from "discord.js";
33
import { M } from "../../../utils/debugging-and-logging.js";
44
import { DAY, HOUR } from "../../../common.js";
55
import { BotComponent } from "../../../bot-component.js";
6+
import { channel_map } from "../../../channel-map.js";
67

78
const LLM_REGEX = /\b(?<!!)llms?\b/gi;
89
const MICROSLOP_REGEX = /\b(?<!!)(?<![./])microsoft?\b/gi;
@@ -17,8 +18,14 @@ export default class Autoreply extends BotComponent {
1718
return true;
1819
}
1920

21+
private channels = channel_map(this.wheatley, this.wheatley.channels.bot_spam);
22+
2023
last_reply_time = Date.now() + RATELIMIT_DURATION; // Hedge against restarts
2124

25+
override async setup() {
26+
await this.channels.resolve();
27+
}
28+
2229
is_ratelimited() {
2330
return Date.now() - this.last_reply_time < RATELIMIT_DURATION;
2431
}
@@ -33,7 +40,7 @@ export default class Autoreply extends BotComponent {
3340
LLM_REGEX.test(message.content) &&
3441
!this.is_ratelimited() &&
3542
Math.random() <= RATELIMIT_PROBABILITY &&
36-
message.channel.id !== this.wheatley.channels.bot_spam.id
43+
message.channel.id !== this.channels.bot_spam.id
3744
) {
3845
this.last_reply_time = Date.now();
3946
M.log("firing llm auto-reply");
@@ -48,7 +55,8 @@ export default class Autoreply extends BotComponent {
4855
MICROSLOP_REGEX.test(message.content) &&
4956
!this.is_ratelimited() &&
5057
Math.random() <= RATELIMIT_PROBABILITY &&
51-
message.channel.id !== this.wheatley.channels.bot_spam.id
58+
message.channel.id !== this.channels.bot_spam.id &&
59+
message.author.id !== "1000654924591403108"
5260
) {
5361
this.last_reply_time = Date.now();
5462
M.log("firing microslop auto-reply");

src/modules/tccpp/components/autoreact.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { is_media_link_embed } from "../../../utils/discord.js";
55
import { M } from "../../../utils/debugging-and-logging.js";
66
import { MINUTE, SECOND } from "../../../common.js";
77
import { BotComponent } from "../../../bot-component.js";
8-
import { Wheatley } from "../../../wheatley.js";
98
import { SelfClearingMap } from "../../../utils/containers.js";
109
import { clear_timeout, set_timeout } from "../../../utils/node.js";
10+
import { channel_map } from "../../../channel-map.js";
1111

1212
/* Here's a little of an explanation as to why we do this messageSnapshot stuff
1313
* A forwarded message is empty, however it contains a property messageSnapshot
@@ -40,8 +40,20 @@ function regular_has_media(message: Discord.Message | Discord.PartialMessage): b
4040
const REACT_TIMEOUT = 90 * SECOND;
4141

4242
export default class Autoreact extends BotComponent {
43+
private channels = channel_map(
44+
this.wheatley,
45+
this.wheatley.channels.introductions,
46+
this.wheatley.channels.memes,
47+
this.wheatley.channels.server_suggestions,
48+
this.wheatley.channels.food,
49+
);
50+
4351
react_timeouts = new SelfClearingMap<string, NodeJS.Timeout>(REACT_TIMEOUT);
4452

53+
override async setup() {
54+
await this.channels.resolve();
55+
}
56+
4557
override async on_ready() {
4658
await this.catch_up();
4759
}
@@ -122,7 +134,7 @@ export default class Autoreact extends BotComponent {
122134
this.wheatley.warn("Unable to find emoji nog4g");
123135
}
124136
}
125-
if (message.channel.id == this.wheatley.channels.introductions.id) {
137+
if (message.channel.id == this.channels.introductions.id) {
126138
if (message.member == null) {
127139
// TODO: Ping zelis?
128140
M.warn("Why??", message);
@@ -132,15 +144,15 @@ export default class Autoreact extends BotComponent {
132144
M.log("Waving to new user", message.author.tag, message.author.id, message.url);
133145
await message.react("👋");
134146
}
135-
} else if (message.channel.id == this.wheatley.channels.memes.id && has_media(message)) {
147+
} else if (message.channel.id == this.channels.memes.id && has_media(message)) {
136148
M.log("Adding star reaction", message.author.tag, message.author.id, message.url);
137149
await message.react("⭐");
138-
} else if (message.channel.id == this.wheatley.channels.server_suggestions.id) {
150+
} else if (message.channel.id == this.channels.server_suggestions.id) {
139151
M.log("Adding server suggestion reactions", message.author.tag, message.author.id, message.url);
140152
await message.react("👍");
141153
await message.react("👎");
142154
await message.react("🤷");
143-
} else if (message.channel.id == this.wheatley.channels.food.id && has_media(message)) {
155+
} else if (message.channel.id == this.channels.food.id && has_media(message)) {
144156
const reaction = message.guild!.emojis.cache.find(emoji => emoji.name === "chefskiss");
145157
if (reaction !== undefined) {
146158
await message.react(reaction);
@@ -152,7 +164,7 @@ export default class Autoreact extends BotComponent {
152164
// reaction blocked
153165
if (e instanceof Discord.DiscordAPIError && e.code === 90001) {
154166
await message.member?.timeout(1 * MINUTE, "Thou shall not block the bot");
155-
if (message.channel.id == this.wheatley.channels.server_suggestions.id) {
167+
if (message.channel.id == this.channels.server_suggestions.id) {
156168
await message.delete();
157169
}
158170
} else {
@@ -177,7 +189,7 @@ export default class Autoreact extends BotComponent {
177189
if (new_message.createdTimestamp && Date.now() - new_message.createdTimestamp > 5 * MINUTE) {
178190
return;
179191
}
180-
if (new_message.channel.id == this.wheatley.channels.memes.id) {
192+
if (new_message.channel.id == this.channels.memes.id) {
181193
const bot_starred = new_message.reactions.cache.get("⭐")?.users.cache.has(this.wheatley.user.id);
182194
// If we haven't stared (or don't know if we've starred) and the new message has media, star
183195
if (!bot_starred && has_media(new_message)) {
@@ -204,8 +216,7 @@ export default class Autoreact extends BotComponent {
204216
}
205217

206218
async catch_up() {
207-
const introductions_channel = await this.utilities.get_channel(this.wheatley.channels.introductions);
208-
const messages = await introductions_channel.messages.fetch({ limit: 100, cache: false });
219+
const messages = await this.channels.introductions.messages.fetch({ limit: 100, cache: false });
209220
for (const [_, message] of messages) {
210221
if (await this.is_new_member(message)) {
211222
M.log("Waving to new user", message.author.tag, message.author.id, message.url);

0 commit comments

Comments
 (0)