Skip to content

Commit 4db3117

Browse files
committed
Revert "Merge remote-tracking branch 'upstream/main' into improvements"
This reverts commit 626bc69, reversing changes made to 21d5eee.
1 parent 84f3085 commit 4db3117

17 files changed

Lines changed: 241 additions & 630 deletions

src/bot-utilities.ts

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,13 @@ export class BotUtilities {
129129
async get_media(message: MessageData | ForwardedMessageData) {
130130
return [
131131
...message.attachments
132-
.filter(a => a.contentType?.startsWith("image"))
132+
.filter(a => a.contentType?.indexOf("image") == 0)
133133
.map(a => ({
134134
type: "image",
135135
attachment: a,
136136
})),
137137
...message.attachments
138-
.filter(a => a.contentType?.startsWith("video"))
138+
.filter(a => a.contentType?.indexOf("video") == 0)
139139
.map(a => ({
140140
type: "video",
141141
attachment: a,
@@ -175,36 +175,55 @@ export class BotUtilities {
175175
const media = await this.get_media(message);
176176
const other_embeds = message.embeds.filter(e => !is_media_link_embed(e));
177177
const attachments: (Discord.Attachment | Discord.AttachmentPayload)[] = [];
178-
const other_attachments = message.attachments.filter(
179-
a => !(a.contentType?.startsWith("image") || a.contentType?.startsWith("video")),
180-
);
181-
const images = media.filter(m => m.type === "image");
182-
const videos = media.filter(m => m.type === "video");
183-
for (const video of videos) {
184-
attachments.push(video.attachment);
185-
}
186-
if (images.length === 1) {
187-
const image = images[0];
188-
embed.setImage(
189-
image.attachment instanceof Discord.Attachment ? image.attachment.url : image.attachment.attachment,
190-
);
191-
} else if (images.length > 1) {
192-
for (const image of images) {
193-
attachments.push(image.attachment);
178+
const other_attachments: Discord.Attachment[] = message.attachments
179+
.map(a => a)
180+
.filter(a => !(a.contentType?.indexOf("image") == 0 || a.contentType?.indexOf("video") == 0));
181+
let set_primary_image = false;
182+
const media_embeds: Discord.EmbedBuilder[] = [];
183+
if (media.length > 0) {
184+
for (const medium of media) {
185+
if (medium.type == "image") {
186+
if (!set_primary_image) {
187+
embed.setImage(
188+
medium.attachment instanceof Discord.Attachment
189+
? medium.attachment.url
190+
: medium.attachment.attachment,
191+
);
192+
set_primary_image = true;
193+
} else {
194+
media_embeds.push(
195+
new Discord.EmbedBuilder({
196+
image: {
197+
url:
198+
medium.attachment instanceof Discord.Attachment
199+
? medium.attachment.url
200+
: medium.attachment.attachment,
201+
},
202+
}),
203+
);
204+
}
205+
} else {
206+
// video
207+
attachments.push(medium.attachment);
208+
}
194209
}
195210
}
196-
// Add stickers as attachments
197211
for (const sticker of message.stickers ?? []) {
198212
if (sticker.url) {
199-
attachments.push({ attachment: sticker.url, name: `${sticker.name}.png` });
213+
media_embeds.push(
214+
new Discord.EmbedBuilder({
215+
image: { url: sticker.url },
216+
}),
217+
);
200218
}
201219
}
202220
if (options?.no_extra_media_embeds) {
221+
media_embeds.splice(0, media_embeds.length);
203222
other_embeds.splice(0, other_embeds.length);
204223
attachments.splice(0, attachments.length);
205224
other_attachments.splice(0, other_attachments.length);
206225
}
207-
const embeds = other_embeds.map(api_embed => new Discord.EmbedBuilder(api_embed));
226+
const embeds = [...media_embeds, ...other_embeds.map(api_embed => new Discord.EmbedBuilder(api_embed))];
208227
const files = [...attachments, ...other_attachments];
209228
return [embeds, files];
210229
}
@@ -291,7 +310,7 @@ export class BotUtilities {
291310
}
292311
return {
293312
embeds: [embed, ...extra_embeds],
294-
files: attachments.length ? attachments : undefined,
313+
files: attachments.length ? undefined : attachments,
295314
};
296315
}
297316

src/command-abstractions/command-set-builder.ts

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@ import { Wheatley } from "../wheatley.js";
1717
const global_context_menu_limit = 5;
1818

1919
export class CommandSetBuilder {
20-
private commands: (Discord.SlashCommandBuilder | Discord.ContextMenuCommandBuilder)[] = [];
21-
private text_commands: Record<string, BotTextBasedCommand<unknown[]>> = {};
22-
private other_commands: Record<string, BaseBotInteraction<unknown[]>> = {};
23-
private button_handlers: Record<string, BotButtonHandler<any[]>> = {};
24-
private modal_handlers: Record<string, BotModalHandler<any[]>> = {};
25-
private pending_text_commands: Map<string, TextBasedCommandBuilder<any, true, any, true>> = new Map();
20+
commands: (Discord.SlashCommandBuilder | Discord.ContextMenuCommandBuilder)[] = [];
21+
text_commands: Record<string, BotTextBasedCommand<unknown[]>> = {};
22+
other_commands: Record<string, BaseBotInteraction<unknown[]>> = {};
23+
button_handlers: Record<string, BotButtonHandler<any[]>> = {};
24+
modal_handlers: Record<string, BotModalHandler<any[]>> = {};
2625

2726
constructor(readonly wheatley: Wheatley) {}
2827

@@ -46,18 +45,12 @@ export class CommandSetBuilder {
4645
| ButtonInteractionBuilder<T, true>,
4746
) {
4847
if (command instanceof TextBasedCommandBuilder) {
49-
const name = command.names[0];
50-
const existing = this.pending_text_commands.get(name);
51-
if (existing) {
52-
assert(
53-
existing.subcommands.length > 0 && command.subcommands.length > 0,
54-
`Cannot merge command "${name}" - both must have subcommands`,
55-
);
56-
for (const subcommand of command.subcommands) {
57-
existing.add_subcommand(subcommand);
48+
for (const descriptor of command.to_command_descriptors(this.wheatley)) {
49+
assert(!(descriptor.name in this.text_commands));
50+
this.text_commands[descriptor.name] = descriptor;
51+
if (descriptor.slash) {
52+
this.register(descriptor.to_slash_command(new Discord.SlashCommandBuilder()));
5853
}
59-
} else {
60-
this.pending_text_commands.set(name, command as TextBasedCommandBuilder<any, true, any, true>);
6154
}
6255
} else if (command instanceof ButtonInteractionBuilder) {
6356
const button_handler = command.build_handler();
@@ -90,14 +83,6 @@ export class CommandSetBuilder {
9083
}
9184

9285
async finalize(token: string) {
93-
for (const command of this.pending_text_commands.values()) {
94-
for (const descriptor of command.to_command_descriptors(this.wheatley)) {
95-
this.text_commands[descriptor.name] = descriptor;
96-
if (descriptor.slash) {
97-
this.register(descriptor.to_slash_command(new Discord.SlashCommandBuilder()));
98-
}
99-
}
100-
}
10186
try {
10287
const rest = new REST({ version: "10" }).setToken(token);
10388
if (this.wheatley.freestanding) {

src/command-handler.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -456,11 +456,8 @@ export class CommandHandler {
456456
}
457457
// TODO: Notify if errors occur in the handler....
458458
} catch (e) {
459-
if (interaction.isAutocomplete() && e instanceof Discord.DiscordAPIError && e.code === 10062) {
460-
M.warn("Autocomplete interaction expired before response could be sent");
461-
} else {
462-
this.wheatley.critical_error(e);
463-
}
459+
// TODO....
460+
this.wheatley.critical_error(e);
464461
}
465462
}
466463
}

src/modules/wheatley/components/anti-everyone.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,12 @@ export default class AntiEveryone extends BotComponent {
4040
if (!this.replies.has(message.author)) {
4141
this.replies.set(message.author, []);
4242
}
43-
try {
44-
const reply = await message.reply({
45-
content: `Did you really just try to ping ${member_count} people?`,
46-
});
47-
// Store the reply for later deletion, along with the message it was replying to
48-
unwrap(this.replies.get(message.author)).push({ reply_to: message.id, reply });
49-
} catch (e) {
50-
if (e instanceof Discord.DiscordAPIError && e.code === 50035) {
51-
// If the original message was deleted before we could reply, ignore the error
52-
} else {
53-
this.wheatley.critical_error(e);
54-
}
55-
}
43+
const reply = await message.reply({
44+
content: `Did you really just try to ping ${member_count} people?`,
45+
});
46+
47+
// Store the reply for later deletion, along with the message it was replying to
48+
unwrap(this.replies.get(message.author)).push({ reply_to: message.id, reply });
5649
}
5750
}
5851

src/modules/wheatley/components/anti-executable.ts

Lines changed: 43 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,8 @@ import { Virustotal } from "../../../infra/virustotal.js";
1111
import Mute from "./moderation/mute.js";
1212
import { delay, unwrap } from "../../../utils/misc.js";
1313
import { CommandSetBuilder } from "../../../command-abstractions/command-set-builder.js";
14-
import { M } from "../../../utils/debugging-and-logging.js";
1514

1615
const ACTION_THRESHOLD = 5;
17-
const BASE_RETRY_DELAY_MS = 1000;
18-
const NOT_FOUND_RETRY_DELAY_MS = 3000;
1916

2017
class HTTPError extends Error {
2118
constructor(
@@ -160,53 +157,21 @@ export default class AntiExecutable extends BotComponent {
160157
return true;
161158
}
162159

163-
private async fetch_with_retry(
164-
url: string,
165-
options?: { limit?: number; max_retries?: number },
166-
): Promise<Buffer | null> {
167-
// Try to fetch, retrying on 5xx failure. If the error is 404, retry once.
168-
const { limit, max_retries = 3 } = options ?? {};
169-
let attempt = 0;
170-
let not_found_retry_used = false;
171-
172-
while (true) {
160+
private async fetch_with_retry(url: string, limit?: number, max_retries = 3): Promise<Buffer> {
161+
const base_delay_ms = 1000;
162+
for (let attempt = 0; attempt <= max_retries; attempt++) {
173163
try {
174164
return await this.fetch(url, limit);
175165
} catch (e) {
176-
if (e instanceof HTTPError && e.status_code === 404) {
177-
if (not_found_retry_used) {
178-
return null;
179-
}
180-
not_found_retry_used = true;
181-
await delay(NOT_FOUND_RETRY_DELAY_MS);
182-
continue;
183-
}
184-
if (attempt >= max_retries || !this.is_retryable_error(e)) {
166+
const is_last_attempt = attempt === max_retries;
167+
if (is_last_attempt || !this.is_retryable_error(e)) {
185168
throw e;
186169
}
187-
await delay(BASE_RETRY_DELAY_MS * Math.pow(2, attempt));
188-
attempt++;
189-
}
190-
}
191-
}
192-
193-
private async fetch_attachment(url: string, message_url: string, limit?: number): Promise<Buffer | null> {
194-
try {
195-
const buffer = await this.fetch_with_retry(url, { limit });
196-
if (buffer === null) {
197-
M.info(`HTTP 404 while fetching attachment for message ${message_url}: \`${url}\``);
198-
}
199-
return buffer;
200-
} catch (e) {
201-
if (e instanceof HTTPError) {
202-
this.wheatley.warn(
203-
`HTTP Error ${e.status_code} while fetching attachment for message ${message_url}: \`${url}\``,
204-
);
205-
} else {
206-
this.wheatley.critical_error(e);
170+
const delay_ms = base_delay_ms * Math.pow(2, attempt);
171+
await delay(delay_ms);
207172
}
208-
return null;
209173
}
174+
throw new Error("Unreachable");
210175
}
211176

212177
async virustotal_scan(
@@ -274,10 +239,24 @@ export default class AntiExecutable extends BotComponent {
274239
) {
275240
await Promise.all(
276241
attachments.map(async attachment => {
277-
const buffer = await this.fetch_attachment(attachment.url, message_url);
278-
if (buffer !== null) {
279-
await this.virustotal_scan(buffer, flag_message, author, original_message);
242+
// download
243+
let file_buffer: Buffer;
244+
try {
245+
file_buffer = await this.fetch_with_retry(attachment.url);
246+
} catch (e) {
247+
if (e instanceof HTTPError) {
248+
this.wheatley.warn(
249+
`HTTP Error ${e.status_code} while fetching attachment for ` +
250+
`message ${message_url}: \`${attachment.url}\``,
251+
);
252+
return;
253+
} else {
254+
this.wheatley.critical_error(e);
255+
return;
256+
}
280257
}
258+
// virustotal
259+
await this.virustotal_scan(file_buffer, flag_message, author, original_message);
281260
}),
282261
);
283262
}
@@ -314,14 +293,24 @@ export default class AntiExecutable extends BotComponent {
314293
const executables: Discord.Attachment[] = [];
315294
const archives: Discord.Attachment[] = [];
316295
for (const [_, attachment] of message.attachments) {
317-
const buffer = await this.fetch_attachment(attachment.url, message.url, 512);
318-
if (buffer === null) {
319-
return;
320-
}
321-
if (this.looks_like_executable(buffer)) {
322-
executables.push(attachment);
323-
} else if (this.looks_like_archive(buffer)) {
324-
archives.push(attachment);
296+
try {
297+
const res = await this.fetch_with_retry(attachment.url, 512);
298+
if (this.looks_like_executable(res)) {
299+
executables.push(attachment);
300+
} else if (this.looks_like_archive(res)) {
301+
archives.push(attachment);
302+
}
303+
} catch (e) {
304+
if (e instanceof HTTPError) {
305+
this.wheatley.warn(
306+
`HTTP Error ${e.status_code} while fetching attachment for ` +
307+
`message ${message.url}: \`${attachment.url}\``,
308+
);
309+
return;
310+
} else {
311+
this.wheatley.critical_error(e);
312+
return;
313+
}
325314
}
326315
}
327316
if (executables.length > 0) {

0 commit comments

Comments
 (0)