koztv-blog-tools 1.2.0 → 1.2.1
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/README.md +215 -0
- package/dist/index.js +7 -3
- package/dist/index.mjs +7 -3
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# koztv-blog-tools
|
|
2
|
+
|
|
3
|
+
Shared utilities for Telegram-based blog sites. Export posts from Telegram channels, translate with LLM APIs, and generate markdown for static site generators.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install koztv-blog-tools
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Telegram Export** — Export full channel history via MTProto (gramjs)
|
|
14
|
+
- **Translation** — Translate posts using OpenAI-compatible APIs (GLM, OpenAI, etc.)
|
|
15
|
+
- **Multi-language** — Export to multiple languages simultaneously
|
|
16
|
+
- **Markdown Generation** — Create markdown files with YAML frontmatter
|
|
17
|
+
- **Media Download** — Download photos, videos, and documents
|
|
18
|
+
- **Incremental Export** — Track processed posts, skip duplicates
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### Export + Translate
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const { exportAndTranslate } = require('koztv-blog-tools');
|
|
26
|
+
|
|
27
|
+
const result = await exportAndTranslate({
|
|
28
|
+
// Telegram credentials (from https://my.telegram.org)
|
|
29
|
+
apiId: 12345678,
|
|
30
|
+
apiHash: 'your_api_hash',
|
|
31
|
+
session: 'saved_session_string', // from previous auth
|
|
32
|
+
|
|
33
|
+
// Target channel
|
|
34
|
+
channel: '@channelname',
|
|
35
|
+
|
|
36
|
+
// Output
|
|
37
|
+
outputDir: './content/posts',
|
|
38
|
+
mediaDir: './public/media',
|
|
39
|
+
|
|
40
|
+
// Translation (optional)
|
|
41
|
+
translate: {
|
|
42
|
+
apiKey: 'your_llm_api_key',
|
|
43
|
+
apiUrl: 'https://api.openai.com/v1', // or GLM, etc.
|
|
44
|
+
model: 'gpt-4',
|
|
45
|
+
sourceLang: 'ru',
|
|
46
|
+
targetLangs: ['en', 'de'], // translate to multiple languages
|
|
47
|
+
keepOriginal: true, // also save original language
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// Callbacks
|
|
51
|
+
onProgress: (msg) => console.log(msg),
|
|
52
|
+
onSession: (s) => saveSession(s), // save for future use
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log(`Exported: ${result.exported}, Processed: ${result.processed}`);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Export Only (no translation)
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
const { exportTelegramChannel } = require('koztv-blog-tools');
|
|
62
|
+
|
|
63
|
+
const result = await exportTelegramChannel({
|
|
64
|
+
apiId: 12345678,
|
|
65
|
+
apiHash: 'your_api_hash',
|
|
66
|
+
session: 'saved_session_string',
|
|
67
|
+
target: '@channelname',
|
|
68
|
+
outputDir: './export',
|
|
69
|
+
downloadMedia: true,
|
|
70
|
+
limit: 100, // optional: limit number of posts
|
|
71
|
+
since: new Date('2024-01-01'), // optional: filter by date
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Translate Text
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
const { translateContent, translateTitle } = require('koztv-blog-tools');
|
|
79
|
+
|
|
80
|
+
const translated = await translateContent('Привет мир', {
|
|
81
|
+
apiKey: 'your_api_key',
|
|
82
|
+
apiUrl: 'https://api.openai.com/v1',
|
|
83
|
+
model: 'gpt-4',
|
|
84
|
+
sourceLang: 'ru',
|
|
85
|
+
targetLang: 'en',
|
|
86
|
+
});
|
|
87
|
+
// => "Hello world"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Authentication
|
|
91
|
+
|
|
92
|
+
First-time authentication requires QR code login. See `scripts/qr-login.js` in your project:
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
// Example QR login script
|
|
96
|
+
const { TelegramClient } = require('telegram');
|
|
97
|
+
const { StringSession } = require('telegram/sessions');
|
|
98
|
+
|
|
99
|
+
const client = new TelegramClient(
|
|
100
|
+
new StringSession(''),
|
|
101
|
+
API_ID,
|
|
102
|
+
API_HASH,
|
|
103
|
+
{ connectionRetries: 5 }
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
await client.start({
|
|
107
|
+
phoneNumber: async () => prompt('Phone: '),
|
|
108
|
+
password: async () => prompt('2FA: '),
|
|
109
|
+
phoneCode: async () => prompt('Code: '),
|
|
110
|
+
onError: console.error,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
console.log('Session:', client.session.save());
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Output Structure
|
|
117
|
+
|
|
118
|
+
With multi-language enabled (`targetLangs` + `keepOriginal`):
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
content/posts/
|
|
122
|
+
en/
|
|
123
|
+
my-post-slug/
|
|
124
|
+
index.md
|
|
125
|
+
ru/
|
|
126
|
+
my-post-slug/
|
|
127
|
+
index.md
|
|
128
|
+
public/media/
|
|
129
|
+
000123/
|
|
130
|
+
image1.jpg
|
|
131
|
+
image2.jpg
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Markdown format:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
---
|
|
138
|
+
title: "Post Title"
|
|
139
|
+
date: 2024-01-15
|
|
140
|
+
lang: en
|
|
141
|
+
original_link: "https://t.me/channel/123"
|
|
142
|
+
translated_from: "ru"
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
Post content here...
|
|
146
|
+
|
|
147
|
+

|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Environment Variables
|
|
151
|
+
|
|
152
|
+
For GitHub Actions / CI:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
TELEGRAM_API_ID=12345678
|
|
156
|
+
TELEGRAM_API_HASH=your_api_hash
|
|
157
|
+
TELEGRAM_SESSION=base64_session_string
|
|
158
|
+
TELEGRAM_CHANNEL=@channelname
|
|
159
|
+
|
|
160
|
+
LLM_API_KEY=your_llm_key
|
|
161
|
+
LLM_API_URL=https://api.openai.com/v1
|
|
162
|
+
LLM_MODEL=gpt-4
|
|
163
|
+
|
|
164
|
+
TARGET_LANGS=en,de # comma-separated
|
|
165
|
+
KEEP_ORIGINAL=true # keep source language
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## API Reference
|
|
169
|
+
|
|
170
|
+
### exportAndTranslate(options)
|
|
171
|
+
|
|
172
|
+
Main function for export + translation workflow.
|
|
173
|
+
|
|
174
|
+
**Options:**
|
|
175
|
+
- `apiId`, `apiHash`, `session` — Telegram credentials
|
|
176
|
+
- `channel` — Target channel (@username or ID)
|
|
177
|
+
- `outputDir` — Where to save markdown files
|
|
178
|
+
- `mediaDir` — Where to save media (optional)
|
|
179
|
+
- `limit` — Max posts to export (optional)
|
|
180
|
+
- `since` — Export posts after this date (optional)
|
|
181
|
+
- `downloadMedia` — Download media files (default: true)
|
|
182
|
+
- `translate` — Translation config (optional)
|
|
183
|
+
- `onProgress` — Progress callback
|
|
184
|
+
- `onSession` — Session save callback
|
|
185
|
+
- `processedLog` — Object tracking processed posts
|
|
186
|
+
- `onProcessedLog` — Callback to save processed log
|
|
187
|
+
|
|
188
|
+
### exportTelegramChannel(options)
|
|
189
|
+
|
|
190
|
+
Low-level Telegram export.
|
|
191
|
+
|
|
192
|
+
### translateContent(text, options)
|
|
193
|
+
|
|
194
|
+
Translate text content.
|
|
195
|
+
|
|
196
|
+
### translateTitle(title, options)
|
|
197
|
+
|
|
198
|
+
Translate title (optimized prompt for short text).
|
|
199
|
+
|
|
200
|
+
### generateEnglishSlug(title, options)
|
|
201
|
+
|
|
202
|
+
Generate URL-friendly slug from any language title.
|
|
203
|
+
|
|
204
|
+
## Used By
|
|
205
|
+
|
|
206
|
+
- [koz.tv](https://koz.tv) — Personal blog with Telegram sync
|
|
207
|
+
- [staskoz.com](https://staskoz.com) — Another blog using this package
|
|
208
|
+
|
|
209
|
+
## Related
|
|
210
|
+
|
|
211
|
+
- [k-engine](https://github.com/Koz-TV/k-engine) — Static site generator with multi-language support
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -725,9 +725,13 @@ async function processPost(post, options, exportDir) {
|
|
|
725
725
|
}
|
|
726
726
|
}
|
|
727
727
|
if (mediaFiles.length > 0) {
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
728
|
+
const images = mediaFiles.filter((f) => !f.match(/\.(mp4|mov|webm|m4v)$/i));
|
|
729
|
+
const videos = mediaFiles.filter((f) => f.match(/\.(mp4|mov|webm|m4v)$/i));
|
|
730
|
+
const imageMarkdown = images.map((f) => ``).join("\n\n");
|
|
731
|
+
const videoMarkdown = videos.map((f) => `<video src="/media/${paddedId}/${f}" controls></video>`).join("\n\n");
|
|
732
|
+
const mediaMarkdown = [imageMarkdown, videoMarkdown].filter(Boolean).join("\n\n");
|
|
733
|
+
if (mediaMarkdown) {
|
|
734
|
+
finalBody = finalBody + "\n\n" + mediaMarkdown;
|
|
731
735
|
}
|
|
732
736
|
}
|
|
733
737
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -668,9 +668,13 @@ async function processPost(post, options, exportDir) {
|
|
|
668
668
|
}
|
|
669
669
|
}
|
|
670
670
|
if (mediaFiles.length > 0) {
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
671
|
+
const images = mediaFiles.filter((f) => !f.match(/\.(mp4|mov|webm|m4v)$/i));
|
|
672
|
+
const videos = mediaFiles.filter((f) => f.match(/\.(mp4|mov|webm|m4v)$/i));
|
|
673
|
+
const imageMarkdown = images.map((f) => ``).join("\n\n");
|
|
674
|
+
const videoMarkdown = videos.map((f) => `<video src="/media/${paddedId}/${f}" controls></video>`).join("\n\n");
|
|
675
|
+
const mediaMarkdown = [imageMarkdown, videoMarkdown].filter(Boolean).join("\n\n");
|
|
676
|
+
if (mediaMarkdown) {
|
|
677
|
+
finalBody = finalBody + "\n\n" + mediaMarkdown;
|
|
674
678
|
}
|
|
675
679
|
}
|
|
676
680
|
}
|