102 lines
2.7 KiB
TypeScript
102 lines
2.7 KiB
TypeScript
// Shared helpers for parsing MEDIA tokens from command/stdout text.
|
|
|
|
// Allow optional wrapping backticks and punctuation after the token; capture the core token.
|
|
export const MEDIA_TOKEN_RE = /\bMEDIA:\s*`?([^\n]+)`?/gi;
|
|
|
|
export function normalizeMediaSource(src: string) {
|
|
return src.startsWith("file://") ? src.replace("file://", "") : src;
|
|
}
|
|
|
|
function cleanCandidate(raw: string) {
|
|
return raw.replace(/^[`"'[{(]+/, "").replace(/[`"'\\})\],]+$/, "");
|
|
}
|
|
|
|
function isValidMedia(candidate: string) {
|
|
if (!candidate) return false;
|
|
if (candidate.length > 1024) return false;
|
|
if (/\s/.test(candidate)) return false;
|
|
return (
|
|
/^https?:\/\//i.test(candidate) ||
|
|
candidate.startsWith("/") ||
|
|
candidate.startsWith("./")
|
|
);
|
|
}
|
|
|
|
export function splitMediaFromOutput(raw: string): {
|
|
text: string;
|
|
mediaUrls?: string[];
|
|
mediaUrl?: string; // legacy first item for backward compatibility
|
|
} {
|
|
const trimmedRaw = raw.trim();
|
|
if (!trimmedRaw) return { text: "" };
|
|
|
|
const media: string[] = [];
|
|
let foundMediaToken = false;
|
|
|
|
// Collect tokens line by line so we can strip them cleanly.
|
|
const lines = trimmedRaw.split("\n");
|
|
const keptLines: string[] = [];
|
|
|
|
for (const line of lines) {
|
|
const matches = Array.from(line.matchAll(MEDIA_TOKEN_RE));
|
|
if (matches.length === 0) {
|
|
keptLines.push(line);
|
|
continue;
|
|
}
|
|
|
|
foundMediaToken = true;
|
|
const pieces: string[] = [];
|
|
let cursor = 0;
|
|
let hasValidMedia = false;
|
|
|
|
for (const match of matches) {
|
|
const start = match.index ?? 0;
|
|
pieces.push(line.slice(cursor, start));
|
|
|
|
const payload = match[1];
|
|
const parts = payload.split(/\s+/).filter(Boolean);
|
|
const invalidParts: string[] = [];
|
|
for (const part of parts) {
|
|
const candidate = normalizeMediaSource(cleanCandidate(part));
|
|
if (isValidMedia(candidate)) {
|
|
media.push(candidate);
|
|
hasValidMedia = true;
|
|
} else {
|
|
invalidParts.push(part);
|
|
}
|
|
}
|
|
|
|
if (hasValidMedia && invalidParts.length > 0) {
|
|
pieces.push(invalidParts.join(" "));
|
|
}
|
|
|
|
cursor = start + match[0].length;
|
|
}
|
|
|
|
pieces.push(line.slice(cursor));
|
|
|
|
const cleanedLine = pieces
|
|
.join("")
|
|
.replace(/[ \t]{2,}/g, " ")
|
|
.trim();
|
|
|
|
// If the line becomes empty, drop it.
|
|
if (cleanedLine) {
|
|
keptLines.push(cleanedLine);
|
|
}
|
|
}
|
|
|
|
const cleanedText = keptLines
|
|
.join("\n")
|
|
.replace(/[ \t]+\n/g, "\n")
|
|
.replace(/[ \t]{2,}/g, " ")
|
|
.replace(/\n{2,}/g, "\n")
|
|
.trim();
|
|
|
|
if (media.length === 0) {
|
|
return { text: foundMediaToken ? cleanedText : trimmedRaw };
|
|
}
|
|
|
|
return { text: cleanedText, mediaUrls: media, mediaUrl: media[0] };
|
|
}
|