koztv-blog-tools 1.2.19 → 1.3.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/chunk-OENFHIEQ.mjs +333 -0
- package/dist/index.d.mts +1 -187
- package/dist/index.d.ts +1 -187
- package/dist/index.js +0 -882
- package/dist/index.mjs +20 -1189
- package/dist/node.d.mts +189 -0
- package/dist/node.d.ts +189 -0
- package/dist/node.js +1268 -0
- package/dist/node.mjs +917 -0
- package/package.json +7 -2
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// src/posts.ts
|
|
2
|
+
import matter from "gray-matter";
|
|
3
|
+
var translitMap = {
|
|
4
|
+
"\u0430": "a",
|
|
5
|
+
"\u0431": "b",
|
|
6
|
+
"\u0432": "v",
|
|
7
|
+
"\u0433": "g",
|
|
8
|
+
"\u0434": "d",
|
|
9
|
+
"\u0435": "e",
|
|
10
|
+
"\u0451": "yo",
|
|
11
|
+
"\u0436": "zh",
|
|
12
|
+
"\u0437": "z",
|
|
13
|
+
"\u0438": "i",
|
|
14
|
+
"\u0439": "y",
|
|
15
|
+
"\u043A": "k",
|
|
16
|
+
"\u043B": "l",
|
|
17
|
+
"\u043C": "m",
|
|
18
|
+
"\u043D": "n",
|
|
19
|
+
"\u043E": "o",
|
|
20
|
+
"\u043F": "p",
|
|
21
|
+
"\u0440": "r",
|
|
22
|
+
"\u0441": "s",
|
|
23
|
+
"\u0442": "t",
|
|
24
|
+
"\u0443": "u",
|
|
25
|
+
"\u0444": "f",
|
|
26
|
+
"\u0445": "h",
|
|
27
|
+
"\u0446": "ts",
|
|
28
|
+
"\u0447": "ch",
|
|
29
|
+
"\u0448": "sh",
|
|
30
|
+
"\u0449": "sch",
|
|
31
|
+
"\u044A": "",
|
|
32
|
+
"\u044B": "y",
|
|
33
|
+
"\u044C": "",
|
|
34
|
+
"\u044D": "e",
|
|
35
|
+
"\u044E": "yu",
|
|
36
|
+
"\u044F": "ya"
|
|
37
|
+
};
|
|
38
|
+
function cleanContent(text) {
|
|
39
|
+
return text.replace(/##\s*Attachments\n(?:- [^\n]+\n?)*/g, "").replace(/- media\/\d+\/[^\n]+/g, "").replace(/\*\*([^*]+)\n\*\*\n/g, "**$1**\n\n").replace(/\*\*([^*\n]+)\n([^*\n]+)\*\*/g, "**$1**\n\n**$2**").trim();
|
|
40
|
+
}
|
|
41
|
+
function generateSlug(text, fallbackIdx) {
|
|
42
|
+
const cleaned = cleanContent(text);
|
|
43
|
+
if (!cleaned || cleaned.length === 0) {
|
|
44
|
+
return `post-${fallbackIdx}`;
|
|
45
|
+
}
|
|
46
|
+
const slug = cleaned.toLowerCase().substring(0, 60).split("").map((char) => translitMap[char] || char).join("").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
47
|
+
return slug || `post-${fallbackIdx}`;
|
|
48
|
+
}
|
|
49
|
+
function extractTitle(content, fallback = "\u0411\u0435\u0437 \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u0430") {
|
|
50
|
+
if (!content) return fallback;
|
|
51
|
+
const cleaned = cleanContent(content);
|
|
52
|
+
if (!cleaned) return fallback;
|
|
53
|
+
const lines = cleaned.split("\n").filter((l) => l.trim());
|
|
54
|
+
if (lines.length === 0) return fallback;
|
|
55
|
+
let title = lines[0].replace(/[#@\[\]*]/g, "").replace(/https?:\/\/[^\s]+/g, "").trim();
|
|
56
|
+
if (title.length > 80) {
|
|
57
|
+
title = title.substring(0, 77) + "...";
|
|
58
|
+
}
|
|
59
|
+
return title || fallback;
|
|
60
|
+
}
|
|
61
|
+
function extractExcerpt(content, maxLength = 200) {
|
|
62
|
+
if (!content) return "";
|
|
63
|
+
const lines = content.replace(/https?:\/\/[^\s]+/g, "").replace(/##\s*Attachments[\s\S]*/g, "").replace(/[#@\[\]*]/g, "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
64
|
+
const text = lines.slice(1).join(" ").trim();
|
|
65
|
+
if (!text) return "";
|
|
66
|
+
if (text.length <= maxLength) return text;
|
|
67
|
+
return text.substring(0, maxLength).replace(/\s+\S*$/, "") + "...";
|
|
68
|
+
}
|
|
69
|
+
function extractAttachments(content) {
|
|
70
|
+
const attachments = [];
|
|
71
|
+
const regex = /media\/\d+\/[^\s\n]+/g;
|
|
72
|
+
let match;
|
|
73
|
+
while ((match = regex.exec(content)) !== null) {
|
|
74
|
+
attachments.push(match[0]);
|
|
75
|
+
}
|
|
76
|
+
return attachments;
|
|
77
|
+
}
|
|
78
|
+
function categorizePost(content, defaultCategory = "\u0411\u043B\u043E\u0433") {
|
|
79
|
+
const text = content.toLowerCase();
|
|
80
|
+
const categories = {
|
|
81
|
+
"\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B": [
|
|
82
|
+
{ keywords: ["\u0434\u043E/\u043F\u043E\u0441\u043B\u0435", "\u0434\u043E\u043F\u043E\u0441\u043B\u0435", "\u0434\u043E \u043A\u043E\u0440\u0440\u0435\u043A\u0446\u0438\u0438", "\u043F\u043E\u0441\u043B\u0435 \u043A\u043E\u0440\u0440\u0435\u043A\u0446\u0438\u0438", "\u043F\u043E\u0441\u043B\u0435 \u0441\u0435\u0430\u043D\u0441\u0430"], weight: 3 },
|
|
83
|
+
{ keywords: ["\u043A\u0435\u0439\u0441 \u0438\u0437 \u043F\u0440\u0430\u043A\u0442\u0438\u043A\u0438", "\u043A\u0435\u0439\u0441:", "\u0438\u0441\u0442\u043E\u0440\u0438\u044F \u043A\u043B\u0438\u0435\u043D\u0442", "\u0441\u043B\u0443\u0447\u0430\u0439 \u0438\u0437 \u043F\u0440\u0430\u043A\u0442\u0438\u043A\u0438"], weight: 3 },
|
|
84
|
+
{ keywords: ["\u043F\u0440\u0438\u0448\u0451\u043B \u0441 \u0436\u0430\u043B\u043E\u0431\u043E\u0439", "\u043F\u0440\u0438\u0448\u043B\u0430 \u0441 \u0436\u0430\u043B\u043E\u0431\u043E\u0439", "\u043E\u0431\u0440\u0430\u0442\u0438\u043B", "\u043E\u0431\u0440\u0430\u0442\u0438\u043B\u0430"], weight: 2 },
|
|
85
|
+
{ keywords: ["\u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442", "\u0443\u0434\u0430\u043B\u043E\u0441\u044C", "\u043F\u043E\u043C\u043E\u0433", "\u0438\u0437\u0431\u0430\u0432\u0438\u043B", "\u0443\u0448\u043B\u0430 \u0431\u043E\u043B\u044C", "\u043F\u0440\u043E\u0448\u043B\u0430 \u0431\u043E\u043B\u044C"], weight: 2 }
|
|
86
|
+
],
|
|
87
|
+
"\u0423\u043F\u0440\u0430\u0436\u043D\u0435\u043D\u0438\u044F": [
|
|
88
|
+
{ keywords: ["\u0443\u043F\u0440\u0430\u0436\u043D\u0435\u043D\u0438", "\u043A\u043E\u043C\u043F\u043B\u0435\u043A\u0441 \u0443\u043F\u0440\u0430\u0436\u043D\u0435\u043D\u0438\u0439", "\u0442\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A"], weight: 3 },
|
|
89
|
+
{ keywords: ["\u0440\u0430\u0441\u0442\u044F\u0436\u043A", "\u0440\u0430\u0441\u0442\u044F\u0436\u0435\u043D\u0438", "\u0440\u0430\u0437\u043C\u0438\u043D\u043A"], weight: 2 },
|
|
90
|
+
{ keywords: ["\u0441\u0430\u043C\u043E\u043C\u0430\u0441\u0441\u0430\u0436", "\u043C\u0430\u0441\u0441\u0430\u0436 \u0441\u0435\u0431\u0435", "\u0441\u0430\u043C\u043E\u0441\u0442\u043E\u044F\u0442\u0435\u043B\u044C\u043D"], weight: 2 },
|
|
91
|
+
{ keywords: ["\u043F\u043E\u0432\u0442\u043E\u0440\u0438", "\u0432\u044B\u043F\u043E\u043B\u043D\u0438", "\u0441\u0434\u0435\u043B\u0430\u0439", "\u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0439"], weight: 1 },
|
|
92
|
+
{ keywords: ["\u043A\u0443\u0440\u0441 \u0443\u043F\u0440\u0430\u0436\u043D\u0435\u043D\u0438\u0439", "\u0432\u0438\u0434\u0435\u043E \u0443\u0440\u043E\u043A", "\u0443\u0440\u043E\u043A"], weight: 2 }
|
|
93
|
+
],
|
|
94
|
+
"\u041E\u0431\u0440\u0430\u0437\u043E\u0432\u0430\u043D\u0438\u0435": [
|
|
95
|
+
{ keywords: ["\u043C\u0438\u0444", "\u043C\u0438\u0444\u043E\u0432", "\u0437\u0430\u0431\u043B\u0443\u0436\u0434\u0435\u043D\u0438"], weight: 3 },
|
|
96
|
+
{ keywords: ["\u043F\u043E\u0447\u0435\u043C\u0443", "\u043F\u0440\u0438\u0447\u0438\u043D", "\u0438\u0437-\u0437\u0430 \u0447\u0435\u0433\u043E", "\u043E\u0442\u043A\u0443\u0434\u0430"], weight: 1 },
|
|
97
|
+
{ keywords: ["\u0430\u043D\u0430\u0442\u043E\u043C\u0438", "\u043C\u044B\u0448\u0446", "\u0441\u0443\u0441\u0442\u0430\u0432", "\u043F\u043E\u0437\u0432\u043E\u043D", "\u043D\u0435\u0440\u0432", "\u0441\u0432\u044F\u0437\u043A"], weight: 2 },
|
|
98
|
+
{ keywords: ["\u043A\u0430\u043A \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442", "\u043C\u0435\u0445\u0430\u043D\u0438\u0437\u043C", "\u0444\u0438\u0437\u0438\u043E\u043B\u043E\u0433\u0438"], weight: 2 },
|
|
99
|
+
{ keywords: ["\u0447\u0442\u043E \u0442\u0430\u043A\u043E\u0435", "\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u043C", "\u043E\u0431\u044A\u044F\u0441\u043D\u044F", "\u0440\u0430\u0441\u0441\u043A\u0430\u0436\u0443"], weight: 1 }
|
|
100
|
+
],
|
|
101
|
+
"\u0417\u0434\u043E\u0440\u043E\u0432\u044C\u0435": [
|
|
102
|
+
{ keywords: ["\u0431\u043E\u043B\u044C \u0432", "\u0431\u043E\u043B\u0438\u0442", "\u0431\u043E\u043B\u0435\u0432", "\u0431\u043E\u043B\u0435\u0437\u043D"], weight: 2 },
|
|
103
|
+
{ keywords: ["\u0441\u043F\u0438\u043D\u0430", "\u0441\u043F\u0438\u043D", "\u043F\u043E\u044F\u0441\u043D\u0438\u0446", "\u0448\u0435\u044F", "\u0448\u0435\u0439\u043D", "\u043F\u043B\u0435\u0447", "\u043A\u043E\u043B\u0435\u043D", "\u0433\u043E\u043B\u043E\u0432"], weight: 2 },
|
|
104
|
+
{ keywords: ["\u0437\u0430\u0449\u0435\u043C\u043B\u0435\u043D", "\u0433\u0440\u044B\u0436", "\u043F\u0440\u043E\u0442\u0440\u0443\u0437\u0438", "\u0441\u043A\u043E\u043B\u0438\u043E\u0437", "\u043E\u0441\u0442\u0435\u043E\u0445\u043E\u043D\u0434\u0440\u043E\u0437"], weight: 3 },
|
|
105
|
+
{ keywords: ["\u0442\u0440\u0430\u0432\u043C", "\u043F\u0435\u0440\u0435\u043B\u043E\u043C", "\u0432\u044B\u0432\u0438\u0445", "\u0440\u0430\u0441\u0442\u044F\u0436\u0435\u043D"], weight: 2 },
|
|
106
|
+
{ keywords: ["\u043B\u0435\u0447\u0435\u043D\u0438", "\u0442\u0435\u0440\u0430\u043F\u0438", "\u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D", "\u0440\u0435\u0430\u0431\u0438\u043B\u0438\u0442\u0430\u0446\u0438"], weight: 1 }
|
|
107
|
+
],
|
|
108
|
+
"\u041D\u043E\u0432\u043E\u0441\u0442\u0438": [
|
|
109
|
+
{ keywords: ["\u043F\u0440\u0438\u0435\u0437\u0436\u0430\u044E", "\u043F\u0440\u0438\u0435\u0434\u0443", "\u0431\u0443\u0434\u0443 \u0432", "\u0437\u0430\u043F\u0438\u0441\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u0430", "\u0437\u0430\u043F\u0438\u0441\u044C \u043D\u0430"], weight: 3 },
|
|
110
|
+
{ keywords: ["\u043E\u0431\u044A\u044F\u0432\u043B\u0435\u043D\u0438", "\u0430\u043D\u043E\u043D\u0441", "\u043D\u043E\u0432\u043E\u0441\u0442\u044C", "\u0441\u043E\u043E\u0431\u0449\u0430\u044E"], weight: 2 },
|
|
111
|
+
{ keywords: ["\u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B \u043E\u0431\u0443\u0447\u0435\u043D\u0438\u0435", "\u043F\u043E\u043B\u0443\u0447\u0438\u043B \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043A\u0430\u0442", "\u043F\u0440\u043E\u0448\u0451\u043B \u043A\u0443\u0440\u0441"], weight: 2 },
|
|
112
|
+
{ keywords: ["\u0434\u0440\u0443\u0437\u044C\u044F,", "\u0434\u043E\u0440\u043E\u0433\u0438\u0435", "\u0440\u0430\u0434 \u0441\u043E\u043E\u0431\u0449\u0438\u0442\u044C"], weight: 1 },
|
|
113
|
+
{ keywords: ["\u0442\u0443\u043B\u0430", "\u0433\u0435\u043B\u0435\u043D\u0434\u0436\u0438\u043A", "\u043C\u043E\u0441\u043A\u0432\u0430", "\u0433\u043E\u0440\u043E\u0434"], weight: 1 }
|
|
114
|
+
],
|
|
115
|
+
"\u041E\u0431\u0440\u0430\u0437 \u0436\u0438\u0437\u043D\u0438": [
|
|
116
|
+
{ keywords: ["\u0441\u043E\u043D", "\u0441\u043F\u0430\u0442\u044C", "\u0431\u0435\u0441\u0441\u043E\u043D\u043D\u0438\u0446", "\u043D\u0435\u0434\u043E\u0441\u044B\u043F"], weight: 2 },
|
|
117
|
+
{ keywords: ["\u0441\u0442\u0440\u0435\u0441\u0441", "\u0442\u0440\u0435\u0432\u043E\u0433", "\u043D\u0435\u0440\u0432\u043D", "\u043D\u0430\u043F\u0440\u044F\u0436\u0435\u043D"], weight: 2 },
|
|
118
|
+
{ keywords: ["\u0443\u0441\u0442\u0430\u043B\u043E\u0441\u0442\u044C", "\u0443\u0441\u0442\u0430\u043B", "\u044D\u043D\u0435\u0440\u0433\u0438", "\u0431\u043E\u0434\u0440\u043E\u0441\u0442\u044C", "\u0441\u0438\u043B\u044B"], weight: 2 },
|
|
119
|
+
{ keywords: ["\u043F\u0440\u0438\u0432\u044B\u0447\u043A", "\u0440\u0435\u0436\u0438\u043C", "\u043E\u0431\u0440\u0430\u0437 \u0436\u0438\u0437\u043D\u0438", "\u043F\u0438\u0442\u0430\u043D\u0438"], weight: 2 },
|
|
120
|
+
{ keywords: ["\u0441\u043E\u0432\u0435\u0442", "\u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0430\u0446\u0438", "\u043B\u0430\u0439\u0444\u0445\u0430\u043A", "\u043F\u0440\u0430\u0432\u0438\u043B"], weight: 1 }
|
|
121
|
+
]
|
|
122
|
+
};
|
|
123
|
+
const scores = {};
|
|
124
|
+
for (const [category, rules] of Object.entries(categories)) {
|
|
125
|
+
scores[category] = 0;
|
|
126
|
+
for (const rule of rules) {
|
|
127
|
+
for (const keyword of rule.keywords) {
|
|
128
|
+
if (text.includes(keyword)) {
|
|
129
|
+
scores[category] += rule.weight;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
let maxScore = 0;
|
|
135
|
+
let bestCategory = defaultCategory;
|
|
136
|
+
for (const [category, score] of Object.entries(scores)) {
|
|
137
|
+
if (score > maxScore) {
|
|
138
|
+
maxScore = score;
|
|
139
|
+
bestCategory = category;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return maxScore >= 2 ? bestCategory : defaultCategory;
|
|
143
|
+
}
|
|
144
|
+
function parsePost(fileContent, options = {}) {
|
|
145
|
+
const { data, content } = matter(fileContent);
|
|
146
|
+
const { generateSlug: shouldGenerateSlug = true, extractAttachments: shouldExtractAttachments = true } = options;
|
|
147
|
+
return {
|
|
148
|
+
idx: data.idx || data.msg_id || 0,
|
|
149
|
+
msg_id: data.msg_id || 0,
|
|
150
|
+
date: data.date || "",
|
|
151
|
+
channel_id: data.channel_id || 0,
|
|
152
|
+
channel_title: data.channel_title || "",
|
|
153
|
+
channel_username: data.channel_username || "",
|
|
154
|
+
link: data.link || "",
|
|
155
|
+
views: data.views || 0,
|
|
156
|
+
forwards: data.forwards || 0,
|
|
157
|
+
has_media: data.has_media || false,
|
|
158
|
+
content: content.trim(),
|
|
159
|
+
slug: shouldGenerateSlug ? generateSlug(content, data.idx) : "",
|
|
160
|
+
attachments: shouldExtractAttachments ? extractAttachments(content) : []
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function groupPosts(posts, timeWindowMs = 30 * 60 * 1e3) {
|
|
164
|
+
const sortedPosts = [...posts].sort(
|
|
165
|
+
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
|
|
166
|
+
);
|
|
167
|
+
const groups = [];
|
|
168
|
+
let currentGroup = [];
|
|
169
|
+
let lastDate = null;
|
|
170
|
+
for (const post of sortedPosts) {
|
|
171
|
+
const postDate = new Date(post.date);
|
|
172
|
+
if (lastDate && Math.abs(postDate.getTime() - lastDate.getTime()) < timeWindowMs) {
|
|
173
|
+
currentGroup.push(post);
|
|
174
|
+
} else {
|
|
175
|
+
if (currentGroup.length > 0) {
|
|
176
|
+
groups.push(createGroupedPost(currentGroup));
|
|
177
|
+
}
|
|
178
|
+
currentGroup = [post];
|
|
179
|
+
}
|
|
180
|
+
lastDate = postDate;
|
|
181
|
+
}
|
|
182
|
+
if (currentGroup.length > 0) {
|
|
183
|
+
groups.push(createGroupedPost(currentGroup));
|
|
184
|
+
}
|
|
185
|
+
return groups;
|
|
186
|
+
}
|
|
187
|
+
function createGroupedPost(posts) {
|
|
188
|
+
const chronologicalGroup = [...posts].reverse();
|
|
189
|
+
const mainPost = chronologicalGroup[0];
|
|
190
|
+
const allContent = chronologicalGroup.map((p) => p.content).join("\n\n");
|
|
191
|
+
const allAttachments = chronologicalGroup.flatMap((p) => p.attachments);
|
|
192
|
+
const uniqueAttachments = [...new Set(allAttachments)];
|
|
193
|
+
const images = uniqueAttachments.filter((a) => a.match(/\.(jpg|jpeg|png|webp|gif)$/i));
|
|
194
|
+
const videos = uniqueAttachments.filter((a) => a.match(/\.(mp4|mov|webm|avi|m4v)$/i));
|
|
195
|
+
return {
|
|
196
|
+
id: `group-${mainPost.idx}`,
|
|
197
|
+
slug: generateSlug(allContent, mainPost.idx),
|
|
198
|
+
title: extractTitle(allContent),
|
|
199
|
+
excerpt: extractExcerpt(allContent),
|
|
200
|
+
date: mainPost.date,
|
|
201
|
+
category: categorizePost(allContent),
|
|
202
|
+
posts: chronologicalGroup,
|
|
203
|
+
hasMedia: chronologicalGroup.some((p) => p.has_media),
|
|
204
|
+
totalViews: chronologicalGroup.reduce((sum, p) => sum + p.views, 0),
|
|
205
|
+
coverImage: images[0],
|
|
206
|
+
images,
|
|
207
|
+
videos
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function deduplicatePosts(posts) {
|
|
211
|
+
const seenMsgIds = /* @__PURE__ */ new Set();
|
|
212
|
+
return posts.filter((p) => {
|
|
213
|
+
if (seenMsgIds.has(p.msg_id)) return false;
|
|
214
|
+
seenMsgIds.add(p.msg_id);
|
|
215
|
+
return true;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/analytics.ts
|
|
220
|
+
var config = {};
|
|
221
|
+
function configureAnalytics(options) {
|
|
222
|
+
config = { ...config, ...options };
|
|
223
|
+
}
|
|
224
|
+
function trackGoal(goal, params) {
|
|
225
|
+
if (typeof window === "undefined") return;
|
|
226
|
+
if (config.yandexId && window.ym) {
|
|
227
|
+
window.ym(config.yandexId, "reachGoal", goal, params);
|
|
228
|
+
}
|
|
229
|
+
if (config.googleId && window.gtag) {
|
|
230
|
+
window.gtag("event", goal, {
|
|
231
|
+
event_category: "engagement",
|
|
232
|
+
...params
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (config.customTracker) {
|
|
236
|
+
config.customTracker(goal, params);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function trackBookAppointment(source) {
|
|
240
|
+
trackGoal("click_book_appointment", { source });
|
|
241
|
+
}
|
|
242
|
+
function trackTelegramClick(source, page) {
|
|
243
|
+
const goalMap = {
|
|
244
|
+
blog: "click_telegram_blog",
|
|
245
|
+
header: "click_telegram_header",
|
|
246
|
+
footer: "click_telegram_footer",
|
|
247
|
+
contact: "click_telegram_contact"
|
|
248
|
+
};
|
|
249
|
+
trackGoal(goalMap[source] || `click_telegram_${source}`, { page });
|
|
250
|
+
}
|
|
251
|
+
function trackLearnMore(service) {
|
|
252
|
+
trackGoal("click_learn_more", { service });
|
|
253
|
+
}
|
|
254
|
+
function trackServiceClick(service) {
|
|
255
|
+
trackGoal("click_service", { service });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/translate.ts
|
|
259
|
+
async function translateContent(text, options) {
|
|
260
|
+
const { apiKey, apiUrl, model, targetLang = "en", sourceLang = "ru" } = options;
|
|
261
|
+
const messages = [
|
|
262
|
+
{
|
|
263
|
+
role: "system",
|
|
264
|
+
content: `You are a professional translator. Translate the following text from ${sourceLang} to ${targetLang}.
|
|
265
|
+
Keep the markdown formatting intact.
|
|
266
|
+
Only output the translated text, nothing else.
|
|
267
|
+
Do not add any explanations or notes.`
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
role: "user",
|
|
271
|
+
content: text
|
|
272
|
+
}
|
|
273
|
+
];
|
|
274
|
+
const endpoint = apiUrl.endsWith("/") ? `${apiUrl}chat/completions` : `${apiUrl}/chat/completions`;
|
|
275
|
+
const maxRetries = 3;
|
|
276
|
+
let lastError = null;
|
|
277
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
278
|
+
if (attempt > 0) {
|
|
279
|
+
const delay = 1e3 * Math.pow(2, attempt - 1);
|
|
280
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
281
|
+
}
|
|
282
|
+
const response = await fetch(endpoint, {
|
|
283
|
+
method: "POST",
|
|
284
|
+
headers: {
|
|
285
|
+
"Content-Type": "application/json",
|
|
286
|
+
"Authorization": `Bearer ${apiKey}`
|
|
287
|
+
},
|
|
288
|
+
body: JSON.stringify({
|
|
289
|
+
model,
|
|
290
|
+
messages,
|
|
291
|
+
temperature: 0.3
|
|
292
|
+
})
|
|
293
|
+
});
|
|
294
|
+
if (response.ok) {
|
|
295
|
+
const data = await response.json();
|
|
296
|
+
return data.choices[0]?.message?.content || "";
|
|
297
|
+
}
|
|
298
|
+
const error = await response.text();
|
|
299
|
+
lastError = new Error(`API error: ${response.status} - ${error}`);
|
|
300
|
+
if (response.status !== 429 && response.status < 500) {
|
|
301
|
+
throw lastError;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
throw lastError;
|
|
305
|
+
}
|
|
306
|
+
async function translateTitle(title, options) {
|
|
307
|
+
const translated = await translateContent(title, options);
|
|
308
|
+
return translated.replace(/^["']|["']$/g, "").trim();
|
|
309
|
+
}
|
|
310
|
+
function generateEnglishSlug(title) {
|
|
311
|
+
return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").substring(0, 60);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export {
|
|
315
|
+
cleanContent,
|
|
316
|
+
generateSlug,
|
|
317
|
+
extractTitle,
|
|
318
|
+
extractExcerpt,
|
|
319
|
+
extractAttachments,
|
|
320
|
+
categorizePost,
|
|
321
|
+
parsePost,
|
|
322
|
+
groupPosts,
|
|
323
|
+
deduplicatePosts,
|
|
324
|
+
configureAnalytics,
|
|
325
|
+
trackGoal,
|
|
326
|
+
trackBookAppointment,
|
|
327
|
+
trackTelegramClick,
|
|
328
|
+
trackLearnMore,
|
|
329
|
+
trackServiceClick,
|
|
330
|
+
translateContent,
|
|
331
|
+
translateTitle,
|
|
332
|
+
generateEnglishSlug
|
|
333
|
+
};
|
package/dist/index.d.mts
CHANGED
|
@@ -142,190 +142,4 @@ declare function translateTitle(title: string, options: TranslateOptions): Promi
|
|
|
142
142
|
*/
|
|
143
143
|
declare function generateEnglishSlug(title: string): string;
|
|
144
144
|
|
|
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 (posts go to outputDir/posts) */
|
|
159
|
-
outputDir: string;
|
|
160
|
-
/** Media output directory (default: outputDir/media) */
|
|
161
|
-
mediaDir?: string;
|
|
162
|
-
/** Maximum number of posts to export (0 = all) */
|
|
163
|
-
limit?: number;
|
|
164
|
-
/** Only export posts since this date */
|
|
165
|
-
since?: Date;
|
|
166
|
-
/** Only export posts until this date */
|
|
167
|
-
until?: Date;
|
|
168
|
-
/** Download media files */
|
|
169
|
-
downloadMedia?: boolean;
|
|
170
|
-
/** Number of concurrent media downloads */
|
|
171
|
-
mediaWorkers?: number;
|
|
172
|
-
/** Callback for progress updates */
|
|
173
|
-
onProgress?: (current: number, total: number, message: string) => void;
|
|
174
|
-
/** Callback to get phone number for login */
|
|
175
|
-
onPhoneNumber?: () => Promise<string>;
|
|
176
|
-
/** Callback to get verification code */
|
|
177
|
-
onCode?: () => Promise<string>;
|
|
178
|
-
/** Callback to get 2FA password */
|
|
179
|
-
onPassword?: () => Promise<string>;
|
|
180
|
-
/** Callback when session string is generated (save this for future use) */
|
|
181
|
-
onSession?: (session: string) => void;
|
|
182
|
-
}
|
|
183
|
-
interface ForwardInfo {
|
|
184
|
-
/** Original channel/user ID */
|
|
185
|
-
fromId?: number;
|
|
186
|
-
/** Original channel username (if channel) */
|
|
187
|
-
fromUsername?: string;
|
|
188
|
-
/** Original channel/user name */
|
|
189
|
-
fromName?: string;
|
|
190
|
-
/** Original post ID in source channel */
|
|
191
|
-
postId?: number;
|
|
192
|
-
/** Original post date */
|
|
193
|
-
date?: Date;
|
|
194
|
-
}
|
|
195
|
-
interface ExportedPost {
|
|
196
|
-
msgId: number;
|
|
197
|
-
date: Date;
|
|
198
|
-
content: string;
|
|
199
|
-
hasMedia: boolean;
|
|
200
|
-
mediaFiles: string[];
|
|
201
|
-
views?: number;
|
|
202
|
-
forwards?: number;
|
|
203
|
-
link: string;
|
|
204
|
-
channelUsername: string;
|
|
205
|
-
channelTitle: string;
|
|
206
|
-
replyToMsgId?: number;
|
|
207
|
-
/** Message IDs merged into this post (for grouped posts) */
|
|
208
|
-
mergedMsgIds?: number[];
|
|
209
|
-
/** Forwarding info if this post was forwarded from another source */
|
|
210
|
-
fwdFrom?: ForwardInfo;
|
|
211
|
-
}
|
|
212
|
-
interface ExportResult {
|
|
213
|
-
channelMeta: {
|
|
214
|
-
id: number;
|
|
215
|
-
username: string;
|
|
216
|
-
title: string;
|
|
217
|
-
description?: string;
|
|
218
|
-
participantsCount?: number;
|
|
219
|
-
};
|
|
220
|
-
posts: ExportedPost[];
|
|
221
|
-
session: string;
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Export messages from a Telegram channel
|
|
225
|
-
*/
|
|
226
|
-
declare function exportTelegramChannel(options: TelegramExportOptions): Promise<ExportResult>;
|
|
227
|
-
/**
|
|
228
|
-
* Format a post as markdown with YAML frontmatter
|
|
229
|
-
*/
|
|
230
|
-
declare function formatPostMarkdown(post: ExportedPost): string;
|
|
231
|
-
/**
|
|
232
|
-
* Resume export from a saved session
|
|
233
|
-
*/
|
|
234
|
-
declare function resumeExport(options: Omit<TelegramExportOptions, 'onPhoneNumber' | 'onCode' | 'onPassword'> & {
|
|
235
|
-
session: string;
|
|
236
|
-
}): Promise<ExportResult>;
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Combined Telegram export + translation module
|
|
240
|
-
*/
|
|
241
|
-
interface TranslationConfig {
|
|
242
|
-
/** API key for translation service */
|
|
243
|
-
apiKey: string;
|
|
244
|
-
/** API base URL (OpenAI-compatible) */
|
|
245
|
-
apiUrl: string;
|
|
246
|
-
/** Model name */
|
|
247
|
-
model: string;
|
|
248
|
-
/** Source language code (default: 'ru') */
|
|
249
|
-
sourceLang?: string;
|
|
250
|
-
/** Target languages array (e.g., ['en', 'de', 'zh']) */
|
|
251
|
-
targetLangs: string[];
|
|
252
|
-
/** Also keep original language version */
|
|
253
|
-
keepOriginal?: boolean;
|
|
254
|
-
}
|
|
255
|
-
interface ExportAndTranslateOptions {
|
|
256
|
-
/** Telegram API ID */
|
|
257
|
-
apiId: number;
|
|
258
|
-
/** Telegram API Hash */
|
|
259
|
-
apiHash: string;
|
|
260
|
-
/** Session string */
|
|
261
|
-
session?: string;
|
|
262
|
-
/** Target channel */
|
|
263
|
-
channel: string;
|
|
264
|
-
/** Output directory for posts */
|
|
265
|
-
outputDir: string;
|
|
266
|
-
/** Media output directory (default: outputDir/../media) */
|
|
267
|
-
mediaDir?: string;
|
|
268
|
-
/** Export limit */
|
|
269
|
-
limit?: number;
|
|
270
|
-
/** Export posts since date */
|
|
271
|
-
since?: Date;
|
|
272
|
-
/** Download media files */
|
|
273
|
-
downloadMedia?: boolean;
|
|
274
|
-
/** Translation config (optional - if not provided, no translation) */
|
|
275
|
-
translate?: TranslationConfig;
|
|
276
|
-
/** Progress callback */
|
|
277
|
-
onProgress?: (message: string) => void;
|
|
278
|
-
/** Session save callback */
|
|
279
|
-
onSession?: (session: string) => void;
|
|
280
|
-
/** Log of already processed posts (to skip) */
|
|
281
|
-
processedLog?: Record<string, any>;
|
|
282
|
-
/** Callback to save processed log */
|
|
283
|
-
onProcessedLog?: (log: Record<string, any>) => void;
|
|
284
|
-
}
|
|
285
|
-
interface ExportAndTranslateResult {
|
|
286
|
-
/** Number of posts exported */
|
|
287
|
-
exported: number;
|
|
288
|
-
/** Number of posts processed (translated/saved) */
|
|
289
|
-
processed: number;
|
|
290
|
-
/** Number of posts skipped */
|
|
291
|
-
skipped: number;
|
|
292
|
-
/** Channel metadata */
|
|
293
|
-
channelMeta: {
|
|
294
|
-
id: number;
|
|
295
|
-
username: string;
|
|
296
|
-
title: string;
|
|
297
|
-
};
|
|
298
|
-
/** Session string for future use */
|
|
299
|
-
session: string;
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Options for processing posts from intermediate files
|
|
303
|
-
*/
|
|
304
|
-
interface ProcessFromFilesOptions {
|
|
305
|
-
/** Directory with intermediate export files (.telegram-export) */
|
|
306
|
-
exportDir: string;
|
|
307
|
-
/** Output directory for posts */
|
|
308
|
-
outputDir: string;
|
|
309
|
-
/** Translation config (optional) */
|
|
310
|
-
translate?: TranslationConfig;
|
|
311
|
-
/** Progress callback */
|
|
312
|
-
onProgress?: (message: string) => void;
|
|
313
|
-
/** Only process specific message IDs (if empty, process all) */
|
|
314
|
-
msgIds?: number[];
|
|
315
|
-
/** Force reprocess even if file exists */
|
|
316
|
-
force?: boolean;
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Process posts from intermediate files (Step 2 only, no Telegram API)
|
|
320
|
-
* Use this after running exportTelegramChannel separately
|
|
321
|
-
*/
|
|
322
|
-
declare function processFromFiles(options: ProcessFromFilesOptions): Promise<{
|
|
323
|
-
processed: number;
|
|
324
|
-
skipped: number;
|
|
325
|
-
}>;
|
|
326
|
-
/**
|
|
327
|
-
* Export posts from Telegram channel with optional translation
|
|
328
|
-
*/
|
|
329
|
-
declare function exportAndTranslate(options: ExportAndTranslateOptions): Promise<ExportAndTranslateResult>;
|
|
330
|
-
|
|
331
|
-
export { type AnalyticsConfig, type ExportAndTranslateOptions, type ExportAndTranslateResult, type ExportResult, type ExportedPost, type GoalName, type GoalParams, type GroupedPost, type ParsePostOptions, type Post, type ProcessFromFilesOptions, type TelegramExportOptions, type TranslateOptions, type TranslationConfig, categorizePost, cleanContent, configureAnalytics, deduplicatePosts, exportAndTranslate, exportTelegramChannel, extractAttachments, extractExcerpt, extractTitle, formatPostMarkdown, generateEnglishSlug, generateSlug, groupPosts, parsePost, processFromFiles, resumeExport, trackBookAppointment, trackGoal, trackLearnMore, trackServiceClick, trackTelegramClick, translateContent, translateTitle };
|
|
145
|
+
export { type AnalyticsConfig, type GoalName, type GoalParams, type GroupedPost, type ParsePostOptions, type Post, type TranslateOptions, categorizePost, cleanContent, configureAnalytics, deduplicatePosts, extractAttachments, extractExcerpt, extractTitle, generateEnglishSlug, generateSlug, groupPosts, parsePost, trackBookAppointment, trackGoal, trackLearnMore, trackServiceClick, trackTelegramClick, translateContent, translateTitle };
|