koztv-blog-tools 1.0.5 → 1.1.0
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/index.d.mts +82 -8
- package/dist/index.d.ts +82 -8
- package/dist/index.js +244 -52
- package/dist/index.mjs +241 -52
- package/package.json +12 -4
package/dist/index.d.mts
CHANGED
|
@@ -115,22 +115,22 @@ declare function trackLearnMore(service: string): void;
|
|
|
115
115
|
declare function trackServiceClick(service: string): void;
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
|
-
* Translation utilities using
|
|
118
|
+
* Translation utilities using OpenAI-compatible APIs
|
|
119
119
|
*/
|
|
120
120
|
interface TranslateOptions {
|
|
121
|
-
/** API key
|
|
121
|
+
/** API key */
|
|
122
122
|
apiKey: string;
|
|
123
|
+
/** API base URL (e.g., https://api.openai.com/v1, https://open.bigmodel.cn/api/paas/v4) */
|
|
124
|
+
apiUrl: string;
|
|
125
|
+
/** Model name (e.g., gpt-4o-mini, GLM-4.7-Flash) */
|
|
126
|
+
model: string;
|
|
123
127
|
/** Target language (default: 'en') */
|
|
124
128
|
targetLang?: string;
|
|
125
129
|
/** Source language (default: 'ru') */
|
|
126
130
|
sourceLang?: string;
|
|
127
|
-
/** API provider (default: 'glm') */
|
|
128
|
-
provider?: 'glm' | 'openai';
|
|
129
|
-
/** Model to use (default depends on provider) */
|
|
130
|
-
model?: string;
|
|
131
131
|
}
|
|
132
132
|
/**
|
|
133
|
-
* Translate text
|
|
133
|
+
* Translate text using any OpenAI-compatible API
|
|
134
134
|
*/
|
|
135
135
|
declare function translateContent(text: string, options: TranslateOptions): Promise<string>;
|
|
136
136
|
/**
|
|
@@ -142,4 +142,78 @@ declare function translateTitle(title: string, options: TranslateOptions): Promi
|
|
|
142
142
|
*/
|
|
143
143
|
declare function generateEnglishSlug(title: string): string;
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Telegram channel export utilities using gramjs (MTProto)
|
|
147
|
+
*/
|
|
148
|
+
|
|
149
|
+
interface TelegramExportOptions {
|
|
150
|
+
/** Telegram API ID from https://my.telegram.org */
|
|
151
|
+
apiId: number;
|
|
152
|
+
/** Telegram API Hash from https://my.telegram.org */
|
|
153
|
+
apiHash: string;
|
|
154
|
+
/** Session string (for re-authentication). If empty, will prompt for login */
|
|
155
|
+
session?: string;
|
|
156
|
+
/** Target channel username, link or ID */
|
|
157
|
+
target: string;
|
|
158
|
+
/** Output directory for exported data */
|
|
159
|
+
outputDir: string;
|
|
160
|
+
/** Maximum number of posts to export (0 = all) */
|
|
161
|
+
limit?: number;
|
|
162
|
+
/** Only export posts since this date */
|
|
163
|
+
since?: Date;
|
|
164
|
+
/** Only export posts until this date */
|
|
165
|
+
until?: Date;
|
|
166
|
+
/** Download media files */
|
|
167
|
+
downloadMedia?: boolean;
|
|
168
|
+
/** Number of concurrent media downloads */
|
|
169
|
+
mediaWorkers?: number;
|
|
170
|
+
/** Callback for progress updates */
|
|
171
|
+
onProgress?: (current: number, total: number, message: string) => void;
|
|
172
|
+
/** Callback to get phone number for login */
|
|
173
|
+
onPhoneNumber?: () => Promise<string>;
|
|
174
|
+
/** Callback to get verification code */
|
|
175
|
+
onCode?: () => Promise<string>;
|
|
176
|
+
/** Callback to get 2FA password */
|
|
177
|
+
onPassword?: () => Promise<string>;
|
|
178
|
+
/** Callback when session string is generated (save this for future use) */
|
|
179
|
+
onSession?: (session: string) => void;
|
|
180
|
+
}
|
|
181
|
+
interface ExportedPost {
|
|
182
|
+
msgId: number;
|
|
183
|
+
date: Date;
|
|
184
|
+
content: string;
|
|
185
|
+
hasMedia: boolean;
|
|
186
|
+
mediaFiles: string[];
|
|
187
|
+
views?: number;
|
|
188
|
+
forwards?: number;
|
|
189
|
+
link: string;
|
|
190
|
+
channelUsername: string;
|
|
191
|
+
channelTitle: string;
|
|
192
|
+
}
|
|
193
|
+
interface ExportResult {
|
|
194
|
+
channelMeta: {
|
|
195
|
+
id: number;
|
|
196
|
+
username: string;
|
|
197
|
+
title: string;
|
|
198
|
+
description?: string;
|
|
199
|
+
participantsCount?: number;
|
|
200
|
+
};
|
|
201
|
+
posts: ExportedPost[];
|
|
202
|
+
session: string;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Export messages from a Telegram channel
|
|
206
|
+
*/
|
|
207
|
+
declare function exportTelegramChannel(options: TelegramExportOptions): Promise<ExportResult>;
|
|
208
|
+
/**
|
|
209
|
+
* Format a post as markdown with YAML frontmatter
|
|
210
|
+
*/
|
|
211
|
+
declare function formatPostMarkdown(post: ExportedPost): string;
|
|
212
|
+
/**
|
|
213
|
+
* Resume export from a saved session
|
|
214
|
+
*/
|
|
215
|
+
declare function resumeExport(options: Omit<TelegramExportOptions, 'onPhoneNumber' | 'onCode' | 'onPassword'> & {
|
|
216
|
+
session: string;
|
|
217
|
+
}): Promise<ExportResult>;
|
|
218
|
+
|
|
219
|
+
export { type AnalyticsConfig, type ExportResult, type ExportedPost, type GoalName, type GoalParams, type GroupedPost, type ParsePostOptions, type Post, type TelegramExportOptions, type TranslateOptions, categorizePost, cleanContent, configureAnalytics, deduplicatePosts, exportTelegramChannel, extractAttachments, extractExcerpt, extractTitle, formatPostMarkdown, generateEnglishSlug, generateSlug, groupPosts, parsePost, resumeExport, trackBookAppointment, trackGoal, trackLearnMore, trackServiceClick, trackTelegramClick, translateContent, translateTitle };
|
package/dist/index.d.ts
CHANGED
|
@@ -115,22 +115,22 @@ declare function trackLearnMore(service: string): void;
|
|
|
115
115
|
declare function trackServiceClick(service: string): void;
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
|
-
* Translation utilities using
|
|
118
|
+
* Translation utilities using OpenAI-compatible APIs
|
|
119
119
|
*/
|
|
120
120
|
interface TranslateOptions {
|
|
121
|
-
/** API key
|
|
121
|
+
/** API key */
|
|
122
122
|
apiKey: string;
|
|
123
|
+
/** API base URL (e.g., https://api.openai.com/v1, https://open.bigmodel.cn/api/paas/v4) */
|
|
124
|
+
apiUrl: string;
|
|
125
|
+
/** Model name (e.g., gpt-4o-mini, GLM-4.7-Flash) */
|
|
126
|
+
model: string;
|
|
123
127
|
/** Target language (default: 'en') */
|
|
124
128
|
targetLang?: string;
|
|
125
129
|
/** Source language (default: 'ru') */
|
|
126
130
|
sourceLang?: string;
|
|
127
|
-
/** API provider (default: 'glm') */
|
|
128
|
-
provider?: 'glm' | 'openai';
|
|
129
|
-
/** Model to use (default depends on provider) */
|
|
130
|
-
model?: string;
|
|
131
131
|
}
|
|
132
132
|
/**
|
|
133
|
-
* Translate text
|
|
133
|
+
* Translate text using any OpenAI-compatible API
|
|
134
134
|
*/
|
|
135
135
|
declare function translateContent(text: string, options: TranslateOptions): Promise<string>;
|
|
136
136
|
/**
|
|
@@ -142,4 +142,78 @@ declare function translateTitle(title: string, options: TranslateOptions): Promi
|
|
|
142
142
|
*/
|
|
143
143
|
declare function generateEnglishSlug(title: string): string;
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Telegram channel export utilities using gramjs (MTProto)
|
|
147
|
+
*/
|
|
148
|
+
|
|
149
|
+
interface TelegramExportOptions {
|
|
150
|
+
/** Telegram API ID from https://my.telegram.org */
|
|
151
|
+
apiId: number;
|
|
152
|
+
/** Telegram API Hash from https://my.telegram.org */
|
|
153
|
+
apiHash: string;
|
|
154
|
+
/** Session string (for re-authentication). If empty, will prompt for login */
|
|
155
|
+
session?: string;
|
|
156
|
+
/** Target channel username, link or ID */
|
|
157
|
+
target: string;
|
|
158
|
+
/** Output directory for exported data */
|
|
159
|
+
outputDir: string;
|
|
160
|
+
/** Maximum number of posts to export (0 = all) */
|
|
161
|
+
limit?: number;
|
|
162
|
+
/** Only export posts since this date */
|
|
163
|
+
since?: Date;
|
|
164
|
+
/** Only export posts until this date */
|
|
165
|
+
until?: Date;
|
|
166
|
+
/** Download media files */
|
|
167
|
+
downloadMedia?: boolean;
|
|
168
|
+
/** Number of concurrent media downloads */
|
|
169
|
+
mediaWorkers?: number;
|
|
170
|
+
/** Callback for progress updates */
|
|
171
|
+
onProgress?: (current: number, total: number, message: string) => void;
|
|
172
|
+
/** Callback to get phone number for login */
|
|
173
|
+
onPhoneNumber?: () => Promise<string>;
|
|
174
|
+
/** Callback to get verification code */
|
|
175
|
+
onCode?: () => Promise<string>;
|
|
176
|
+
/** Callback to get 2FA password */
|
|
177
|
+
onPassword?: () => Promise<string>;
|
|
178
|
+
/** Callback when session string is generated (save this for future use) */
|
|
179
|
+
onSession?: (session: string) => void;
|
|
180
|
+
}
|
|
181
|
+
interface ExportedPost {
|
|
182
|
+
msgId: number;
|
|
183
|
+
date: Date;
|
|
184
|
+
content: string;
|
|
185
|
+
hasMedia: boolean;
|
|
186
|
+
mediaFiles: string[];
|
|
187
|
+
views?: number;
|
|
188
|
+
forwards?: number;
|
|
189
|
+
link: string;
|
|
190
|
+
channelUsername: string;
|
|
191
|
+
channelTitle: string;
|
|
192
|
+
}
|
|
193
|
+
interface ExportResult {
|
|
194
|
+
channelMeta: {
|
|
195
|
+
id: number;
|
|
196
|
+
username: string;
|
|
197
|
+
title: string;
|
|
198
|
+
description?: string;
|
|
199
|
+
participantsCount?: number;
|
|
200
|
+
};
|
|
201
|
+
posts: ExportedPost[];
|
|
202
|
+
session: string;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Export messages from a Telegram channel
|
|
206
|
+
*/
|
|
207
|
+
declare function exportTelegramChannel(options: TelegramExportOptions): Promise<ExportResult>;
|
|
208
|
+
/**
|
|
209
|
+
* Format a post as markdown with YAML frontmatter
|
|
210
|
+
*/
|
|
211
|
+
declare function formatPostMarkdown(post: ExportedPost): string;
|
|
212
|
+
/**
|
|
213
|
+
* Resume export from a saved session
|
|
214
|
+
*/
|
|
215
|
+
declare function resumeExport(options: Omit<TelegramExportOptions, 'onPhoneNumber' | 'onCode' | 'onPassword'> & {
|
|
216
|
+
session: string;
|
|
217
|
+
}): Promise<ExportResult>;
|
|
218
|
+
|
|
219
|
+
export { type AnalyticsConfig, type ExportResult, type ExportedPost, type GoalName, type GoalParams, type GroupedPost, type ParsePostOptions, type Post, type TelegramExportOptions, type TranslateOptions, categorizePost, cleanContent, configureAnalytics, deduplicatePosts, exportTelegramChannel, extractAttachments, extractExcerpt, extractTitle, formatPostMarkdown, generateEnglishSlug, generateSlug, groupPosts, parsePost, resumeExport, trackBookAppointment, trackGoal, trackLearnMore, trackServiceClick, trackTelegramClick, translateContent, translateTitle };
|
package/dist/index.js
CHANGED
|
@@ -34,13 +34,16 @@ __export(index_exports, {
|
|
|
34
34
|
cleanContent: () => cleanContent,
|
|
35
35
|
configureAnalytics: () => configureAnalytics,
|
|
36
36
|
deduplicatePosts: () => deduplicatePosts,
|
|
37
|
+
exportTelegramChannel: () => exportTelegramChannel,
|
|
37
38
|
extractAttachments: () => extractAttachments,
|
|
38
39
|
extractExcerpt: () => extractExcerpt,
|
|
39
40
|
extractTitle: () => extractTitle,
|
|
41
|
+
formatPostMarkdown: () => formatPostMarkdown,
|
|
40
42
|
generateEnglishSlug: () => generateEnglishSlug,
|
|
41
43
|
generateSlug: () => generateSlug,
|
|
42
44
|
groupPosts: () => groupPosts,
|
|
43
45
|
parsePost: () => parsePost,
|
|
46
|
+
resumeExport: () => resumeExport,
|
|
44
47
|
trackBookAppointment: () => trackBookAppointment,
|
|
45
48
|
trackGoal: () => trackGoal,
|
|
46
49
|
trackLearnMore: () => trackLearnMore,
|
|
@@ -309,10 +312,8 @@ function trackServiceClick(service) {
|
|
|
309
312
|
}
|
|
310
313
|
|
|
311
314
|
// src/translate.ts
|
|
312
|
-
async function
|
|
313
|
-
const model =
|
|
314
|
-
const targetLang = options.targetLang || "en";
|
|
315
|
-
const sourceLang = options.sourceLang || "ru";
|
|
315
|
+
async function translateContent(text, options) {
|
|
316
|
+
const { apiKey, apiUrl, model, targetLang = "en", sourceLang = "ru" } = options;
|
|
316
317
|
const messages = [
|
|
317
318
|
{
|
|
318
319
|
role: "system",
|
|
@@ -326,11 +327,12 @@ Do not add any explanations or notes.`
|
|
|
326
327
|
content: text
|
|
327
328
|
}
|
|
328
329
|
];
|
|
329
|
-
const
|
|
330
|
+
const endpoint = apiUrl.endsWith("/") ? `${apiUrl}chat/completions` : `${apiUrl}/chat/completions`;
|
|
331
|
+
const response = await fetch(endpoint, {
|
|
330
332
|
method: "POST",
|
|
331
333
|
headers: {
|
|
332
334
|
"Content-Type": "application/json",
|
|
333
|
-
"Authorization": `Bearer ${
|
|
335
|
+
"Authorization": `Bearer ${apiKey}`
|
|
334
336
|
},
|
|
335
337
|
body: JSON.stringify({
|
|
336
338
|
model,
|
|
@@ -340,56 +342,11 @@ Do not add any explanations or notes.`
|
|
|
340
342
|
});
|
|
341
343
|
if (!response.ok) {
|
|
342
344
|
const error = await response.text();
|
|
343
|
-
throw new Error(`
|
|
345
|
+
throw new Error(`API error: ${response.status} - ${error}`);
|
|
344
346
|
}
|
|
345
347
|
const data = await response.json();
|
|
346
348
|
return data.choices[0]?.message?.content || "";
|
|
347
349
|
}
|
|
348
|
-
async function translateWithOpenAI(text, options) {
|
|
349
|
-
const model = options.model || "gpt-4o-mini";
|
|
350
|
-
const targetLang = options.targetLang || "en";
|
|
351
|
-
const sourceLang = options.sourceLang || "ru";
|
|
352
|
-
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
353
|
-
method: "POST",
|
|
354
|
-
headers: {
|
|
355
|
-
"Content-Type": "application/json",
|
|
356
|
-
"Authorization": `Bearer ${options.apiKey}`
|
|
357
|
-
},
|
|
358
|
-
body: JSON.stringify({
|
|
359
|
-
model,
|
|
360
|
-
messages: [
|
|
361
|
-
{
|
|
362
|
-
role: "system",
|
|
363
|
-
content: `You are a professional translator. Translate the following text from ${sourceLang} to ${targetLang}.
|
|
364
|
-
Keep the markdown formatting intact.
|
|
365
|
-
Only output the translated text, nothing else.`
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
role: "user",
|
|
369
|
-
content: text
|
|
370
|
-
}
|
|
371
|
-
],
|
|
372
|
-
temperature: 0.3
|
|
373
|
-
})
|
|
374
|
-
});
|
|
375
|
-
if (!response.ok) {
|
|
376
|
-
const error = await response.text();
|
|
377
|
-
throw new Error(`OpenAI API error: ${response.status} - ${error}`);
|
|
378
|
-
}
|
|
379
|
-
const data = await response.json();
|
|
380
|
-
return data.choices[0]?.message?.content || "";
|
|
381
|
-
}
|
|
382
|
-
async function translateContent(text, options) {
|
|
383
|
-
const provider = options.provider || "glm";
|
|
384
|
-
switch (provider) {
|
|
385
|
-
case "glm":
|
|
386
|
-
return translateWithGLM(text, options);
|
|
387
|
-
case "openai":
|
|
388
|
-
return translateWithOpenAI(text, options);
|
|
389
|
-
default:
|
|
390
|
-
throw new Error(`Unknown provider: ${provider}`);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
350
|
async function translateTitle(title, options) {
|
|
394
351
|
const translated = await translateContent(title, options);
|
|
395
352
|
return translated.replace(/^["']|["']$/g, "").trim();
|
|
@@ -397,19 +354,254 @@ async function translateTitle(title, options) {
|
|
|
397
354
|
function generateEnglishSlug(title) {
|
|
398
355
|
return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").substring(0, 60);
|
|
399
356
|
}
|
|
357
|
+
|
|
358
|
+
// src/telegram.ts
|
|
359
|
+
var import_telegram = require("telegram");
|
|
360
|
+
var import_sessions = require("telegram/sessions");
|
|
361
|
+
var fs = __toESM(require("fs"));
|
|
362
|
+
var path = __toESM(require("path"));
|
|
363
|
+
var readline = __toESM(require("readline"));
|
|
364
|
+
async function defaultReadline(prompt) {
|
|
365
|
+
const rl = readline.createInterface({
|
|
366
|
+
input: process.stdin,
|
|
367
|
+
output: process.stdout
|
|
368
|
+
});
|
|
369
|
+
return new Promise((resolve) => {
|
|
370
|
+
rl.question(prompt, (answer) => {
|
|
371
|
+
rl.close();
|
|
372
|
+
resolve(answer);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
async function exportTelegramChannel(options) {
|
|
377
|
+
const {
|
|
378
|
+
apiId,
|
|
379
|
+
apiHash,
|
|
380
|
+
session = "",
|
|
381
|
+
target,
|
|
382
|
+
outputDir,
|
|
383
|
+
limit = 0,
|
|
384
|
+
since,
|
|
385
|
+
until,
|
|
386
|
+
downloadMedia = true,
|
|
387
|
+
mediaWorkers = 3,
|
|
388
|
+
onProgress,
|
|
389
|
+
onPhoneNumber = () => defaultReadline("Phone number: "),
|
|
390
|
+
onCode = () => defaultReadline("Verification code: "),
|
|
391
|
+
onPassword = () => defaultReadline("2FA Password: "),
|
|
392
|
+
onSession
|
|
393
|
+
} = options;
|
|
394
|
+
const postsDir = path.join(outputDir, "posts");
|
|
395
|
+
const mediaDir = path.join(outputDir, "media");
|
|
396
|
+
fs.mkdirSync(postsDir, { recursive: true });
|
|
397
|
+
fs.mkdirSync(mediaDir, { recursive: true });
|
|
398
|
+
const stringSession = new import_sessions.StringSession(session);
|
|
399
|
+
const client = new import_telegram.TelegramClient(stringSession, apiId, apiHash, {
|
|
400
|
+
connectionRetries: 5
|
|
401
|
+
});
|
|
402
|
+
await client.start({
|
|
403
|
+
phoneNumber: onPhoneNumber,
|
|
404
|
+
phoneCode: onCode,
|
|
405
|
+
password: onPassword,
|
|
406
|
+
onError: (err) => console.error("Auth error:", err)
|
|
407
|
+
});
|
|
408
|
+
const newSession = client.session.save();
|
|
409
|
+
if (onSession) {
|
|
410
|
+
onSession(newSession);
|
|
411
|
+
}
|
|
412
|
+
const entity = await client.getEntity(target);
|
|
413
|
+
if (!(entity instanceof import_telegram.Api.Channel)) {
|
|
414
|
+
throw new Error(`Target "${target}" is not a channel`);
|
|
415
|
+
}
|
|
416
|
+
const channelMeta = {
|
|
417
|
+
id: entity.id.toJSNumber(),
|
|
418
|
+
username: entity.username || "",
|
|
419
|
+
title: entity.title,
|
|
420
|
+
description: void 0,
|
|
421
|
+
participantsCount: void 0
|
|
422
|
+
};
|
|
423
|
+
try {
|
|
424
|
+
const fullChannel = await client.invoke(
|
|
425
|
+
new import_telegram.Api.channels.GetFullChannel({ channel: entity })
|
|
426
|
+
);
|
|
427
|
+
if (fullChannel.fullChat instanceof import_telegram.Api.ChannelFull) {
|
|
428
|
+
channelMeta.description = fullChannel.fullChat.about;
|
|
429
|
+
channelMeta.participantsCount = fullChannel.fullChat.participantsCount;
|
|
430
|
+
}
|
|
431
|
+
} catch (e) {
|
|
432
|
+
}
|
|
433
|
+
fs.writeFileSync(
|
|
434
|
+
path.join(outputDir, "channel_meta.json"),
|
|
435
|
+
JSON.stringify(channelMeta, null, 2)
|
|
436
|
+
);
|
|
437
|
+
const posts = [];
|
|
438
|
+
let processedCount = 0;
|
|
439
|
+
const iterParams = {
|
|
440
|
+
entity,
|
|
441
|
+
reverse: true
|
|
442
|
+
// Oldest first
|
|
443
|
+
};
|
|
444
|
+
if (limit > 0) {
|
|
445
|
+
iterParams.limit = limit;
|
|
446
|
+
}
|
|
447
|
+
if (since) {
|
|
448
|
+
iterParams.offsetDate = Math.floor(since.getTime() / 1e3);
|
|
449
|
+
}
|
|
450
|
+
let totalMessages = limit || 0;
|
|
451
|
+
if (!limit) {
|
|
452
|
+
try {
|
|
453
|
+
const history = await client.invoke(
|
|
454
|
+
new import_telegram.Api.messages.GetHistory({
|
|
455
|
+
peer: entity,
|
|
456
|
+
limit: 1,
|
|
457
|
+
offsetId: 0,
|
|
458
|
+
offsetDate: 0,
|
|
459
|
+
addOffset: 0,
|
|
460
|
+
maxId: 0,
|
|
461
|
+
minId: 0,
|
|
462
|
+
hash: 0n
|
|
463
|
+
})
|
|
464
|
+
);
|
|
465
|
+
if ("count" in history) {
|
|
466
|
+
totalMessages = history.count;
|
|
467
|
+
}
|
|
468
|
+
} catch (e) {
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
for await (const message of client.iterMessages(entity, iterParams)) {
|
|
472
|
+
if (until && message.date && message.date * 1e3 > until.getTime()) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (since && message.date && message.date * 1e3 < since.getTime()) {
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
processedCount++;
|
|
479
|
+
if (onProgress) {
|
|
480
|
+
onProgress(processedCount, totalMessages, `Processing message ${message.id}`);
|
|
481
|
+
}
|
|
482
|
+
const msgId = message.id;
|
|
483
|
+
const paddedId = String(msgId).padStart(6, "0");
|
|
484
|
+
const postMediaDir = path.join(mediaDir, paddedId);
|
|
485
|
+
const mediaFiles = [];
|
|
486
|
+
if (downloadMedia && message.media) {
|
|
487
|
+
fs.mkdirSync(postMediaDir, { recursive: true });
|
|
488
|
+
try {
|
|
489
|
+
const buffer = await client.downloadMedia(message.media, {});
|
|
490
|
+
if (buffer) {
|
|
491
|
+
let ext = ".bin";
|
|
492
|
+
if (message.media instanceof import_telegram.Api.MessageMediaPhoto) {
|
|
493
|
+
ext = ".jpg";
|
|
494
|
+
} else if (message.media instanceof import_telegram.Api.MessageMediaDocument) {
|
|
495
|
+
const doc = message.media.document;
|
|
496
|
+
if (doc instanceof import_telegram.Api.Document) {
|
|
497
|
+
const mimeExt = doc.mimeType?.split("/")[1];
|
|
498
|
+
if (mimeExt) {
|
|
499
|
+
ext = "." + mimeExt.replace("jpeg", "jpg");
|
|
500
|
+
}
|
|
501
|
+
for (const attr of doc.attributes) {
|
|
502
|
+
if (attr instanceof import_telegram.Api.DocumentAttributeVideo) {
|
|
503
|
+
ext = ".mp4";
|
|
504
|
+
}
|
|
505
|
+
if (attr instanceof import_telegram.Api.DocumentAttributeFilename) {
|
|
506
|
+
ext = path.extname(attr.fileName) || ext;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
const mediaFileName = `media${ext}`;
|
|
512
|
+
const mediaPath = path.join(postMediaDir, mediaFileName);
|
|
513
|
+
fs.writeFileSync(mediaPath, buffer);
|
|
514
|
+
mediaFiles.push(`media/${paddedId}/${mediaFileName}`);
|
|
515
|
+
}
|
|
516
|
+
} catch (e) {
|
|
517
|
+
console.error(`Error downloading media for message ${msgId}:`, e);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
const content = message.message || "";
|
|
521
|
+
const link = channelMeta.username ? `https://t.me/${channelMeta.username}/${msgId}` : "";
|
|
522
|
+
const post = {
|
|
523
|
+
msgId,
|
|
524
|
+
date: new Date(message.date * 1e3),
|
|
525
|
+
content,
|
|
526
|
+
hasMedia: mediaFiles.length > 0 || !!message.media,
|
|
527
|
+
mediaFiles,
|
|
528
|
+
views: message.views,
|
|
529
|
+
forwards: message.forwards,
|
|
530
|
+
link,
|
|
531
|
+
channelUsername: channelMeta.username,
|
|
532
|
+
channelTitle: channelMeta.title
|
|
533
|
+
};
|
|
534
|
+
posts.push(post);
|
|
535
|
+
const markdown = formatPostMarkdown(post);
|
|
536
|
+
fs.writeFileSync(path.join(postsDir, `${paddedId}.md`), markdown);
|
|
537
|
+
}
|
|
538
|
+
const ndjsonPath = path.join(outputDir, "posts.ndjson");
|
|
539
|
+
const ndjsonContent = posts.map((p) => JSON.stringify(p)).join("\n");
|
|
540
|
+
fs.writeFileSync(ndjsonPath, ndjsonContent);
|
|
541
|
+
await client.disconnect();
|
|
542
|
+
return {
|
|
543
|
+
channelMeta,
|
|
544
|
+
posts,
|
|
545
|
+
session: newSession
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function formatPostMarkdown(post) {
|
|
549
|
+
const dateStr = post.date.toISOString();
|
|
550
|
+
const dateOnly = dateStr.split("T")[0];
|
|
551
|
+
let frontmatter = `---
|
|
552
|
+
msg_id: ${post.msgId}
|
|
553
|
+
date: ${dateStr}
|
|
554
|
+
channel_username: "${post.channelUsername}"
|
|
555
|
+
channel_title: "${post.channelTitle.replace(/"/g, '\\"')}"
|
|
556
|
+
link: "${post.link}"
|
|
557
|
+
has_media: ${post.hasMedia}`;
|
|
558
|
+
if (post.views !== void 0) {
|
|
559
|
+
frontmatter += `
|
|
560
|
+
views: ${post.views}`;
|
|
561
|
+
}
|
|
562
|
+
if (post.forwards !== void 0) {
|
|
563
|
+
frontmatter += `
|
|
564
|
+
forwards: ${post.forwards}`;
|
|
565
|
+
}
|
|
566
|
+
frontmatter += "\n---\n\n";
|
|
567
|
+
let body = post.content || "";
|
|
568
|
+
if (post.mediaFiles.length > 0) {
|
|
569
|
+
body += "\n\n## Attachments\n\n";
|
|
570
|
+
for (const file of post.mediaFiles) {
|
|
571
|
+
const ext = path.extname(file).toLowerCase();
|
|
572
|
+
if ([".jpg", ".jpeg", ".png", ".gif", ".webp"].includes(ext)) {
|
|
573
|
+
body += `
|
|
574
|
+
`;
|
|
575
|
+
} else {
|
|
576
|
+
body += `- ${file}
|
|
577
|
+
`;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return frontmatter + body;
|
|
582
|
+
}
|
|
583
|
+
async function resumeExport(options) {
|
|
584
|
+
if (!options.session) {
|
|
585
|
+
throw new Error("Session string is required for resumeExport");
|
|
586
|
+
}
|
|
587
|
+
return exportTelegramChannel(options);
|
|
588
|
+
}
|
|
400
589
|
// Annotate the CommonJS export names for ESM import in node:
|
|
401
590
|
0 && (module.exports = {
|
|
402
591
|
categorizePost,
|
|
403
592
|
cleanContent,
|
|
404
593
|
configureAnalytics,
|
|
405
594
|
deduplicatePosts,
|
|
595
|
+
exportTelegramChannel,
|
|
406
596
|
extractAttachments,
|
|
407
597
|
extractExcerpt,
|
|
408
598
|
extractTitle,
|
|
599
|
+
formatPostMarkdown,
|
|
409
600
|
generateEnglishSlug,
|
|
410
601
|
generateSlug,
|
|
411
602
|
groupPosts,
|
|
412
603
|
parsePost,
|
|
604
|
+
resumeExport,
|
|
413
605
|
trackBookAppointment,
|
|
414
606
|
trackGoal,
|
|
415
607
|
trackLearnMore,
|
package/dist/index.mjs
CHANGED
|
@@ -256,10 +256,8 @@ function trackServiceClick(service) {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
// src/translate.ts
|
|
259
|
-
async function
|
|
260
|
-
const model =
|
|
261
|
-
const targetLang = options.targetLang || "en";
|
|
262
|
-
const sourceLang = options.sourceLang || "ru";
|
|
259
|
+
async function translateContent(text, options) {
|
|
260
|
+
const { apiKey, apiUrl, model, targetLang = "en", sourceLang = "ru" } = options;
|
|
263
261
|
const messages = [
|
|
264
262
|
{
|
|
265
263
|
role: "system",
|
|
@@ -273,11 +271,12 @@ Do not add any explanations or notes.`
|
|
|
273
271
|
content: text
|
|
274
272
|
}
|
|
275
273
|
];
|
|
276
|
-
const
|
|
274
|
+
const endpoint = apiUrl.endsWith("/") ? `${apiUrl}chat/completions` : `${apiUrl}/chat/completions`;
|
|
275
|
+
const response = await fetch(endpoint, {
|
|
277
276
|
method: "POST",
|
|
278
277
|
headers: {
|
|
279
278
|
"Content-Type": "application/json",
|
|
280
|
-
"Authorization": `Bearer ${
|
|
279
|
+
"Authorization": `Bearer ${apiKey}`
|
|
281
280
|
},
|
|
282
281
|
body: JSON.stringify({
|
|
283
282
|
model,
|
|
@@ -287,56 +286,11 @@ Do not add any explanations or notes.`
|
|
|
287
286
|
});
|
|
288
287
|
if (!response.ok) {
|
|
289
288
|
const error = await response.text();
|
|
290
|
-
throw new Error(`
|
|
289
|
+
throw new Error(`API error: ${response.status} - ${error}`);
|
|
291
290
|
}
|
|
292
291
|
const data = await response.json();
|
|
293
292
|
return data.choices[0]?.message?.content || "";
|
|
294
293
|
}
|
|
295
|
-
async function translateWithOpenAI(text, options) {
|
|
296
|
-
const model = options.model || "gpt-4o-mini";
|
|
297
|
-
const targetLang = options.targetLang || "en";
|
|
298
|
-
const sourceLang = options.sourceLang || "ru";
|
|
299
|
-
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
300
|
-
method: "POST",
|
|
301
|
-
headers: {
|
|
302
|
-
"Content-Type": "application/json",
|
|
303
|
-
"Authorization": `Bearer ${options.apiKey}`
|
|
304
|
-
},
|
|
305
|
-
body: JSON.stringify({
|
|
306
|
-
model,
|
|
307
|
-
messages: [
|
|
308
|
-
{
|
|
309
|
-
role: "system",
|
|
310
|
-
content: `You are a professional translator. Translate the following text from ${sourceLang} to ${targetLang}.
|
|
311
|
-
Keep the markdown formatting intact.
|
|
312
|
-
Only output the translated text, nothing else.`
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
role: "user",
|
|
316
|
-
content: text
|
|
317
|
-
}
|
|
318
|
-
],
|
|
319
|
-
temperature: 0.3
|
|
320
|
-
})
|
|
321
|
-
});
|
|
322
|
-
if (!response.ok) {
|
|
323
|
-
const error = await response.text();
|
|
324
|
-
throw new Error(`OpenAI API error: ${response.status} - ${error}`);
|
|
325
|
-
}
|
|
326
|
-
const data = await response.json();
|
|
327
|
-
return data.choices[0]?.message?.content || "";
|
|
328
|
-
}
|
|
329
|
-
async function translateContent(text, options) {
|
|
330
|
-
const provider = options.provider || "glm";
|
|
331
|
-
switch (provider) {
|
|
332
|
-
case "glm":
|
|
333
|
-
return translateWithGLM(text, options);
|
|
334
|
-
case "openai":
|
|
335
|
-
return translateWithOpenAI(text, options);
|
|
336
|
-
default:
|
|
337
|
-
throw new Error(`Unknown provider: ${provider}`);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
294
|
async function translateTitle(title, options) {
|
|
341
295
|
const translated = await translateContent(title, options);
|
|
342
296
|
return translated.replace(/^["']|["']$/g, "").trim();
|
|
@@ -344,18 +298,253 @@ async function translateTitle(title, options) {
|
|
|
344
298
|
function generateEnglishSlug(title) {
|
|
345
299
|
return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").substring(0, 60);
|
|
346
300
|
}
|
|
301
|
+
|
|
302
|
+
// src/telegram.ts
|
|
303
|
+
import { TelegramClient, Api } from "telegram";
|
|
304
|
+
import { StringSession } from "telegram/sessions";
|
|
305
|
+
import * as fs from "fs";
|
|
306
|
+
import * as path from "path";
|
|
307
|
+
import * as readline from "readline";
|
|
308
|
+
async function defaultReadline(prompt) {
|
|
309
|
+
const rl = readline.createInterface({
|
|
310
|
+
input: process.stdin,
|
|
311
|
+
output: process.stdout
|
|
312
|
+
});
|
|
313
|
+
return new Promise((resolve) => {
|
|
314
|
+
rl.question(prompt, (answer) => {
|
|
315
|
+
rl.close();
|
|
316
|
+
resolve(answer);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
async function exportTelegramChannel(options) {
|
|
321
|
+
const {
|
|
322
|
+
apiId,
|
|
323
|
+
apiHash,
|
|
324
|
+
session = "",
|
|
325
|
+
target,
|
|
326
|
+
outputDir,
|
|
327
|
+
limit = 0,
|
|
328
|
+
since,
|
|
329
|
+
until,
|
|
330
|
+
downloadMedia = true,
|
|
331
|
+
mediaWorkers = 3,
|
|
332
|
+
onProgress,
|
|
333
|
+
onPhoneNumber = () => defaultReadline("Phone number: "),
|
|
334
|
+
onCode = () => defaultReadline("Verification code: "),
|
|
335
|
+
onPassword = () => defaultReadline("2FA Password: "),
|
|
336
|
+
onSession
|
|
337
|
+
} = options;
|
|
338
|
+
const postsDir = path.join(outputDir, "posts");
|
|
339
|
+
const mediaDir = path.join(outputDir, "media");
|
|
340
|
+
fs.mkdirSync(postsDir, { recursive: true });
|
|
341
|
+
fs.mkdirSync(mediaDir, { recursive: true });
|
|
342
|
+
const stringSession = new StringSession(session);
|
|
343
|
+
const client = new TelegramClient(stringSession, apiId, apiHash, {
|
|
344
|
+
connectionRetries: 5
|
|
345
|
+
});
|
|
346
|
+
await client.start({
|
|
347
|
+
phoneNumber: onPhoneNumber,
|
|
348
|
+
phoneCode: onCode,
|
|
349
|
+
password: onPassword,
|
|
350
|
+
onError: (err) => console.error("Auth error:", err)
|
|
351
|
+
});
|
|
352
|
+
const newSession = client.session.save();
|
|
353
|
+
if (onSession) {
|
|
354
|
+
onSession(newSession);
|
|
355
|
+
}
|
|
356
|
+
const entity = await client.getEntity(target);
|
|
357
|
+
if (!(entity instanceof Api.Channel)) {
|
|
358
|
+
throw new Error(`Target "${target}" is not a channel`);
|
|
359
|
+
}
|
|
360
|
+
const channelMeta = {
|
|
361
|
+
id: entity.id.toJSNumber(),
|
|
362
|
+
username: entity.username || "",
|
|
363
|
+
title: entity.title,
|
|
364
|
+
description: void 0,
|
|
365
|
+
participantsCount: void 0
|
|
366
|
+
};
|
|
367
|
+
try {
|
|
368
|
+
const fullChannel = await client.invoke(
|
|
369
|
+
new Api.channels.GetFullChannel({ channel: entity })
|
|
370
|
+
);
|
|
371
|
+
if (fullChannel.fullChat instanceof Api.ChannelFull) {
|
|
372
|
+
channelMeta.description = fullChannel.fullChat.about;
|
|
373
|
+
channelMeta.participantsCount = fullChannel.fullChat.participantsCount;
|
|
374
|
+
}
|
|
375
|
+
} catch (e) {
|
|
376
|
+
}
|
|
377
|
+
fs.writeFileSync(
|
|
378
|
+
path.join(outputDir, "channel_meta.json"),
|
|
379
|
+
JSON.stringify(channelMeta, null, 2)
|
|
380
|
+
);
|
|
381
|
+
const posts = [];
|
|
382
|
+
let processedCount = 0;
|
|
383
|
+
const iterParams = {
|
|
384
|
+
entity,
|
|
385
|
+
reverse: true
|
|
386
|
+
// Oldest first
|
|
387
|
+
};
|
|
388
|
+
if (limit > 0) {
|
|
389
|
+
iterParams.limit = limit;
|
|
390
|
+
}
|
|
391
|
+
if (since) {
|
|
392
|
+
iterParams.offsetDate = Math.floor(since.getTime() / 1e3);
|
|
393
|
+
}
|
|
394
|
+
let totalMessages = limit || 0;
|
|
395
|
+
if (!limit) {
|
|
396
|
+
try {
|
|
397
|
+
const history = await client.invoke(
|
|
398
|
+
new Api.messages.GetHistory({
|
|
399
|
+
peer: entity,
|
|
400
|
+
limit: 1,
|
|
401
|
+
offsetId: 0,
|
|
402
|
+
offsetDate: 0,
|
|
403
|
+
addOffset: 0,
|
|
404
|
+
maxId: 0,
|
|
405
|
+
minId: 0,
|
|
406
|
+
hash: 0n
|
|
407
|
+
})
|
|
408
|
+
);
|
|
409
|
+
if ("count" in history) {
|
|
410
|
+
totalMessages = history.count;
|
|
411
|
+
}
|
|
412
|
+
} catch (e) {
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
for await (const message of client.iterMessages(entity, iterParams)) {
|
|
416
|
+
if (until && message.date && message.date * 1e3 > until.getTime()) {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
if (since && message.date && message.date * 1e3 < since.getTime()) {
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
processedCount++;
|
|
423
|
+
if (onProgress) {
|
|
424
|
+
onProgress(processedCount, totalMessages, `Processing message ${message.id}`);
|
|
425
|
+
}
|
|
426
|
+
const msgId = message.id;
|
|
427
|
+
const paddedId = String(msgId).padStart(6, "0");
|
|
428
|
+
const postMediaDir = path.join(mediaDir, paddedId);
|
|
429
|
+
const mediaFiles = [];
|
|
430
|
+
if (downloadMedia && message.media) {
|
|
431
|
+
fs.mkdirSync(postMediaDir, { recursive: true });
|
|
432
|
+
try {
|
|
433
|
+
const buffer = await client.downloadMedia(message.media, {});
|
|
434
|
+
if (buffer) {
|
|
435
|
+
let ext = ".bin";
|
|
436
|
+
if (message.media instanceof Api.MessageMediaPhoto) {
|
|
437
|
+
ext = ".jpg";
|
|
438
|
+
} else if (message.media instanceof Api.MessageMediaDocument) {
|
|
439
|
+
const doc = message.media.document;
|
|
440
|
+
if (doc instanceof Api.Document) {
|
|
441
|
+
const mimeExt = doc.mimeType?.split("/")[1];
|
|
442
|
+
if (mimeExt) {
|
|
443
|
+
ext = "." + mimeExt.replace("jpeg", "jpg");
|
|
444
|
+
}
|
|
445
|
+
for (const attr of doc.attributes) {
|
|
446
|
+
if (attr instanceof Api.DocumentAttributeVideo) {
|
|
447
|
+
ext = ".mp4";
|
|
448
|
+
}
|
|
449
|
+
if (attr instanceof Api.DocumentAttributeFilename) {
|
|
450
|
+
ext = path.extname(attr.fileName) || ext;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const mediaFileName = `media${ext}`;
|
|
456
|
+
const mediaPath = path.join(postMediaDir, mediaFileName);
|
|
457
|
+
fs.writeFileSync(mediaPath, buffer);
|
|
458
|
+
mediaFiles.push(`media/${paddedId}/${mediaFileName}`);
|
|
459
|
+
}
|
|
460
|
+
} catch (e) {
|
|
461
|
+
console.error(`Error downloading media for message ${msgId}:`, e);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const content = message.message || "";
|
|
465
|
+
const link = channelMeta.username ? `https://t.me/${channelMeta.username}/${msgId}` : "";
|
|
466
|
+
const post = {
|
|
467
|
+
msgId,
|
|
468
|
+
date: new Date(message.date * 1e3),
|
|
469
|
+
content,
|
|
470
|
+
hasMedia: mediaFiles.length > 0 || !!message.media,
|
|
471
|
+
mediaFiles,
|
|
472
|
+
views: message.views,
|
|
473
|
+
forwards: message.forwards,
|
|
474
|
+
link,
|
|
475
|
+
channelUsername: channelMeta.username,
|
|
476
|
+
channelTitle: channelMeta.title
|
|
477
|
+
};
|
|
478
|
+
posts.push(post);
|
|
479
|
+
const markdown = formatPostMarkdown(post);
|
|
480
|
+
fs.writeFileSync(path.join(postsDir, `${paddedId}.md`), markdown);
|
|
481
|
+
}
|
|
482
|
+
const ndjsonPath = path.join(outputDir, "posts.ndjson");
|
|
483
|
+
const ndjsonContent = posts.map((p) => JSON.stringify(p)).join("\n");
|
|
484
|
+
fs.writeFileSync(ndjsonPath, ndjsonContent);
|
|
485
|
+
await client.disconnect();
|
|
486
|
+
return {
|
|
487
|
+
channelMeta,
|
|
488
|
+
posts,
|
|
489
|
+
session: newSession
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function formatPostMarkdown(post) {
|
|
493
|
+
const dateStr = post.date.toISOString();
|
|
494
|
+
const dateOnly = dateStr.split("T")[0];
|
|
495
|
+
let frontmatter = `---
|
|
496
|
+
msg_id: ${post.msgId}
|
|
497
|
+
date: ${dateStr}
|
|
498
|
+
channel_username: "${post.channelUsername}"
|
|
499
|
+
channel_title: "${post.channelTitle.replace(/"/g, '\\"')}"
|
|
500
|
+
link: "${post.link}"
|
|
501
|
+
has_media: ${post.hasMedia}`;
|
|
502
|
+
if (post.views !== void 0) {
|
|
503
|
+
frontmatter += `
|
|
504
|
+
views: ${post.views}`;
|
|
505
|
+
}
|
|
506
|
+
if (post.forwards !== void 0) {
|
|
507
|
+
frontmatter += `
|
|
508
|
+
forwards: ${post.forwards}`;
|
|
509
|
+
}
|
|
510
|
+
frontmatter += "\n---\n\n";
|
|
511
|
+
let body = post.content || "";
|
|
512
|
+
if (post.mediaFiles.length > 0) {
|
|
513
|
+
body += "\n\n## Attachments\n\n";
|
|
514
|
+
for (const file of post.mediaFiles) {
|
|
515
|
+
const ext = path.extname(file).toLowerCase();
|
|
516
|
+
if ([".jpg", ".jpeg", ".png", ".gif", ".webp"].includes(ext)) {
|
|
517
|
+
body += `
|
|
518
|
+
`;
|
|
519
|
+
} else {
|
|
520
|
+
body += `- ${file}
|
|
521
|
+
`;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return frontmatter + body;
|
|
526
|
+
}
|
|
527
|
+
async function resumeExport(options) {
|
|
528
|
+
if (!options.session) {
|
|
529
|
+
throw new Error("Session string is required for resumeExport");
|
|
530
|
+
}
|
|
531
|
+
return exportTelegramChannel(options);
|
|
532
|
+
}
|
|
347
533
|
export {
|
|
348
534
|
categorizePost,
|
|
349
535
|
cleanContent,
|
|
350
536
|
configureAnalytics,
|
|
351
537
|
deduplicatePosts,
|
|
538
|
+
exportTelegramChannel,
|
|
352
539
|
extractAttachments,
|
|
353
540
|
extractExcerpt,
|
|
354
541
|
extractTitle,
|
|
542
|
+
formatPostMarkdown,
|
|
355
543
|
generateEnglishSlug,
|
|
356
544
|
generateSlug,
|
|
357
545
|
groupPosts,
|
|
358
546
|
parsePost,
|
|
547
|
+
resumeExport,
|
|
359
548
|
trackBookAppointment,
|
|
360
549
|
trackGoal,
|
|
361
550
|
trackLearnMore,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koztv-blog-tools",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Shared utilities for Telegram-based blog sites",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -23,23 +23,31 @@
|
|
|
23
23
|
"blog",
|
|
24
24
|
"telegram",
|
|
25
25
|
"markdown",
|
|
26
|
-
"static-site"
|
|
26
|
+
"static-site",
|
|
27
|
+
"telegram-export",
|
|
28
|
+
"mtproto"
|
|
27
29
|
],
|
|
28
30
|
"author": "Koz TV",
|
|
29
31
|
"license": "MIT",
|
|
30
32
|
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.0.0",
|
|
31
34
|
"tsup": "^8.0.0",
|
|
32
35
|
"typescript": "^5.0.0"
|
|
33
36
|
},
|
|
34
37
|
"dependencies": {
|
|
35
|
-
"gray-matter": "^4.0.3"
|
|
38
|
+
"gray-matter": "^4.0.3",
|
|
39
|
+
"telegram": "^2.26.22"
|
|
36
40
|
},
|
|
37
41
|
"peerDependencies": {
|
|
38
|
-
"gray-matter": "^4.0.0"
|
|
42
|
+
"gray-matter": "^4.0.0",
|
|
43
|
+
"telegram": "^2.0.0"
|
|
39
44
|
},
|
|
40
45
|
"peerDependenciesMeta": {
|
|
41
46
|
"gray-matter": {
|
|
42
47
|
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"telegram": {
|
|
50
|
+
"optional": true
|
|
43
51
|
}
|
|
44
52
|
}
|
|
45
53
|
}
|