feat: add searchMessages function and corresponding API endpoint for message queries
This commit is contained in:
@@ -121,7 +121,10 @@ export class MediaController {
|
||||
this.assertCanStartMusic();
|
||||
this.queueStore.add(resolved, mode, options.requestedBy);
|
||||
logger.info(
|
||||
{ title: resolved.title, queueSize: this.queueStore.snapshot().queue.length },
|
||||
{
|
||||
title: resolved.title,
|
||||
queueSize: this.queueStore.snapshot().queue.length,
|
||||
},
|
||||
"Added to queue",
|
||||
);
|
||||
this.startNextIfIdle();
|
||||
@@ -225,7 +228,11 @@ export class MediaController {
|
||||
|
||||
const token = ++this.playbackToken;
|
||||
logger.info(
|
||||
{ title: item.title, token, queueSize: this.queueStore.snapshot().queue.length },
|
||||
{
|
||||
title: item.title,
|
||||
token,
|
||||
queueSize: this.queueStore.snapshot().queue.length,
|
||||
},
|
||||
"Starting playback",
|
||||
);
|
||||
try {
|
||||
|
||||
@@ -72,7 +72,10 @@ export function createMusicPlayer(
|
||||
const errorMsg = `ffmpeg exited with code ${code}`;
|
||||
console.error("[musicPlayer]", errorMsg);
|
||||
if (stderrOutput) {
|
||||
console.error("[musicPlayer] ffmpeg stderr:", stderrOutput.slice(-500));
|
||||
console.error(
|
||||
"[musicPlayer] ffmpeg stderr:",
|
||||
stderrOutput.slice(-500),
|
||||
);
|
||||
}
|
||||
reject(new Error(errorMsg));
|
||||
});
|
||||
@@ -92,16 +95,12 @@ export function createMusicPlayer(
|
||||
}
|
||||
|
||||
export function buildFfmpegArgs(source: string): string[] {
|
||||
const args = [
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"warning",
|
||||
];
|
||||
const args = ["-hide_banner", "-loglevel", "warning"];
|
||||
|
||||
if (source.startsWith("http://") || source.startsWith("https://")) {
|
||||
args.push(
|
||||
"-user_agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,6 +119,10 @@ export function buildFfmpegArgs(source: string): string[] {
|
||||
"pipe:1",
|
||||
);
|
||||
|
||||
console.log("[ffmpeg] Command:", "ffmpeg", args.join(" ").slice(0, 200) + "...");
|
||||
console.log(
|
||||
"[ffmpeg] Command:",
|
||||
"ffmpeg",
|
||||
args.join(" ").slice(0, 200) + "...",
|
||||
);
|
||||
return args;
|
||||
}
|
||||
|
||||
+4
-1
@@ -53,7 +53,10 @@ export function createYtDlp(dependencies: YtDlpDependencies = {}): YtDlpClient {
|
||||
console.warn("[ytdlp] No audio URL returned for:", url);
|
||||
throw new Error(`Failed to resolve audio URL for: ${url}`);
|
||||
}
|
||||
console.log("[ytdlp] Resolved audio URL:", directUrl.slice(0, 100) + "...");
|
||||
console.log(
|
||||
"[ytdlp] Resolved audio URL:",
|
||||
directUrl.slice(0, 100) + "...",
|
||||
);
|
||||
return directUrl;
|
||||
},
|
||||
|
||||
|
||||
@@ -188,7 +188,9 @@ function scheduleConversationAnalysis(conversationKey: string): void {
|
||||
|
||||
// If we have available slots, process immediately with shorter debounce
|
||||
const debounceTime =
|
||||
activeRequests < MAX_ACTIVE_REQUESTS ? Math.min(DEBOUNCE_MS, 500) : DEBOUNCE_MS;
|
||||
activeRequests < MAX_ACTIVE_REQUESTS
|
||||
? Math.min(DEBOUNCE_MS, 500)
|
||||
: DEBOUNCE_MS;
|
||||
|
||||
// Set new debounced timer
|
||||
const timer = setTimeout(async () => {
|
||||
|
||||
@@ -180,8 +180,20 @@ export function parseModerationResponse(
|
||||
// Check that all target IDs were found
|
||||
const missingIds = targetIds.filter((id) => !foundIds.has(id));
|
||||
if (missingIds.length > 0) {
|
||||
log.warn({ missingIds }, "Some target IDs missing in response");
|
||||
throw new Error(`Missing target IDs: ${missingIds.join(",")}`);
|
||||
log.warn(
|
||||
{ missingIds, foundCount: foundIds.size, totalCount: targetIds.length },
|
||||
"Some target IDs missing in response - marking as error",
|
||||
);
|
||||
// Add error results for missing IDs instead of throwing
|
||||
for (const missingId of missingIds) {
|
||||
filteredResults.push({
|
||||
messageId: missingId,
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0,
|
||||
analysis: "Analysis incomplete - LLM did not process this message",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return filteredResults;
|
||||
|
||||
@@ -639,3 +639,54 @@ export async function getAttachmentsForMessages(
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchMessages(input: {
|
||||
query: string;
|
||||
channelId?: string;
|
||||
limit?: number;
|
||||
}): Promise<MessageRecord[]> {
|
||||
try {
|
||||
const { query, channelId, limit = 20 } = input;
|
||||
const database = db();
|
||||
|
||||
const searchPattern = `%${query}%`;
|
||||
const conditions: (SQL | undefined)[] = [isNull(messagesTable.deleted_at)];
|
||||
|
||||
if (channelId) {
|
||||
conditions.push(
|
||||
or(
|
||||
eq(messagesTable.channel_id, channelId),
|
||||
eq(messagesTable.thread_id, channelId),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
conditions.push(
|
||||
or(
|
||||
sql`${messagesTable.content} LIKE ${searchPattern}`,
|
||||
sql`${messagesTable.edited_content} LIKE ${searchPattern}`,
|
||||
),
|
||||
);
|
||||
|
||||
const validConditions = conditions.filter((c): c is SQL => c !== undefined);
|
||||
|
||||
const rows = await database
|
||||
.select()
|
||||
.from(messagesTable)
|
||||
.where(and(...validConditions))
|
||||
.orderBy(desc(messagesTable.created_at))
|
||||
.limit(limit);
|
||||
|
||||
return rows as MessageRecord[];
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
{
|
||||
query: input.query,
|
||||
channelId: input.channelId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
"Failed to search messages",
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import type { Router } from "express";
|
||||
import express from "express";
|
||||
import { AppError } from "../errors";
|
||||
import type { MessageRecord } from "../moderation/types";
|
||||
import {
|
||||
getAnalysisQueueStatus,
|
||||
queueMessageAnalysis,
|
||||
} from "../moderation/aiAnalyzer";
|
||||
import {
|
||||
getMessageById,
|
||||
searchMessages,
|
||||
updateMessageAIAnalysis,
|
||||
} from "../moderation/messageStore";
|
||||
|
||||
@@ -23,6 +25,51 @@ export function createAnalysisRoutes(): Router {
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/analysis/search - Search for message IDs by query
|
||||
router.get("/analysis/search", async (req, res, next) => {
|
||||
try {
|
||||
const {
|
||||
q,
|
||||
channelId,
|
||||
limit = "20",
|
||||
} = req.query as {
|
||||
q?: string;
|
||||
channelId?: string;
|
||||
limit?: string;
|
||||
};
|
||||
|
||||
if (!q) {
|
||||
throw new AppError(
|
||||
"Query parameter 'q' is required",
|
||||
"MISSING_QUERY",
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
const limitNum = Math.min(parseInt(limit) || 20, 100);
|
||||
|
||||
const results = await searchMessages({
|
||||
query: q,
|
||||
channelId,
|
||||
limit: limitNum,
|
||||
});
|
||||
|
||||
res.json({
|
||||
query: q,
|
||||
count: results.length,
|
||||
results: results.map((msg: MessageRecord) => ({
|
||||
id: msg.id,
|
||||
content: msg.edited_content ?? msg.content,
|
||||
username: msg.username,
|
||||
created_at: msg.created_at,
|
||||
ai_status: msg.ai_status,
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/messages/:id/reanalyze - Queue a message for re-analysis
|
||||
router.post("/messages/:id/reanalyze", async (req, res, next) => {
|
||||
try {
|
||||
|
||||
@@ -31,16 +31,15 @@ export class Transcoder {
|
||||
const bitrate = String(this.opts.bitrate ?? "2500k");
|
||||
const preset = this.opts.preset ?? "superfast";
|
||||
|
||||
const args = [
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"warning",
|
||||
];
|
||||
const args = ["-hide_banner", "-loglevel", "warning"];
|
||||
|
||||
if (this.source.startsWith("http://") || this.source.startsWith("https://")) {
|
||||
if (
|
||||
this.source.startsWith("http://") ||
|
||||
this.source.startsWith("https://")
|
||||
) {
|
||||
args.push(
|
||||
"-user_agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,7 +62,7 @@ export class Transcoder {
|
||||
"libopus",
|
||||
"-f",
|
||||
"matroska",
|
||||
"-"
|
||||
"-",
|
||||
);
|
||||
|
||||
const cmd = spawn("ffmpeg", args, { stdio: ["ignore", "pipe", "pipe"] });
|
||||
|
||||
Reference in New Issue
Block a user