mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-11 06:11:15 +00:00
Adds full Telegram ZIP ingestion pipeline: TDLib worker service scans source channels for archive files, deduplicates by content hash, extracts metadata, uploads to archive channel, and indexes in Postgres. Forum supergroups are scanned per-topic with topic names used as creator. Filename-based creator extraction (e.g. "Mammoth Factory - 2026-01.zip") serves as fallback. Includes admin UI for managing accounts/channels, simplified account setup (API credentials via env vars), auth code/password submission dialog, package browser with creator column, and live ingestion activity tracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
import yauzl from "yauzl";
|
|
import path from "path";
|
|
import { childLogger } from "../util/logger.js";
|
|
|
|
const log = childLogger("zip-reader");
|
|
|
|
export interface FileEntry {
|
|
path: string;
|
|
fileName: string;
|
|
extension: string | null;
|
|
compressedSize: bigint;
|
|
uncompressedSize: bigint;
|
|
crc32: string | null;
|
|
}
|
|
|
|
/**
|
|
* Read the central directory of a ZIP file without extracting any contents.
|
|
* For multipart ZIPs, pass the paths sorted by part order.
|
|
* We attempt to read from the last part first (central directory is at the end).
|
|
*/
|
|
export async function readZipCentralDirectory(
|
|
filePaths: string[]
|
|
): Promise<FileEntry[]> {
|
|
// The central directory lives at the end of the last file
|
|
const targetFile = filePaths[filePaths.length - 1];
|
|
|
|
return new Promise((resolve, reject) => {
|
|
yauzl.open(targetFile, { lazyEntries: true, autoClose: true }, (err, zipFile) => {
|
|
if (err) {
|
|
log.warn({ err, file: targetFile }, "Failed to open ZIP for reading");
|
|
resolve([]); // Fallback: return empty on error
|
|
return;
|
|
}
|
|
|
|
const entries: FileEntry[] = [];
|
|
|
|
zipFile.readEntry();
|
|
zipFile.on("entry", (entry: yauzl.Entry) => {
|
|
// Skip directories
|
|
if (!entry.fileName.endsWith("/")) {
|
|
const ext = path.extname(entry.fileName).toLowerCase();
|
|
entries.push({
|
|
path: entry.fileName,
|
|
fileName: path.basename(entry.fileName),
|
|
extension: ext ? ext.slice(1) : null, // Remove leading dot
|
|
compressedSize: BigInt(entry.compressedSize),
|
|
uncompressedSize: BigInt(entry.uncompressedSize),
|
|
crc32: entry.crc32 !== 0 ? entry.crc32.toString(16).padStart(8, "0") : null,
|
|
});
|
|
}
|
|
zipFile.readEntry();
|
|
});
|
|
|
|
zipFile.on("end", () => resolve(entries));
|
|
zipFile.on("error", (error) => {
|
|
log.warn({ error, file: targetFile }, "Error reading ZIP entries");
|
|
resolve(entries); // Return whatever we got
|
|
});
|
|
});
|
|
});
|
|
}
|