// ============================================================================ // File Parser Utilities // ============================================================================ import * as XLSX from "xlsx"; import Papa from "papaparse"; export interface ParsedChannel { telegram_id?: number; title: string; username?: string; description?: string; subscribers_count?: number; row: number; // Номер строки для отчетов об ошибках } export interface ParseResult { channels: ParsedChannel[]; errors: string[]; } /** * Парсит Excel файл (xlsx) */ export const parseExcelFile = async (file: File): Promise => { const errors: string[] = []; const channels: ParsedChannel[] = []; try { const arrayBuffer = await file.arrayBuffer(); const workbook = XLSX.read(arrayBuffer, { type: "array" }); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); // Пропускаем заголовок (первая строка) const rows = jsonData.slice(1) as any[][]; rows.forEach((row, index) => { const rowNumber = index + 2; // +2 потому что пропустили заголовок и индекс с 0 // Пропускаем пустые строки if (!row || row.length === 0 || !row[0]) { return; } try { const channel: ParsedChannel = { row: rowNumber, title: String(row[0] || "").trim(), username: row[1] ? String(row[1]).trim().replace("@", "") : undefined, telegram_id: row[2] ? parseInt(String(row[2])) : undefined, subscribers_count: row[3] ? parseInt(String(row[3])) : undefined, description: row[4] ? String(row[4]).trim() : undefined, }; if (!channel.title) { errors.push(`Строка ${rowNumber}: отсутствует название канала`); return; } channels.push(channel); } catch (err) { errors.push( `Строка ${rowNumber}: ошибка парсинга - ${ err instanceof Error ? err.message : "неизвестная ошибка" }` ); } }); } catch (err) { errors.push( `Ошибка чтения файла: ${ err instanceof Error ? err.message : "неизвестная ошибка" }` ); } return { channels, errors }; }; /** * Парсит CSV файл */ export const parseCsvFile = async (file: File): Promise => { return new Promise((resolve) => { const errors: string[] = []; const channels: ParsedChannel[] = []; Papa.parse(file, { header: false, skipEmptyLines: true, complete: (results) => { const rows = results.data as string[][]; // Пропускаем заголовок rows.slice(1).forEach((row, index) => { const rowNumber = index + 2; if (!row || row.length === 0 || !row[0]) { return; } try { const channel: ParsedChannel = { row: rowNumber, title: (row[0] || "").trim(), username: row[1] ? row[1].trim().replace("@", "") : undefined, telegram_id: row[2] ? parseInt(row[2]) : undefined, subscribers_count: row[3] ? parseInt(row[3]) : undefined, description: row[4] ? row[4].trim() : undefined, }; if (!channel.title) { errors.push(`Строка ${rowNumber}: отсутствует название канала`); return; } channels.push(channel); } catch (err) { errors.push( `Строка ${rowNumber}: ошибка парсинга - ${ err instanceof Error ? err.message : "неизвестная ошибка" }` ); } }); resolve({ channels, errors }); }, error: (err) => { errors.push(`Ошибка парсинга CSV: ${err.message}`); resolve({ channels: [], errors }); }, }); }); }; /** * Универсальная функция парсинга файла */ export const parseChannelsFile = async (file: File): Promise => { const extension = file.name.split(".").pop()?.toLowerCase(); if (extension === "xlsx" || extension === "xls") { return parseExcelFile(file); } else if (extension === "csv") { return parseCsvFile(file); } else { return { channels: [], errors: [ "Неподдерживаемый формат файла. Поддерживаются только .xlsx, .xls и .csv", ], }; } }; /** * Скачать шаблон для импорта */ export const downloadTemplate = (format: "xlsx" | "csv") => { const headers = [ "Название канала*", "Username (без @)", "Telegram ID", "Подписчиков", "Описание", ]; const example = [ "Пример канала", "example_channel", "-1001234567890", "10000", "Описание канала", ]; if (format === "xlsx") { const ws = XLSX.utils.aoa_to_sheet([headers, example]); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Channels"); XLSX.writeFile(wb, "channels_template.xlsx"); } else { const csv = Papa.unparse([headers, example]); const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = "channels_template.csv"; link.click(); } };