import { isOneShotRoutineCrons } from "@rakazo/core"; /** A relative, forward-slash path without `.`/`..` segments, or null when it could escape. */ export function safeImportPath(raw: string): string | null { const normalized = raw.replace(/\\/g, "/"); if (normalized.startsWith("/") || /^[a-z]:/i.test(normalized)) return null; const segments = normalized.split("/").filter((segment) => segment !== "" && segment !== "."); if (segments.length === 0 || segments.includes("..")) return null; return segments.join("/"); } /** * Exports of bots on a Team Computer carry their workspace prefix (`bots//…`). * The prefix names the old bot, so it is dropped and the importer writes under the new one. */ export function relativeImportedFiles( files: T[], ): Array | null { const safe: Array = []; for (const file of files) { const path = safeImportPath(file.path); if (!path) return null; safe.push({ ...file, path }); } const first = safe[0]?.path.split("/"); const workspace = first && first.length > 2 && first[0] === "bots" ? `bots/${first[1]}/` : null; if (!workspace || !safe.every((file) => file.path.startsWith(workspace))) return safe; return safe.map((file) => ({ ...file, path: file.path.slice(workspace.length) })); } /** One-shot schedules belong to the chat that set them, so they are not carried over. */ export function importableRoutines(routines: T[]): T[] { return routines.filter((routine) => !isOneShotRoutineCrons(routine.crons)); }