Answer the search command immediately, and don't wait on a slow source
A search looked like it did nothing: the log shows the two sources finishing 21 seconds apart, and since the command message is deleted on sight, the channel stayed empty that whole time with no sign the bot had heard anything. The reply now goes out at once as "Ищу …" and is edited into the results, so there is always something on screen, and each source gets 12 seconds before the answer goes out without it — searching several at once otherwise means always waiting for the slowest, which behind a proxy is tens of seconds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e7e80e704b
commit
bf2d04d579
+13
-3
@@ -13,10 +13,11 @@ import {
|
||||
trackTitle,
|
||||
} from "./format.js";
|
||||
|
||||
/** What we need back from a sent message to hang reactions on it. */
|
||||
/** What we need back from a sent message to work with it afterwards. */
|
||||
export interface SentMessage {
|
||||
id: string;
|
||||
react(emoji: string): Promise<void>;
|
||||
edit(content: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface CommandContext {
|
||||
@@ -170,18 +171,27 @@ export const commands: Command[] = [
|
||||
description: "Найти треки и выбрать реакцией",
|
||||
async run(ctx) {
|
||||
if (!ctx.rest) throw new UserFacingError("Укажите поисковый запрос");
|
||||
// Searching several sources can take tens of seconds through a proxy, and
|
||||
// the command message is already deleted by then — without a placeholder
|
||||
// the chat sits silent and the bot looks broken.
|
||||
const placeholder = await ctx.reply(`🔎 Ищу **${ctx.rest}**…`);
|
||||
|
||||
const tracks = (await ctx.manager.search(ctx.rest, ctx.actor, CHAT_SEARCH_LIMIT)).slice(
|
||||
0,
|
||||
CHAT_SEARCH_LIMIT,
|
||||
);
|
||||
if (tracks.length === 0) {
|
||||
await ctx.reply(`🔎 ${NOTHING_FOUND}`);
|
||||
const missing = `🔎 ${NOTHING_FOUND}`;
|
||||
if (placeholder) await placeholder.edit(missing);
|
||||
else await ctx.reply(missing);
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = tracks.map((track, index) => trackLine(track, { index: index + 1 }));
|
||||
const sent = await ctx.reply(`🔎 **${ctx.rest}**\n${lines.join("\n")}`);
|
||||
const text = `🔎 **${ctx.rest}**\n${lines.join("\n")}`;
|
||||
const sent = placeholder ?? (await ctx.reply(text));
|
||||
if (!sent) return;
|
||||
if (placeholder) await placeholder.edit(text);
|
||||
|
||||
rememberSearch(ctx, sent.id, tracks);
|
||||
for (const emoji of CHOICE_EMOJI.slice(0, tracks.length)) {
|
||||
|
||||
+11
-1
@@ -82,7 +82,17 @@ async function handleMessage(manager: MusicManager, message: Message): Promise<v
|
||||
if (!message.authorId || message.author?.bot) return;
|
||||
|
||||
const serverId = message.server?.id;
|
||||
const reply = async (text: string) => message.channel?.sendMessage(text);
|
||||
const reply = async (text: string) => {
|
||||
const sent = await message.channel?.sendMessage(text);
|
||||
if (!sent) return undefined;
|
||||
return {
|
||||
id: sent.id,
|
||||
react: (emoji: string) => sent.react(emoji),
|
||||
edit: async (content: string) => {
|
||||
await sent.edit({ content });
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
if (!serverId) {
|
||||
await reply("Команды работают только внутри сервера.");
|
||||
|
||||
+21
-2
@@ -135,13 +135,32 @@ export async function searchTracks(
|
||||
}
|
||||
|
||||
const [youtube, soundcloud, localHits] = await Promise.all([
|
||||
ytdlp.search(text, "youtube", limit, requestedBy).catch(() => []),
|
||||
ytdlp.search(text, "soundcloud", limit, requestedBy).catch(() => []),
|
||||
withDeadline(ytdlp.search(text, "youtube", limit, requestedBy), "youtube"),
|
||||
withDeadline(ytdlp.search(text, "soundcloud", limit, requestedBy), "soundcloud"),
|
||||
local.isEnabled() ? local.search(text, 3, requestedBy).catch(() => []) : Promise.resolve([]),
|
||||
]);
|
||||
return [...localHits, ...interleave(youtube, soundcloud)].slice(0, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* One slow source should not hold up the whole answer: searching several at once
|
||||
* means waiting for the slowest, and behind a proxy that can be tens of seconds.
|
||||
*/
|
||||
const SOURCE_DEADLINE_MS = 12_000;
|
||||
|
||||
async function withDeadline(search: Promise<Track[]>, source: string): Promise<Track[]> {
|
||||
let timer: NodeJS.Timeout | undefined;
|
||||
const deadline = new Promise<Track[]>((resolve) => {
|
||||
timer = setTimeout(() => {
|
||||
log.warn({ source }, "search took too long, answering without it");
|
||||
resolve([]);
|
||||
}, SOURCE_DEADLINE_MS);
|
||||
});
|
||||
const tracks = await Promise.race([search.catch(() => []), deadline]);
|
||||
if (timer) clearTimeout(timer);
|
||||
return tracks;
|
||||
}
|
||||
|
||||
/**
|
||||
* YouTube silently returns nothing for queries its restricted mode dislikes —
|
||||
* same query, same words, results in a browser but an empty list over the API.
|
||||
|
||||
Reference in New Issue
Block a user