koztv-blog-tools 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.d.mts CHANGED
@@ -25,6 +25,8 @@ interface TelegramExportOptions {
25
25
  until?: Date;
26
26
  /** Download media files */
27
27
  downloadMedia?: boolean;
28
+ /** Skip posts that already have markdown files */
29
+ skipExisting?: boolean;
28
30
  /** Number of concurrent media downloads */
29
31
  mediaWorkers?: number;
30
32
  /** Callback for progress updates */
package/dist/node.d.ts CHANGED
@@ -25,6 +25,8 @@ interface TelegramExportOptions {
25
25
  until?: Date;
26
26
  /** Download media files */
27
27
  downloadMedia?: boolean;
28
+ /** Skip posts that already have markdown files */
29
+ skipExisting?: boolean;
28
30
  /** Number of concurrent media downloads */
29
31
  mediaWorkers?: number;
30
32
  /** Callback for progress updates */
package/dist/node.js CHANGED
@@ -375,6 +375,30 @@ var import_sessions = require("telegram/sessions/index.js");
375
375
  var fs = __toESM(require("fs"));
376
376
  var path = __toESM(require("path"));
377
377
  var readline = __toESM(require("readline"));
378
+ var import_child_process = require("child_process");
379
+ var MAX_FILE_SIZE = 95 * 1024 * 1024;
380
+ var COMPRESS_THRESHOLD = 50 * 1024 * 1024;
381
+ function hasFFmpeg() {
382
+ try {
383
+ (0, import_child_process.execSync)("ffmpeg -version", { stdio: "ignore" });
384
+ return true;
385
+ } catch {
386
+ return false;
387
+ }
388
+ }
389
+ function compressVideo(inputPath, outputPath) {
390
+ try {
391
+ (0, import_child_process.execSync)(
392
+ `ffmpeg -i "${inputPath}" -vf "scale='min(1280,iw)':'-2'" -c:v libx264 -crf 28 -preset fast -c:a aac -b:a 128k -y "${outputPath}"`,
393
+ { stdio: "ignore", timeout: 3e5 }
394
+ // 5 min timeout
395
+ );
396
+ const stats = fs.statSync(outputPath);
397
+ return stats.size <= MAX_FILE_SIZE;
398
+ } catch {
399
+ return false;
400
+ }
401
+ }
378
402
  function entitiesToMarkdown(text, entities) {
379
403
  if (!entities || entities.length === 0) return text;
380
404
  const mergedEntities = [];
@@ -514,6 +538,7 @@ async function exportTelegramChannel(options) {
514
538
  since,
515
539
  until,
516
540
  downloadMedia = true,
541
+ skipExisting = false,
517
542
  mediaWorkers = 3,
518
543
  onProgress,
519
544
  onPhoneNumber = () => defaultReadline("Phone number: "),
@@ -611,7 +636,14 @@ async function exportTelegramChannel(options) {
611
636
  }
612
637
  const msgId = message.id;
613
638
  const paddedId = String(msgId).padStart(6, "0");
639
+ const postPath = path.join(postsDir, `${paddedId}.md`);
614
640
  const postMediaDir = path.join(mediaDir, paddedId);
641
+ if (skipExisting && fs.existsSync(postPath)) {
642
+ if (onProgress) {
643
+ onProgress(processedCount, totalMessages, `Skipping ${paddedId} (exists)`);
644
+ }
645
+ continue;
646
+ }
615
647
  const mediaFiles = [];
616
648
  if (downloadMedia && message.media) {
617
649
  const existingFiles = fs.existsSync(postMediaDir) ? fs.readdirSync(postMediaDir).filter((f) => f.startsWith("media.")) : [];
@@ -647,7 +679,39 @@ async function exportTelegramChannel(options) {
647
679
  const mediaFileName = `media${ext}`;
648
680
  const mediaPath = path.join(postMediaDir, mediaFileName);
649
681
  fs.writeFileSync(mediaPath, buffer);
650
- mediaFiles.push(`media/${paddedId}/${mediaFileName}`);
682
+ const stats = fs.statSync(mediaPath);
683
+ const isVideo = [".mp4", ".mov", ".avi", ".webm", ".m4v"].includes(ext.toLowerCase());
684
+ if (stats.size > MAX_FILE_SIZE) {
685
+ if (isVideo && hasFFmpeg()) {
686
+ const compressedPath = path.join(postMediaDir, `media_compressed${ext}`);
687
+ if (compressVideo(mediaPath, compressedPath)) {
688
+ fs.unlinkSync(mediaPath);
689
+ fs.renameSync(compressedPath, mediaPath);
690
+ mediaFiles.push(`media/${paddedId}/${mediaFileName}`);
691
+ } else {
692
+ fs.unlinkSync(mediaPath);
693
+ if (fs.existsSync(compressedPath)) fs.unlinkSync(compressedPath);
694
+ console.warn(`Skipping large media for message ${msgId} (>${MAX_FILE_SIZE / 1024 / 1024}MB)`);
695
+ }
696
+ } else {
697
+ fs.unlinkSync(mediaPath);
698
+ console.warn(`Skipping large media for message ${msgId} (>${MAX_FILE_SIZE / 1024 / 1024}MB)`);
699
+ }
700
+ } else if (stats.size > COMPRESS_THRESHOLD && isVideo && hasFFmpeg()) {
701
+ const compressedPath = path.join(postMediaDir, `media_compressed${ext}`);
702
+ if (compressVideo(mediaPath, compressedPath)) {
703
+ const compressedStats = fs.statSync(compressedPath);
704
+ if (compressedStats.size < stats.size) {
705
+ fs.unlinkSync(mediaPath);
706
+ fs.renameSync(compressedPath, mediaPath);
707
+ } else {
708
+ fs.unlinkSync(compressedPath);
709
+ }
710
+ }
711
+ mediaFiles.push(`media/${paddedId}/${mediaFileName}`);
712
+ } else {
713
+ mediaFiles.push(`media/${paddedId}/${mediaFileName}`);
714
+ }
651
715
  }
652
716
  } catch (e) {
653
717
  console.error(`Error downloading media for message ${msgId}:`, e);
package/dist/node.mjs CHANGED
@@ -25,6 +25,30 @@ import { StringSession } from "telegram/sessions/index.js";
25
25
  import * as fs from "fs";
26
26
  import * as path from "path";
27
27
  import * as readline from "readline";
28
+ import { execSync } from "child_process";
29
+ var MAX_FILE_SIZE = 95 * 1024 * 1024;
30
+ var COMPRESS_THRESHOLD = 50 * 1024 * 1024;
31
+ function hasFFmpeg() {
32
+ try {
33
+ execSync("ffmpeg -version", { stdio: "ignore" });
34
+ return true;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+ function compressVideo(inputPath, outputPath) {
40
+ try {
41
+ execSync(
42
+ `ffmpeg -i "${inputPath}" -vf "scale='min(1280,iw)':'-2'" -c:v libx264 -crf 28 -preset fast -c:a aac -b:a 128k -y "${outputPath}"`,
43
+ { stdio: "ignore", timeout: 3e5 }
44
+ // 5 min timeout
45
+ );
46
+ const stats = fs.statSync(outputPath);
47
+ return stats.size <= MAX_FILE_SIZE;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
28
52
  function entitiesToMarkdown(text, entities) {
29
53
  if (!entities || entities.length === 0) return text;
30
54
  const mergedEntities = [];
@@ -164,6 +188,7 @@ async function exportTelegramChannel(options) {
164
188
  since,
165
189
  until,
166
190
  downloadMedia = true,
191
+ skipExisting = false,
167
192
  mediaWorkers = 3,
168
193
  onProgress,
169
194
  onPhoneNumber = () => defaultReadline("Phone number: "),
@@ -261,7 +286,14 @@ async function exportTelegramChannel(options) {
261
286
  }
262
287
  const msgId = message.id;
263
288
  const paddedId = String(msgId).padStart(6, "0");
289
+ const postPath = path.join(postsDir, `${paddedId}.md`);
264
290
  const postMediaDir = path.join(mediaDir, paddedId);
291
+ if (skipExisting && fs.existsSync(postPath)) {
292
+ if (onProgress) {
293
+ onProgress(processedCount, totalMessages, `Skipping ${paddedId} (exists)`);
294
+ }
295
+ continue;
296
+ }
265
297
  const mediaFiles = [];
266
298
  if (downloadMedia && message.media) {
267
299
  const existingFiles = fs.existsSync(postMediaDir) ? fs.readdirSync(postMediaDir).filter((f) => f.startsWith("media.")) : [];
@@ -297,7 +329,39 @@ async function exportTelegramChannel(options) {
297
329
  const mediaFileName = `media${ext}`;
298
330
  const mediaPath = path.join(postMediaDir, mediaFileName);
299
331
  fs.writeFileSync(mediaPath, buffer);
300
- mediaFiles.push(`media/${paddedId}/${mediaFileName}`);
332
+ const stats = fs.statSync(mediaPath);
333
+ const isVideo = [".mp4", ".mov", ".avi", ".webm", ".m4v"].includes(ext.toLowerCase());
334
+ if (stats.size > MAX_FILE_SIZE) {
335
+ if (isVideo && hasFFmpeg()) {
336
+ const compressedPath = path.join(postMediaDir, `media_compressed${ext}`);
337
+ if (compressVideo(mediaPath, compressedPath)) {
338
+ fs.unlinkSync(mediaPath);
339
+ fs.renameSync(compressedPath, mediaPath);
340
+ mediaFiles.push(`media/${paddedId}/${mediaFileName}`);
341
+ } else {
342
+ fs.unlinkSync(mediaPath);
343
+ if (fs.existsSync(compressedPath)) fs.unlinkSync(compressedPath);
344
+ console.warn(`Skipping large media for message ${msgId} (>${MAX_FILE_SIZE / 1024 / 1024}MB)`);
345
+ }
346
+ } else {
347
+ fs.unlinkSync(mediaPath);
348
+ console.warn(`Skipping large media for message ${msgId} (>${MAX_FILE_SIZE / 1024 / 1024}MB)`);
349
+ }
350
+ } else if (stats.size > COMPRESS_THRESHOLD && isVideo && hasFFmpeg()) {
351
+ const compressedPath = path.join(postMediaDir, `media_compressed${ext}`);
352
+ if (compressVideo(mediaPath, compressedPath)) {
353
+ const compressedStats = fs.statSync(compressedPath);
354
+ if (compressedStats.size < stats.size) {
355
+ fs.unlinkSync(mediaPath);
356
+ fs.renameSync(compressedPath, mediaPath);
357
+ } else {
358
+ fs.unlinkSync(compressedPath);
359
+ }
360
+ }
361
+ mediaFiles.push(`media/${paddedId}/${mediaFileName}`);
362
+ } else {
363
+ mediaFiles.push(`media/${paddedId}/${mediaFileName}`);
364
+ }
301
365
  }
302
366
  } catch (e) {
303
367
  console.error(`Error downloading media for message ${msgId}:`, e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koztv-blog-tools",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Shared utilities for Telegram-based blog sites",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",