Files
tgex-frontend/lib/utils/file-parser.ts
2025-11-19 12:56:04 +03:00

192 lines
5.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================================
// 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<ParseResult> => {
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<ParseResult> => {
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<ParseResult> => {
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();
}
};