koztv-blog-tools 1.0.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 +117 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +324 -0
- package/dist/index.mjs +273 -0
- package/package.json +45 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
interface Post {
|
|
2
|
+
idx: number;
|
|
3
|
+
msg_id: number;
|
|
4
|
+
date: string;
|
|
5
|
+
channel_id: number;
|
|
6
|
+
channel_title: string;
|
|
7
|
+
channel_username: string;
|
|
8
|
+
link: string;
|
|
9
|
+
views: number;
|
|
10
|
+
forwards: number;
|
|
11
|
+
has_media: boolean;
|
|
12
|
+
content: string;
|
|
13
|
+
slug: string;
|
|
14
|
+
attachments: string[];
|
|
15
|
+
}
|
|
16
|
+
interface GroupedPost {
|
|
17
|
+
id: string;
|
|
18
|
+
slug: string;
|
|
19
|
+
title: string;
|
|
20
|
+
excerpt: string;
|
|
21
|
+
date: string;
|
|
22
|
+
category: string;
|
|
23
|
+
posts: Post[];
|
|
24
|
+
hasMedia: boolean;
|
|
25
|
+
totalViews: number;
|
|
26
|
+
coverImage?: string;
|
|
27
|
+
images: string[];
|
|
28
|
+
videos: string[];
|
|
29
|
+
}
|
|
30
|
+
interface ParsePostOptions {
|
|
31
|
+
/** Generate slug from content */
|
|
32
|
+
generateSlug?: boolean;
|
|
33
|
+
/** Extract attachments from content */
|
|
34
|
+
extractAttachments?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Clean content by removing attachment sections and media references
|
|
38
|
+
*/
|
|
39
|
+
declare function cleanContent(text: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Generate URL-safe slug from text
|
|
42
|
+
*/
|
|
43
|
+
declare function generateSlug(text: string, fallbackIdx: number): string;
|
|
44
|
+
/**
|
|
45
|
+
* Extract title from post content
|
|
46
|
+
*/
|
|
47
|
+
declare function extractTitle(content: string, fallback?: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Extract excerpt from post content (skips first line which is title)
|
|
50
|
+
*/
|
|
51
|
+
declare function extractExcerpt(content: string, maxLength?: number): string;
|
|
52
|
+
/**
|
|
53
|
+
* Extract media attachments from content
|
|
54
|
+
*/
|
|
55
|
+
declare function extractAttachments(content: string): string[];
|
|
56
|
+
/**
|
|
57
|
+
* Categorize post based on content keywords
|
|
58
|
+
*/
|
|
59
|
+
declare function categorizePost(content: string, defaultCategory?: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Parse a markdown file content into a Post object
|
|
62
|
+
*/
|
|
63
|
+
declare function parsePost(fileContent: string, options?: ParsePostOptions): Post;
|
|
64
|
+
/**
|
|
65
|
+
* Group posts by time proximity (posts within timeWindowMs are grouped together)
|
|
66
|
+
*/
|
|
67
|
+
declare function groupPosts(posts: Post[], timeWindowMs?: number): GroupedPost[];
|
|
68
|
+
/**
|
|
69
|
+
* Deduplicate posts by msg_id
|
|
70
|
+
*/
|
|
71
|
+
declare function deduplicatePosts(posts: Post[]): Post[];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Analytics tracking utilities
|
|
75
|
+
* Works with Yandex Metrica, Google Analytics, or custom trackers
|
|
76
|
+
*/
|
|
77
|
+
type GoalName = 'click_book_appointment' | 'click_telegram_blog' | 'click_telegram_header' | 'click_telegram_footer' | 'click_telegram_contact' | 'click_learn_more' | 'click_service' | string;
|
|
78
|
+
interface GoalParams {
|
|
79
|
+
page?: string;
|
|
80
|
+
service?: string;
|
|
81
|
+
source?: string;
|
|
82
|
+
[key: string]: unknown;
|
|
83
|
+
}
|
|
84
|
+
interface AnalyticsConfig {
|
|
85
|
+
/** Yandex Metrika counter ID */
|
|
86
|
+
yandexId?: number;
|
|
87
|
+
/** Google Analytics measurement ID */
|
|
88
|
+
googleId?: string;
|
|
89
|
+
/** Custom tracking function */
|
|
90
|
+
customTracker?: (goal: string, params?: GoalParams) => void;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Configure analytics tracking
|
|
94
|
+
*/
|
|
95
|
+
declare function configureAnalytics(options: AnalyticsConfig): void;
|
|
96
|
+
/**
|
|
97
|
+
* Track a goal/event
|
|
98
|
+
*/
|
|
99
|
+
declare function trackGoal(goal: GoalName, params?: GoalParams): void;
|
|
100
|
+
/**
|
|
101
|
+
* Track appointment booking click
|
|
102
|
+
*/
|
|
103
|
+
declare function trackBookAppointment(source: string): void;
|
|
104
|
+
/**
|
|
105
|
+
* Track Telegram link click
|
|
106
|
+
*/
|
|
107
|
+
declare function trackTelegramClick(source: 'blog' | 'header' | 'footer' | 'contact' | string, page?: string): void;
|
|
108
|
+
/**
|
|
109
|
+
* Track "Learn more" click
|
|
110
|
+
*/
|
|
111
|
+
declare function trackLearnMore(service: string): void;
|
|
112
|
+
/**
|
|
113
|
+
* Track service click
|
|
114
|
+
*/
|
|
115
|
+
declare function trackServiceClick(service: string): void;
|
|
116
|
+
|
|
117
|
+
export { type AnalyticsConfig, type GoalName, type GoalParams, type GroupedPost, type ParsePostOptions, type Post, categorizePost, cleanContent, configureAnalytics, deduplicatePosts, extractAttachments, extractExcerpt, extractTitle, generateSlug, groupPosts, parsePost, trackBookAppointment, trackGoal, trackLearnMore, trackServiceClick, trackTelegramClick };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
interface Post {
|
|
2
|
+
idx: number;
|
|
3
|
+
msg_id: number;
|
|
4
|
+
date: string;
|
|
5
|
+
channel_id: number;
|
|
6
|
+
channel_title: string;
|
|
7
|
+
channel_username: string;
|
|
8
|
+
link: string;
|
|
9
|
+
views: number;
|
|
10
|
+
forwards: number;
|
|
11
|
+
has_media: boolean;
|
|
12
|
+
content: string;
|
|
13
|
+
slug: string;
|
|
14
|
+
attachments: string[];
|
|
15
|
+
}
|
|
16
|
+
interface GroupedPost {
|
|
17
|
+
id: string;
|
|
18
|
+
slug: string;
|
|
19
|
+
title: string;
|
|
20
|
+
excerpt: string;
|
|
21
|
+
date: string;
|
|
22
|
+
category: string;
|
|
23
|
+
posts: Post[];
|
|
24
|
+
hasMedia: boolean;
|
|
25
|
+
totalViews: number;
|
|
26
|
+
coverImage?: string;
|
|
27
|
+
images: string[];
|
|
28
|
+
videos: string[];
|
|
29
|
+
}
|
|
30
|
+
interface ParsePostOptions {
|
|
31
|
+
/** Generate slug from content */
|
|
32
|
+
generateSlug?: boolean;
|
|
33
|
+
/** Extract attachments from content */
|
|
34
|
+
extractAttachments?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Clean content by removing attachment sections and media references
|
|
38
|
+
*/
|
|
39
|
+
declare function cleanContent(text: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Generate URL-safe slug from text
|
|
42
|
+
*/
|
|
43
|
+
declare function generateSlug(text: string, fallbackIdx: number): string;
|
|
44
|
+
/**
|
|
45
|
+
* Extract title from post content
|
|
46
|
+
*/
|
|
47
|
+
declare function extractTitle(content: string, fallback?: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Extract excerpt from post content (skips first line which is title)
|
|
50
|
+
*/
|
|
51
|
+
declare function extractExcerpt(content: string, maxLength?: number): string;
|
|
52
|
+
/**
|
|
53
|
+
* Extract media attachments from content
|
|
54
|
+
*/
|
|
55
|
+
declare function extractAttachments(content: string): string[];
|
|
56
|
+
/**
|
|
57
|
+
* Categorize post based on content keywords
|
|
58
|
+
*/
|
|
59
|
+
declare function categorizePost(content: string, defaultCategory?: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Parse a markdown file content into a Post object
|
|
62
|
+
*/
|
|
63
|
+
declare function parsePost(fileContent: string, options?: ParsePostOptions): Post;
|
|
64
|
+
/**
|
|
65
|
+
* Group posts by time proximity (posts within timeWindowMs are grouped together)
|
|
66
|
+
*/
|
|
67
|
+
declare function groupPosts(posts: Post[], timeWindowMs?: number): GroupedPost[];
|
|
68
|
+
/**
|
|
69
|
+
* Deduplicate posts by msg_id
|
|
70
|
+
*/
|
|
71
|
+
declare function deduplicatePosts(posts: Post[]): Post[];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Analytics tracking utilities
|
|
75
|
+
* Works with Yandex Metrica, Google Analytics, or custom trackers
|
|
76
|
+
*/
|
|
77
|
+
type GoalName = 'click_book_appointment' | 'click_telegram_blog' | 'click_telegram_header' | 'click_telegram_footer' | 'click_telegram_contact' | 'click_learn_more' | 'click_service' | string;
|
|
78
|
+
interface GoalParams {
|
|
79
|
+
page?: string;
|
|
80
|
+
service?: string;
|
|
81
|
+
source?: string;
|
|
82
|
+
[key: string]: unknown;
|
|
83
|
+
}
|
|
84
|
+
interface AnalyticsConfig {
|
|
85
|
+
/** Yandex Metrika counter ID */
|
|
86
|
+
yandexId?: number;
|
|
87
|
+
/** Google Analytics measurement ID */
|
|
88
|
+
googleId?: string;
|
|
89
|
+
/** Custom tracking function */
|
|
90
|
+
customTracker?: (goal: string, params?: GoalParams) => void;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Configure analytics tracking
|
|
94
|
+
*/
|
|
95
|
+
declare function configureAnalytics(options: AnalyticsConfig): void;
|
|
96
|
+
/**
|
|
97
|
+
* Track a goal/event
|
|
98
|
+
*/
|
|
99
|
+
declare function trackGoal(goal: GoalName, params?: GoalParams): void;
|
|
100
|
+
/**
|
|
101
|
+
* Track appointment booking click
|
|
102
|
+
*/
|
|
103
|
+
declare function trackBookAppointment(source: string): void;
|
|
104
|
+
/**
|
|
105
|
+
* Track Telegram link click
|
|
106
|
+
*/
|
|
107
|
+
declare function trackTelegramClick(source: 'blog' | 'header' | 'footer' | 'contact' | string, page?: string): void;
|
|
108
|
+
/**
|
|
109
|
+
* Track "Learn more" click
|
|
110
|
+
*/
|
|
111
|
+
declare function trackLearnMore(service: string): void;
|
|
112
|
+
/**
|
|
113
|
+
* Track service click
|
|
114
|
+
*/
|
|
115
|
+
declare function trackServiceClick(service: string): void;
|
|
116
|
+
|
|
117
|
+
export { type AnalyticsConfig, type GoalName, type GoalParams, type GroupedPost, type ParsePostOptions, type Post, categorizePost, cleanContent, configureAnalytics, deduplicatePosts, extractAttachments, extractExcerpt, extractTitle, generateSlug, groupPosts, parsePost, trackBookAppointment, trackGoal, trackLearnMore, trackServiceClick, trackTelegramClick };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
categorizePost: () => categorizePost,
|
|
34
|
+
cleanContent: () => cleanContent,
|
|
35
|
+
configureAnalytics: () => configureAnalytics,
|
|
36
|
+
deduplicatePosts: () => deduplicatePosts,
|
|
37
|
+
extractAttachments: () => extractAttachments,
|
|
38
|
+
extractExcerpt: () => extractExcerpt,
|
|
39
|
+
extractTitle: () => extractTitle,
|
|
40
|
+
generateSlug: () => generateSlug,
|
|
41
|
+
groupPosts: () => groupPosts,
|
|
42
|
+
parsePost: () => parsePost,
|
|
43
|
+
trackBookAppointment: () => trackBookAppointment,
|
|
44
|
+
trackGoal: () => trackGoal,
|
|
45
|
+
trackLearnMore: () => trackLearnMore,
|
|
46
|
+
trackServiceClick: () => trackServiceClick,
|
|
47
|
+
trackTelegramClick: () => trackTelegramClick
|
|
48
|
+
});
|
|
49
|
+
module.exports = __toCommonJS(index_exports);
|
|
50
|
+
|
|
51
|
+
// src/posts.ts
|
|
52
|
+
var import_gray_matter = __toESM(require("gray-matter"));
|
|
53
|
+
var translitMap = {
|
|
54
|
+
"\u0430": "a",
|
|
55
|
+
"\u0431": "b",
|
|
56
|
+
"\u0432": "v",
|
|
57
|
+
"\u0433": "g",
|
|
58
|
+
"\u0434": "d",
|
|
59
|
+
"\u0435": "e",
|
|
60
|
+
"\u0451": "yo",
|
|
61
|
+
"\u0436": "zh",
|
|
62
|
+
"\u0437": "z",
|
|
63
|
+
"\u0438": "i",
|
|
64
|
+
"\u0439": "y",
|
|
65
|
+
"\u043A": "k",
|
|
66
|
+
"\u043B": "l",
|
|
67
|
+
"\u043C": "m",
|
|
68
|
+
"\u043D": "n",
|
|
69
|
+
"\u043E": "o",
|
|
70
|
+
"\u043F": "p",
|
|
71
|
+
"\u0440": "r",
|
|
72
|
+
"\u0441": "s",
|
|
73
|
+
"\u0442": "t",
|
|
74
|
+
"\u0443": "u",
|
|
75
|
+
"\u0444": "f",
|
|
76
|
+
"\u0445": "h",
|
|
77
|
+
"\u0446": "ts",
|
|
78
|
+
"\u0447": "ch",
|
|
79
|
+
"\u0448": "sh",
|
|
80
|
+
"\u0449": "sch",
|
|
81
|
+
"\u044A": "",
|
|
82
|
+
"\u044B": "y",
|
|
83
|
+
"\u044C": "",
|
|
84
|
+
"\u044D": "e",
|
|
85
|
+
"\u044E": "yu",
|
|
86
|
+
"\u044F": "ya"
|
|
87
|
+
};
|
|
88
|
+
function cleanContent(text) {
|
|
89
|
+
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();
|
|
90
|
+
}
|
|
91
|
+
function generateSlug(text, fallbackIdx) {
|
|
92
|
+
const cleaned = cleanContent(text);
|
|
93
|
+
if (!cleaned || cleaned.length === 0) {
|
|
94
|
+
return `post-${fallbackIdx}`;
|
|
95
|
+
}
|
|
96
|
+
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, "");
|
|
97
|
+
return slug || `post-${fallbackIdx}`;
|
|
98
|
+
}
|
|
99
|
+
function extractTitle(content, fallback = "\u0411\u0435\u0437 \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u0430") {
|
|
100
|
+
if (!content) return fallback;
|
|
101
|
+
const cleaned = cleanContent(content);
|
|
102
|
+
if (!cleaned) return fallback;
|
|
103
|
+
const lines = cleaned.split("\n").filter((l) => l.trim());
|
|
104
|
+
if (lines.length === 0) return fallback;
|
|
105
|
+
let title = lines[0].replace(/[#@\[\]*]/g, "").replace(/https?:\/\/[^\s]+/g, "").trim();
|
|
106
|
+
if (title.length > 80) {
|
|
107
|
+
title = title.substring(0, 77) + "...";
|
|
108
|
+
}
|
|
109
|
+
return title || fallback;
|
|
110
|
+
}
|
|
111
|
+
function extractExcerpt(content, maxLength = 200) {
|
|
112
|
+
if (!content) return "";
|
|
113
|
+
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);
|
|
114
|
+
const text = lines.slice(1).join(" ").trim();
|
|
115
|
+
if (!text) return "";
|
|
116
|
+
if (text.length <= maxLength) return text;
|
|
117
|
+
return text.substring(0, maxLength).replace(/\s+\S*$/, "") + "...";
|
|
118
|
+
}
|
|
119
|
+
function extractAttachments(content) {
|
|
120
|
+
const attachments = [];
|
|
121
|
+
const regex = /media\/\d+\/[^\s\n]+/g;
|
|
122
|
+
let match;
|
|
123
|
+
while ((match = regex.exec(content)) !== null) {
|
|
124
|
+
attachments.push(match[0]);
|
|
125
|
+
}
|
|
126
|
+
return attachments;
|
|
127
|
+
}
|
|
128
|
+
function categorizePost(content, defaultCategory = "\u0411\u043B\u043E\u0433") {
|
|
129
|
+
const text = content.toLowerCase();
|
|
130
|
+
const categories = {
|
|
131
|
+
"\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B": [
|
|
132
|
+
{ 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 },
|
|
133
|
+
{ 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 },
|
|
134
|
+
{ 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 },
|
|
135
|
+
{ 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 }
|
|
136
|
+
],
|
|
137
|
+
"\u0423\u043F\u0440\u0430\u0436\u043D\u0435\u043D\u0438\u044F": [
|
|
138
|
+
{ 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 },
|
|
139
|
+
{ 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 },
|
|
140
|
+
{ 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 },
|
|
141
|
+
{ 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 },
|
|
142
|
+
{ 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 }
|
|
143
|
+
],
|
|
144
|
+
"\u041E\u0431\u0440\u0430\u0437\u043E\u0432\u0430\u043D\u0438\u0435": [
|
|
145
|
+
{ keywords: ["\u043C\u0438\u0444", "\u043C\u0438\u0444\u043E\u0432", "\u0437\u0430\u0431\u043B\u0443\u0436\u0434\u0435\u043D\u0438"], weight: 3 },
|
|
146
|
+
{ 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 },
|
|
147
|
+
{ 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 },
|
|
148
|
+
{ 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 },
|
|
149
|
+
{ 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 }
|
|
150
|
+
],
|
|
151
|
+
"\u0417\u0434\u043E\u0440\u043E\u0432\u044C\u0435": [
|
|
152
|
+
{ keywords: ["\u0431\u043E\u043B\u044C \u0432", "\u0431\u043E\u043B\u0438\u0442", "\u0431\u043E\u043B\u0435\u0432", "\u0431\u043E\u043B\u0435\u0437\u043D"], weight: 2 },
|
|
153
|
+
{ 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 },
|
|
154
|
+
{ 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 },
|
|
155
|
+
{ 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 },
|
|
156
|
+
{ 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 }
|
|
157
|
+
],
|
|
158
|
+
"\u041D\u043E\u0432\u043E\u0441\u0442\u0438": [
|
|
159
|
+
{ 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 },
|
|
160
|
+
{ 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 },
|
|
161
|
+
{ 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 },
|
|
162
|
+
{ 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 },
|
|
163
|
+
{ 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 }
|
|
164
|
+
],
|
|
165
|
+
"\u041E\u0431\u0440\u0430\u0437 \u0436\u0438\u0437\u043D\u0438": [
|
|
166
|
+
{ 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 },
|
|
167
|
+
{ 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 },
|
|
168
|
+
{ 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 },
|
|
169
|
+
{ 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 },
|
|
170
|
+
{ 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 }
|
|
171
|
+
]
|
|
172
|
+
};
|
|
173
|
+
const scores = {};
|
|
174
|
+
for (const [category, rules] of Object.entries(categories)) {
|
|
175
|
+
scores[category] = 0;
|
|
176
|
+
for (const rule of rules) {
|
|
177
|
+
for (const keyword of rule.keywords) {
|
|
178
|
+
if (text.includes(keyword)) {
|
|
179
|
+
scores[category] += rule.weight;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
let maxScore = 0;
|
|
185
|
+
let bestCategory = defaultCategory;
|
|
186
|
+
for (const [category, score] of Object.entries(scores)) {
|
|
187
|
+
if (score > maxScore) {
|
|
188
|
+
maxScore = score;
|
|
189
|
+
bestCategory = category;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return maxScore >= 2 ? bestCategory : defaultCategory;
|
|
193
|
+
}
|
|
194
|
+
function parsePost(fileContent, options = {}) {
|
|
195
|
+
const { data, content } = (0, import_gray_matter.default)(fileContent);
|
|
196
|
+
const { generateSlug: shouldGenerateSlug = true, extractAttachments: shouldExtractAttachments = true } = options;
|
|
197
|
+
return {
|
|
198
|
+
idx: data.idx || 0,
|
|
199
|
+
msg_id: data.msg_id || 0,
|
|
200
|
+
date: data.date || "",
|
|
201
|
+
channel_id: data.channel_id || 0,
|
|
202
|
+
channel_title: data.channel_title || "",
|
|
203
|
+
channel_username: data.channel_username || "",
|
|
204
|
+
link: data.link || "",
|
|
205
|
+
views: data.views || 0,
|
|
206
|
+
forwards: data.forwards || 0,
|
|
207
|
+
has_media: data.has_media || false,
|
|
208
|
+
content: content.trim(),
|
|
209
|
+
slug: shouldGenerateSlug ? generateSlug(content, data.idx) : "",
|
|
210
|
+
attachments: shouldExtractAttachments ? extractAttachments(content) : []
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function groupPosts(posts, timeWindowMs = 30 * 60 * 1e3) {
|
|
214
|
+
const sortedPosts = [...posts].sort(
|
|
215
|
+
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
|
|
216
|
+
);
|
|
217
|
+
const groups = [];
|
|
218
|
+
let currentGroup = [];
|
|
219
|
+
let lastDate = null;
|
|
220
|
+
for (const post of sortedPosts) {
|
|
221
|
+
const postDate = new Date(post.date);
|
|
222
|
+
if (lastDate && Math.abs(postDate.getTime() - lastDate.getTime()) < timeWindowMs) {
|
|
223
|
+
currentGroup.push(post);
|
|
224
|
+
} else {
|
|
225
|
+
if (currentGroup.length > 0) {
|
|
226
|
+
groups.push(createGroupedPost(currentGroup));
|
|
227
|
+
}
|
|
228
|
+
currentGroup = [post];
|
|
229
|
+
}
|
|
230
|
+
lastDate = postDate;
|
|
231
|
+
}
|
|
232
|
+
if (currentGroup.length > 0) {
|
|
233
|
+
groups.push(createGroupedPost(currentGroup));
|
|
234
|
+
}
|
|
235
|
+
return groups;
|
|
236
|
+
}
|
|
237
|
+
function createGroupedPost(posts) {
|
|
238
|
+
const chronologicalGroup = [...posts].reverse();
|
|
239
|
+
const mainPost = chronologicalGroup[0];
|
|
240
|
+
const allContent = chronologicalGroup.map((p) => p.content).join("\n\n");
|
|
241
|
+
const allAttachments = chronologicalGroup.flatMap((p) => p.attachments);
|
|
242
|
+
const uniqueAttachments = [...new Set(allAttachments)];
|
|
243
|
+
const images = uniqueAttachments.filter((a) => a.match(/\.(jpg|jpeg|png|webp|gif)$/i));
|
|
244
|
+
const videos = uniqueAttachments.filter((a) => a.match(/\.(mp4|mov|webm|avi|m4v)$/i));
|
|
245
|
+
return {
|
|
246
|
+
id: `group-${mainPost.idx}`,
|
|
247
|
+
slug: generateSlug(allContent, mainPost.idx),
|
|
248
|
+
title: extractTitle(allContent),
|
|
249
|
+
excerpt: extractExcerpt(allContent),
|
|
250
|
+
date: mainPost.date,
|
|
251
|
+
category: categorizePost(allContent),
|
|
252
|
+
posts: chronologicalGroup,
|
|
253
|
+
hasMedia: chronologicalGroup.some((p) => p.has_media),
|
|
254
|
+
totalViews: chronologicalGroup.reduce((sum, p) => sum + p.views, 0),
|
|
255
|
+
coverImage: images[0],
|
|
256
|
+
images,
|
|
257
|
+
videos
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function deduplicatePosts(posts) {
|
|
261
|
+
const seenMsgIds = /* @__PURE__ */ new Set();
|
|
262
|
+
return posts.filter((p) => {
|
|
263
|
+
if (seenMsgIds.has(p.msg_id)) return false;
|
|
264
|
+
seenMsgIds.add(p.msg_id);
|
|
265
|
+
return true;
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/analytics.ts
|
|
270
|
+
var config = {};
|
|
271
|
+
function configureAnalytics(options) {
|
|
272
|
+
config = { ...config, ...options };
|
|
273
|
+
}
|
|
274
|
+
function trackGoal(goal, params) {
|
|
275
|
+
if (typeof window === "undefined") return;
|
|
276
|
+
if (config.yandexId && window.ym) {
|
|
277
|
+
window.ym(config.yandexId, "reachGoal", goal, params);
|
|
278
|
+
}
|
|
279
|
+
if (config.googleId && window.gtag) {
|
|
280
|
+
window.gtag("event", goal, {
|
|
281
|
+
event_category: "engagement",
|
|
282
|
+
...params
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (config.customTracker) {
|
|
286
|
+
config.customTracker(goal, params);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function trackBookAppointment(source) {
|
|
290
|
+
trackGoal("click_book_appointment", { source });
|
|
291
|
+
}
|
|
292
|
+
function trackTelegramClick(source, page) {
|
|
293
|
+
const goalMap = {
|
|
294
|
+
blog: "click_telegram_blog",
|
|
295
|
+
header: "click_telegram_header",
|
|
296
|
+
footer: "click_telegram_footer",
|
|
297
|
+
contact: "click_telegram_contact"
|
|
298
|
+
};
|
|
299
|
+
trackGoal(goalMap[source] || `click_telegram_${source}`, { page });
|
|
300
|
+
}
|
|
301
|
+
function trackLearnMore(service) {
|
|
302
|
+
trackGoal("click_learn_more", { service });
|
|
303
|
+
}
|
|
304
|
+
function trackServiceClick(service) {
|
|
305
|
+
trackGoal("click_service", { service });
|
|
306
|
+
}
|
|
307
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
308
|
+
0 && (module.exports = {
|
|
309
|
+
categorizePost,
|
|
310
|
+
cleanContent,
|
|
311
|
+
configureAnalytics,
|
|
312
|
+
deduplicatePosts,
|
|
313
|
+
extractAttachments,
|
|
314
|
+
extractExcerpt,
|
|
315
|
+
extractTitle,
|
|
316
|
+
generateSlug,
|
|
317
|
+
groupPosts,
|
|
318
|
+
parsePost,
|
|
319
|
+
trackBookAppointment,
|
|
320
|
+
trackGoal,
|
|
321
|
+
trackLearnMore,
|
|
322
|
+
trackServiceClick,
|
|
323
|
+
trackTelegramClick
|
|
324
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
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 || 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
|
+
export {
|
|
258
|
+
categorizePost,
|
|
259
|
+
cleanContent,
|
|
260
|
+
configureAnalytics,
|
|
261
|
+
deduplicatePosts,
|
|
262
|
+
extractAttachments,
|
|
263
|
+
extractExcerpt,
|
|
264
|
+
extractTitle,
|
|
265
|
+
generateSlug,
|
|
266
|
+
groupPosts,
|
|
267
|
+
parsePost,
|
|
268
|
+
trackBookAppointment,
|
|
269
|
+
trackGoal,
|
|
270
|
+
trackLearnMore,
|
|
271
|
+
trackServiceClick,
|
|
272
|
+
trackTelegramClick
|
|
273
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koztv-blog-tools",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared utilities for Telegram-based blog sites",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"blog",
|
|
24
|
+
"telegram",
|
|
25
|
+
"markdown",
|
|
26
|
+
"static-site"
|
|
27
|
+
],
|
|
28
|
+
"author": "Koz TV",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"tsup": "^8.0.0",
|
|
32
|
+
"typescript": "^5.0.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"gray-matter": "^4.0.3"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"gray-matter": "^4.0.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependenciesMeta": {
|
|
41
|
+
"gray-matter": {
|
|
42
|
+
"optional": true
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|