telekit-lib 2.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/PUBLISHING.md +52 -0
- package/PUBLISH_GUIDE.md +52 -0
- package/README.md +45 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +356 -0
- package/docs/index.html +675 -0
- package/examples/database_bot.ts +77 -0
- package/examples/simple_bot.ts +18 -0
- package/package.json +29 -0
package/docs/index.html
ADDED
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ru">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Документация TeleKit</title>
|
|
8
|
+
<!-- Tailwind CSS -->
|
|
9
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
10
|
+
<!-- Highlight.js for beautiful code syntax -->
|
|
11
|
+
<link rel="stylesheet"
|
|
12
|
+
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
|
13
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
14
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/typescript.min.js"></script>
|
|
15
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js"></script>
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
html {
|
|
19
|
+
scroll-behavior: smooth;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.sidebar-scroll::-webkit-scrollbar {
|
|
23
|
+
width: 6px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.sidebar-scroll::-webkit-scrollbar-track {
|
|
27
|
+
background: #f1f5f9;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.sidebar-scroll::-webkit-scrollbar-thumb {
|
|
31
|
+
background: #cbd5e1;
|
|
32
|
+
border-radius: 3px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pre {
|
|
36
|
+
margin: 0 !important;
|
|
37
|
+
border-radius: 0.5rem;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.hljs {
|
|
41
|
+
padding: 1.5rem !important;
|
|
42
|
+
background: #1e1e1e !important;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
45
|
+
|
|
46
|
+
<script>
|
|
47
|
+
document.addEventListener('DOMContentLoaded', (event) => {
|
|
48
|
+
hljs.highlightAll();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const copyIcon = `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>`;
|
|
52
|
+
const checkIcon = `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>`;
|
|
53
|
+
|
|
54
|
+
function copyCode(btn) {
|
|
55
|
+
const pre = btn.nextElementSibling;
|
|
56
|
+
const code = pre.querySelector('code').innerText;
|
|
57
|
+
|
|
58
|
+
navigator.clipboard.writeText(code).then(() => {
|
|
59
|
+
btn.innerHTML = checkIcon;
|
|
60
|
+
btn.classList.add('text-green-400');
|
|
61
|
+
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
btn.innerHTML = copyIcon;
|
|
64
|
+
btn.classList.remove('text-green-400');
|
|
65
|
+
}, 2000);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
69
|
+
</head>
|
|
70
|
+
|
|
71
|
+
<body class="bg-slate-50 text-slate-900 font-sans">
|
|
72
|
+
|
|
73
|
+
<!-- Mobile Header -->
|
|
74
|
+
<div class="md:hidden bg-white border-b border-slate-200 p-4 sticky top-0 z-20 flex justify-between items-center">
|
|
75
|
+
<h1 class="text-xl font-bold text-blue-600">TeleKit ⚡</h1>
|
|
76
|
+
<button onclick="document.getElementById('sidebar').classList.toggle('-translate-x-full')"
|
|
77
|
+
class="text-slate-600">
|
|
78
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
79
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16">
|
|
80
|
+
</path>
|
|
81
|
+
</svg>
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<!-- Sidebar -->
|
|
86
|
+
<div id="sidebar"
|
|
87
|
+
class="fixed top-0 left-0 w-72 h-full bg-white border-r border-slate-200 z-30 transform -translate-x-full md:translate-x-0 transition-transform duration-200 flex flex-col">
|
|
88
|
+
<div class="p-6 border-b border-slate-100">
|
|
89
|
+
<h1 class="text-2xl font-bold text-blue-600">TeleKit ⚡</h1>
|
|
90
|
+
<p class="text-xs text-slate-400 mt-1">v2.0.0 Enterprise Edition</p>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<nav class="flex-1 overflow-y-auto sidebar-scroll p-6 space-y-8">
|
|
94
|
+
<div>
|
|
95
|
+
<h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">Начало</h3>
|
|
96
|
+
<a href="#intro"
|
|
97
|
+
class="block py-1 text-slate-600 hover:text-blue-600 hover:translate-x-1 transition-transform">Введение</a>
|
|
98
|
+
<a href="#install"
|
|
99
|
+
class="block py-1 text-slate-600 hover:text-blue-600 hover:translate-x-1 transition-transform">Установка</a>
|
|
100
|
+
<a href="#quick-start"
|
|
101
|
+
class="block py-1 text-slate-600 hover:text-blue-600 hover:translate-x-1 transition-transform">Быстрый
|
|
102
|
+
старт</a>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div>
|
|
106
|
+
<h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">База Данных</h3>
|
|
107
|
+
<a href="#localdb"
|
|
108
|
+
class="block py-1 text-slate-600 hover:text-blue-600 hover:translate-x-1 transition-transform">LocalDB
|
|
109
|
+
(Встроенная)</a>
|
|
110
|
+
<a href="#sessions"
|
|
111
|
+
class="block py-1 text-slate-600 hover:text-blue-600 hover:translate-x-1 transition-transform">Хранилище
|
|
112
|
+
Сессий</a>
|
|
113
|
+
<a href="#postgres"
|
|
114
|
+
class="block py-1 text-slate-600 hover:text-blue-600 hover:translate-x-1 transition-transform">Подключение
|
|
115
|
+
PostgreSQL</a>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div>
|
|
119
|
+
<h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">Основные Команды</h3>
|
|
120
|
+
<a href="#handlers"
|
|
121
|
+
class="block py-1 text-slate-600 hover:text-blue-600 hover:translate-x-1 transition-transform">Обработчики</a>
|
|
122
|
+
<a href="#scenes"
|
|
123
|
+
class="block py-1 text-slate-600 hover:text-blue-600 hover:translate-x-1 transition-transform">Сцены
|
|
124
|
+
(Wizards)</a>
|
|
125
|
+
<a href="#keyboards"
|
|
126
|
+
class="block py-1 text-slate-600 hover:text-blue-600 hover:translate-x-1 transition-transform">Клавиатуры</a>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<div>
|
|
130
|
+
<h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">Справочник</h3>
|
|
131
|
+
<a href="#api-telekit"
|
|
132
|
+
class="block py-1 text-slate-600 hover:text-blue-600 hover:translate-x-1 transition-transform">API
|
|
133
|
+
Reference</a>
|
|
134
|
+
</div>
|
|
135
|
+
</nav>
|
|
136
|
+
|
|
137
|
+
<div class="p-6 border-t border-slate-100 bg-slate-50">
|
|
138
|
+
<a href="https://github.com/TaHel-UDev/telekit"
|
|
139
|
+
class="flex items-center gap-2 text-slate-600 hover:text-blue-600 font-medium">
|
|
140
|
+
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
|
141
|
+
<path
|
|
142
|
+
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
|
143
|
+
</svg>
|
|
144
|
+
GitHub
|
|
145
|
+
</a>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<!-- Main Content -->
|
|
150
|
+
<div class="ml-0 md:ml-72 p-8 max-w-5xl">
|
|
151
|
+
|
|
152
|
+
<!-- Intro Section -->
|
|
153
|
+
<header id="intro" class="mb-20 pt-10">
|
|
154
|
+
<div class="inline-block bg-blue-100 text-blue-700 px-3 py-1 rounded-full text-sm font-semibold mb-4">v2.0.0
|
|
155
|
+
Stable</div>
|
|
156
|
+
<h1 class="text-5xl md:text-6xl font-extrabold mb-6 tracking-tight text-slate-900">
|
|
157
|
+
Создавайте ботов <br>
|
|
158
|
+
<span class="bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-indigo-600">без головной
|
|
159
|
+
боли</span>
|
|
160
|
+
</h1>
|
|
161
|
+
<p class="text-xl text-slate-600 leading-relaxed max-w-2xl mb-8">
|
|
162
|
+
Профессиональный фреймворк для Telegram ботов на TypeScript.
|
|
163
|
+
Встроенная база данных, управление сессиями и сценами из коробки.
|
|
164
|
+
</p>
|
|
165
|
+
<div class="flex flex-wrap gap-4">
|
|
166
|
+
<a href="#quick-start"
|
|
167
|
+
class="bg-blue-600 text-white px-8 py-4 rounded-xl font-bold hover:bg-blue-700 transition shadow-lg shadow-blue-200">Начать
|
|
168
|
+
работу →</a>
|
|
169
|
+
<a href="#api-telekit"
|
|
170
|
+
class="bg-white border border-slate-300 text-slate-700 px-8 py-4 rounded-xl font-bold hover:bg-slate-50 transition">API
|
|
171
|
+
Справочник</a>
|
|
172
|
+
</div>
|
|
173
|
+
</header>
|
|
174
|
+
|
|
175
|
+
<hr class="border-slate-200 mb-16">
|
|
176
|
+
|
|
177
|
+
<!-- Installation -->
|
|
178
|
+
<section id="install" class="mb-20 scroll-mt-24">
|
|
179
|
+
<h2 class="text-3xl font-bold mb-6 flex items-center gap-3">
|
|
180
|
+
<span class="bg-slate-100 p-2 rounded-lg text-2xl">📦</span> Установка
|
|
181
|
+
</h2>
|
|
182
|
+
<div class="relative group my-4 rounded-lg overflow-hidden shadow-lg border border-slate-800">
|
|
183
|
+
<button onclick="copyCode(this)"
|
|
184
|
+
class="absolute top-3 right-3 p-1.5 bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white rounded-lg opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
185
|
+
title="Copy code">
|
|
186
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
187
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
188
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
|
189
|
+
</path>
|
|
190
|
+
</svg>
|
|
191
|
+
</button>
|
|
192
|
+
<pre><code class="language-bash">npm install telekit-lib</code></pre>
|
|
193
|
+
</div>
|
|
194
|
+
</section>
|
|
195
|
+
|
|
196
|
+
<!-- Quick Start -->
|
|
197
|
+
<section id="quick-start" class="mb-20 scroll-mt-24">
|
|
198
|
+
<h2 class="text-3xl font-bold mb-6 flex items-center gap-3">
|
|
199
|
+
<span class="bg-slate-100 p-2 rounded-lg text-2xl">⚡</span> Быстрый старт
|
|
200
|
+
</h2>
|
|
201
|
+
<div class="relative group my-4 rounded-lg overflow-hidden shadow-lg border border-slate-800">
|
|
202
|
+
<button onclick="copyCode(this)"
|
|
203
|
+
class="absolute top-3 right-3 p-1.5 bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white rounded-lg opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
204
|
+
title="Copy code">
|
|
205
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
206
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
207
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
|
208
|
+
</path>
|
|
209
|
+
</svg>
|
|
210
|
+
</button>
|
|
211
|
+
<pre><code class="language-typescript">import { TeleKit } from 'telekit-lib';
|
|
212
|
+
|
|
213
|
+
// 1. Инициализация
|
|
214
|
+
const bot = new TeleKit('YOUR_BOT_TOKEN');
|
|
215
|
+
|
|
216
|
+
// 2. Простая команда
|
|
217
|
+
bot.command('start', async (ctx) => {
|
|
218
|
+
await ctx.reply('Привет! Бот успешно запущен.');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// 3. Запуск
|
|
222
|
+
bot.start();</code></pre>
|
|
223
|
+
</div>
|
|
224
|
+
</section>
|
|
225
|
+
|
|
226
|
+
<!-- Command Menu -->
|
|
227
|
+
<section id="commands-menu" class="mb-20 scroll-mt-24">
|
|
228
|
+
<h2 class="text-3xl font-bold mb-6 flex items-center gap-3">
|
|
229
|
+
<span class="bg-slate-100 p-2 rounded-lg text-2xl">📋</span> Меню команд
|
|
230
|
+
</h2>
|
|
231
|
+
<p class="text-slate-600 mb-6">Чтобы у пользователей появилась кнопка "Меню" со списком команд,
|
|
232
|
+
зарегистрируйте их один раз при старте:</p>
|
|
233
|
+
|
|
234
|
+
<div class="relative group my-4 rounded-lg overflow-hidden shadow-lg border border-slate-800">
|
|
235
|
+
<button onclick="copyCode(this)"
|
|
236
|
+
class="absolute top-3 right-3 p-1.5 bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white rounded-lg opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
237
|
+
title="Copy code"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
238
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
239
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
|
240
|
+
</path>
|
|
241
|
+
</svg></button>
|
|
242
|
+
<pre><code class="language-typescript">await bot.setCommands([
|
|
243
|
+
{ command: 'start', description: 'Запустить бота' },
|
|
244
|
+
{ command: 'help', description: 'Помощь' },
|
|
245
|
+
{ command: 'settings', description: 'Настройки' }
|
|
246
|
+
]);</code></pre>
|
|
247
|
+
</div>
|
|
248
|
+
</section>
|
|
249
|
+
|
|
250
|
+
<!-- LocalDB -->
|
|
251
|
+
<section id="localdb" class="mb-20 scroll-mt-24">
|
|
252
|
+
<h2 class="text-3xl font-bold mb-6 flex items-center gap-3">
|
|
253
|
+
<span class="bg-slate-100 p-2 rounded-lg text-2xl">📂</span> LocalDB (Прототипирование)
|
|
254
|
+
</h2>
|
|
255
|
+
|
|
256
|
+
<div class="bg-amber-50 border border-amber-200 text-amber-900 p-4 rounded-lg mb-6 text-sm">
|
|
257
|
+
<strong>⚠️ Для прототипов:</strong> LocalDB загружает весь JSON-файл в память.
|
|
258
|
+
Это удобно для старта, но при большой нагрузке используйте <a href="#postgres"
|
|
259
|
+
class="underline hover:text-amber-700">PostgreSQL</a> или Redis.
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<p class="text-slate-600 mb-6">
|
|
263
|
+
<code>LocalDB</code> — это имитация базы данных (похожа на массивы JS), которая сохраняет данные в файл.
|
|
264
|
+
Отлично подходит для хранения списка товаров, настроек или задач в небольших ботах.
|
|
265
|
+
</p>
|
|
266
|
+
|
|
267
|
+
<h3 class="text-xl font-bold mb-3 text-slate-800">Пример использования</h3>
|
|
268
|
+
<div class="relative group my-4 rounded-lg overflow-hidden shadow-lg border border-slate-800">
|
|
269
|
+
<button onclick="copyCode(this)"
|
|
270
|
+
class="absolute top-3 right-3 p-1.5 bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white rounded-lg opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
271
|
+
title="Copy code">
|
|
272
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
273
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
274
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
|
275
|
+
</path>
|
|
276
|
+
</svg>
|
|
277
|
+
</button>
|
|
278
|
+
<pre><code class="language-typescript">import { LocalDB } from 'telekit-lib';
|
|
279
|
+
|
|
280
|
+
// 1. Опишите тип данных
|
|
281
|
+
interface Product {
|
|
282
|
+
id: number;
|
|
283
|
+
name: string;
|
|
284
|
+
price: number;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 2. Создайте экземпляр (укажите имя файла)
|
|
288
|
+
const products = new LocalDB<Product>('products.json');
|
|
289
|
+
|
|
290
|
+
// --- Основные операции ---
|
|
291
|
+
|
|
292
|
+
// Добавить запись
|
|
293
|
+
await products.push({ id: 1, name: 'iPhone 15', price: 999 });
|
|
294
|
+
|
|
295
|
+
// Найти одну запись
|
|
296
|
+
const item = await products.findOne(p => p.id === 1);
|
|
297
|
+
if (item) console.log(item.name);
|
|
298
|
+
|
|
299
|
+
// Найти все записи по условию
|
|
300
|
+
const cheapItems = await products.find(p => p.price < 1000);
|
|
301
|
+
|
|
302
|
+
// Обновить запись
|
|
303
|
+
await products.update(
|
|
304
|
+
p => p.id === 1, // Условие поиска
|
|
305
|
+
{ price: 899 } // Новые данные
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
// Удалить запись
|
|
309
|
+
await products.delete(p => p.id === 1);</code></pre>
|
|
310
|
+
</div>
|
|
311
|
+
</section>
|
|
312
|
+
|
|
313
|
+
<!-- Sessions -->
|
|
314
|
+
<section id="sessions" class="mb-20 scroll-mt-24">
|
|
315
|
+
<h2 class="text-3xl font-bold mb-6 flex items-center gap-3">
|
|
316
|
+
<span class="bg-slate-100 p-2 rounded-lg text-2xl">💾</span> Хранилище Сессий (FileStore)
|
|
317
|
+
</h2>
|
|
318
|
+
<p class="text-slate-600 mb-6">Чтобы данные пользователя не исчезали после перезагрузки бота, используйте
|
|
319
|
+
<code>FileStore</code>.
|
|
320
|
+
</p>
|
|
321
|
+
|
|
322
|
+
<div class="relative group my-4 rounded-lg overflow-hidden shadow-lg border border-slate-800">
|
|
323
|
+
<button onclick="copyCode(this)"
|
|
324
|
+
class="absolute top-3 right-3 p-1.5 bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white rounded-lg opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
325
|
+
title="Copy code">
|
|
326
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
327
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
328
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
|
329
|
+
</path>
|
|
330
|
+
</svg>
|
|
331
|
+
</button>
|
|
332
|
+
<pre><code class="language-typescript">import { TeleKit, FileStore } from 'telekit-lib';
|
|
333
|
+
|
|
334
|
+
// Сессии будут сохраняться в 'sessions.json'
|
|
335
|
+
const bot = new TeleKit('TOKEN', {
|
|
336
|
+
store: new FileStore('sessions.json')
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
bot.onText('Имя', (ctx) => {
|
|
340
|
+
ctx.session.name = 'Алекс';
|
|
341
|
+
ctx.reply('Запомнил!');
|
|
342
|
+
});</code></pre>
|
|
343
|
+
</div>
|
|
344
|
+
</section>
|
|
345
|
+
|
|
346
|
+
<!-- Postgres DB -->
|
|
347
|
+
<section id="postgres" class="mb-20 scroll-mt-24">
|
|
348
|
+
<h2 class="text-3xl font-bold mb-6 flex items-center gap-3">
|
|
349
|
+
<span class="bg-slate-100 p-2 rounded-lg text-2xl">🐘</span> Подключение PostgreSQL
|
|
350
|
+
</h2>
|
|
351
|
+
<p class="text-slate-600 mb-6">Вы можете хранить сессии пользователей в PostgreSQL. А также использовать
|
|
352
|
+
базу данных для бизнес-логики бота.</p>
|
|
353
|
+
|
|
354
|
+
<h3 class="text-xl font-bold mb-3 text-slate-800">1. Хранение сессий (Session Store)</h3>
|
|
355
|
+
<div class="relative group my-4 rounded-lg overflow-hidden shadow-lg border border-slate-800">
|
|
356
|
+
<button onclick="copyCode(this)"
|
|
357
|
+
class="absolute top-3 right-3 p-1.5 bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white rounded-lg opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
358
|
+
title="Copy code">
|
|
359
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
360
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
361
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
|
362
|
+
</path>
|
|
363
|
+
</svg>
|
|
364
|
+
</button>
|
|
365
|
+
<pre><code class="language-typescript">import { ISessionStore } from 'telekit-lib';
|
|
366
|
+
import { Client } from 'pg';
|
|
367
|
+
|
|
368
|
+
export class PostgresStore implements ISessionStore {
|
|
369
|
+
private client: Client;
|
|
370
|
+
|
|
371
|
+
constructor(connectionString: string) {
|
|
372
|
+
this.client = new Client({ connectionString });
|
|
373
|
+
this.client.connect();
|
|
374
|
+
// Убедитесь, что таблица sessions существует
|
|
375
|
+
this.client.query(`CREATE TABLE IF NOT EXISTS sessions (
|
|
376
|
+
key TEXT PRIMARY KEY,
|
|
377
|
+
value JSONB
|
|
378
|
+
)`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async get(key: string) {
|
|
382
|
+
const res = await this.client.query(
|
|
383
|
+
'SELECT value FROM sessions WHERE key = $1',
|
|
384
|
+
[key]
|
|
385
|
+
);
|
|
386
|
+
return res.rows[0]?.value || null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async set(key: string, value: any) {
|
|
390
|
+
await this.client.query(
|
|
391
|
+
`INSERT INTO sessions (key, value) VALUES ($1, $2)
|
|
392
|
+
ON CONFLICT (key) DO UPDATE SET value = $2`,
|
|
393
|
+
[key, value]
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async delete(key: string) {
|
|
398
|
+
await this.client.query('DELETE FROM sessions WHERE key = $1', [key]);
|
|
399
|
+
}
|
|
400
|
+
}</code></pre>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<h3 class="text-xl font-bold mb-3 text-slate-800 mt-8">2. Использование БД в командах (Бизнес-логика)</h3>
|
|
404
|
+
<p class="text-slate-600 mb-4">Пример того, как выполнять SQL-запросы прямо внутри команд бота.</p>
|
|
405
|
+
<div class="relative group my-4 rounded-lg overflow-hidden shadow-lg border border-slate-800">
|
|
406
|
+
<button onclick="copyCode(this)"
|
|
407
|
+
class="absolute top-3 right-3 p-1.5 bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white rounded-lg opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
408
|
+
title="Copy code">
|
|
409
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
410
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
411
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
|
412
|
+
</path>
|
|
413
|
+
</svg>
|
|
414
|
+
</button>
|
|
415
|
+
<pre><code class="language-typescript">import { TeleKit } from 'telekit-lib';
|
|
416
|
+
import { Client } from 'pg';
|
|
417
|
+
|
|
418
|
+
// 1. Настройка подключения
|
|
419
|
+
const db = new Client({ connectionString: 'postgresql://user:pass@localhost:5432/mydb' });
|
|
420
|
+
await db.connect();
|
|
421
|
+
|
|
422
|
+
const bot = new TeleKit('TOKEN');
|
|
423
|
+
|
|
424
|
+
// 2. Команда отображения топа пользователей из БД
|
|
425
|
+
bot.command('top', async (ctx) => {
|
|
426
|
+
try {
|
|
427
|
+
// Прямой SQL запрос
|
|
428
|
+
const res = await db.query(
|
|
429
|
+
'SELECT username, score FROM users ORDER BY score DESC LIMIT 5'
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
if (res.rows.length === 0) {
|
|
433
|
+
return ctx.reply('Список пуст.');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const text = res.rows
|
|
437
|
+
.map((u, i) => `${i + 1}. ${u.username} — ${u.score} pts`)
|
|
438
|
+
.join('\n');
|
|
439
|
+
|
|
440
|
+
await ctx.reply(`🏆 Таблица лидеров:\n${text}`);
|
|
441
|
+
} catch (err) {
|
|
442
|
+
console.error(err);
|
|
443
|
+
ctx.reply('Ошибка при получении данных.');
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
bot.start();</code></pre>
|
|
448
|
+
</div>
|
|
449
|
+
</section>
|
|
450
|
+
|
|
451
|
+
<!-- Handlers -->
|
|
452
|
+
<section id="handlers" class="mb-20 scroll-mt-24">
|
|
453
|
+
<h2 class="text-3xl font-bold mb-6 flex items-center gap-3">
|
|
454
|
+
<span class="bg-slate-100 p-2 rounded-lg text-2xl">🎮</span> Обработчики (Handlers)
|
|
455
|
+
</h2>
|
|
456
|
+
<div class="relative group my-4 rounded-lg overflow-hidden shadow-lg border border-slate-800">
|
|
457
|
+
<button onclick="copyCode(this)"
|
|
458
|
+
class="absolute top-3 right-3 p-1.5 bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white rounded-lg opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
459
|
+
title="Copy code">
|
|
460
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
461
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
462
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
|
463
|
+
</path>
|
|
464
|
+
</svg>
|
|
465
|
+
</button>
|
|
466
|
+
<pre><code class="language-typescript">// Точное совпадение
|
|
467
|
+
bot.onText('Меню', (ctx) => ctx.reply('Вот меню...'));
|
|
468
|
+
|
|
469
|
+
// Регулярное выражение (RegExp)
|
|
470
|
+
bot.onText(/^Привет (.+)$/, (ctx) => {
|
|
471
|
+
const name = ctx.update.message.text.match(/^Привет (.+)$/)[1];
|
|
472
|
+
ctx.reply(`Здравствуй, ${name}!`);
|
|
473
|
+
});</code></pre>
|
|
474
|
+
</div>
|
|
475
|
+
</section>
|
|
476
|
+
|
|
477
|
+
<!-- Scenes -->
|
|
478
|
+
<section id="scenes" class="mb-20 scroll-mt-24">
|
|
479
|
+
<h2 class="text-3xl font-bold mb-6 flex items-center gap-3">
|
|
480
|
+
<span class="bg-slate-100 p-2 rounded-lg text-2xl">🎬</span> Сцены (Wizards)
|
|
481
|
+
</h2>
|
|
482
|
+
<p class="text-slate-600 mb-6">Сцены нужны для пошагового сбора данных (например, анкета).</p>
|
|
483
|
+
<div class="relative group my-4 rounded-lg overflow-hidden shadow-lg border border-slate-800">
|
|
484
|
+
<button onclick="copyCode(this)"
|
|
485
|
+
class="absolute top-3 right-3 p-1.5 bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white rounded-lg opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
486
|
+
title="Copy code">
|
|
487
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
488
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
489
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
|
490
|
+
</path>
|
|
491
|
+
</svg>
|
|
492
|
+
</button>
|
|
493
|
+
<pre><code class="language-typescript">import { Scene } from 'telekit-lib';
|
|
494
|
+
|
|
495
|
+
// Создаем сцену 'register' с тремя шагами
|
|
496
|
+
const registerScene = new Scene('register',
|
|
497
|
+
// Шаг 1
|
|
498
|
+
(ctx) => {
|
|
499
|
+
ctx.reply('Введите ваше имя:');
|
|
500
|
+
ctx.next(); // Переход к следующему шагу
|
|
501
|
+
},
|
|
502
|
+
// Шаг 2
|
|
503
|
+
(ctx) => {
|
|
504
|
+
ctx.session.name = ctx.text;
|
|
505
|
+
ctx.reply('Сколько вам лет?');
|
|
506
|
+
ctx.next();
|
|
507
|
+
},
|
|
508
|
+
// Шаг 3
|
|
509
|
+
(ctx) => {
|
|
510
|
+
const age = ctx.text;
|
|
511
|
+
ctx.reply(`Спасибо, ${ctx.session.name}! Вам ${age} лет.`);
|
|
512
|
+
ctx.leave(); // Выход из сцены
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
bot.addScene(registerScene);
|
|
517
|
+
|
|
518
|
+
// Команда для входа в сцену
|
|
519
|
+
bot.command('reg', (ctx) => ctx.enter('register'));</code></pre>
|
|
520
|
+
</div>
|
|
521
|
+
</section>
|
|
522
|
+
|
|
523
|
+
<!-- Keyboards -->
|
|
524
|
+
<section id="keyboards" class="mb-20 scroll-mt-24">
|
|
525
|
+
<h2 class="text-3xl font-bold mb-6 flex items-center gap-3">
|
|
526
|
+
<span class="bg-slate-100 p-2 rounded-lg text-2xl">🎹</span> Клавиатуры
|
|
527
|
+
</h2>
|
|
528
|
+
|
|
529
|
+
<h3 class="text-xl font-bold mb-3 text-slate-800">1. Обычные кнопки (Reply)</h3>
|
|
530
|
+
<p class="text-slate-600 mb-4">Появляются под полем ввода текста. Удобны для главных меню.</p>
|
|
531
|
+
<div class="relative group my-4 rounded-lg overflow-hidden shadow-lg border border-slate-800">
|
|
532
|
+
<button onclick="copyCode(this)"
|
|
533
|
+
class="absolute top-3 right-3 p-1.5 bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white rounded-lg opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
534
|
+
title="Copy code"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
535
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
536
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
|
537
|
+
</path>
|
|
538
|
+
</svg></button>
|
|
539
|
+
<pre><code class="language-typescript">import { Keyboard } from 'telekit-lib';
|
|
540
|
+
|
|
541
|
+
bot.command('menu', (ctx) => {
|
|
542
|
+
ctx.reply('Выберите действие:', {
|
|
543
|
+
reply_markup: Keyboard.reply([
|
|
544
|
+
['📦 Товары', '🛒 Корзина'], // Ряд 1
|
|
545
|
+
['⚙️ Настройки'] // Ряд 2
|
|
546
|
+
])
|
|
547
|
+
});
|
|
548
|
+
});</code></pre>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
<h3 class="text-xl font-bold mb-3 text-slate-800 mt-8">2. Инлайн кнопки (Inline)</h3>
|
|
552
|
+
<p class="text-slate-600 mb-4">Прикрепляются к сообщению. У каждой кнопки есть <code>callback_data</code>
|
|
553
|
+
(ID действия) или <code>url</code> (ссылка).</p>
|
|
554
|
+
<div class="relative group my-4 rounded-lg overflow-hidden shadow-lg border border-slate-800">
|
|
555
|
+
<button onclick="copyCode(this)"
|
|
556
|
+
class="absolute top-3 right-3 p-1.5 bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white rounded-lg opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
557
|
+
title="Copy code"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
558
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
559
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
|
560
|
+
</path>
|
|
561
|
+
</svg></button>
|
|
562
|
+
<pre><code class="language-typescript">bot.command('help', (ctx) => {
|
|
563
|
+
ctx.reply('Чем помочь?', {
|
|
564
|
+
reply_markup: Keyboard.inline([
|
|
565
|
+
// data: 'promo' - это ID кнопки
|
|
566
|
+
[Keyboard.callback('🔥 Акции', 'promo'), Keyboard.callback('ℹ️ О нас', 'about')],
|
|
567
|
+
[Keyboard.url('🌐 Наш сайт', 'https://example.com')]
|
|
568
|
+
])
|
|
569
|
+
});
|
|
570
|
+
});</code></pre>
|
|
571
|
+
</div>
|
|
572
|
+
|
|
573
|
+
<h3 class="text-xl font-bold mb-3 text-slate-800 mt-8">3. Обработка нажатий (Callbacks)</h3>
|
|
574
|
+
<p class="text-slate-600 mb-4">Когда пользователь нажимает Инлайн-кнопку, срабатывает
|
|
575
|
+
<code>bot.onCallback</code>.
|
|
576
|
+
</p>
|
|
577
|
+
<div class="relative group my-4 rounded-lg overflow-hidden shadow-lg border border-slate-800">
|
|
578
|
+
<button onclick="copyCode(this)"
|
|
579
|
+
class="absolute top-3 right-3 p-1.5 bg-white/5 hover:bg-white/10 text-slate-400 hover:text-white rounded-lg opacity-0 group-hover:opacity-100 transition-all z-10"
|
|
580
|
+
title="Copy code"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
581
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
582
|
+
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
|
583
|
+
</path>
|
|
584
|
+
</svg></button>
|
|
585
|
+
<pre><code class="language-typescript">// Обработка кнопки 'promo'
|
|
586
|
+
bot.onCallback('promo', (ctx) => {
|
|
587
|
+
ctx.answerCallback(); // Скрыть часики загрузки
|
|
588
|
+
ctx.reply('Сегодня скидка 50% на всё!');
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// Обработка по шаблону (RegExp)
|
|
592
|
+
// Например, для кнопок 'item_5', 'item_10'
|
|
593
|
+
bot.onCallback(/^item_(\d+)$/, (ctx) => {
|
|
594
|
+
const itemId = ctx.callbackData.match(/^item_(\d+)$/)[1];
|
|
595
|
+
ctx.answerCallback(`Выбран товар #${itemId}`); // Показать всплывашку
|
|
596
|
+
ctx.reply(`Информация о товаре ${itemId}...`);
|
|
597
|
+
});</code></pre>
|
|
598
|
+
</div>
|
|
599
|
+
</section>
|
|
600
|
+
|
|
601
|
+
<!-- API Reference -->
|
|
602
|
+
<section id="api-telekit" class="mb-20 scroll-mt-24">
|
|
603
|
+
<h2 class="text-3xl font-bold mb-6 flex items-center gap-3">
|
|
604
|
+
<span class="bg-slate-100 p-2 rounded-lg text-2xl">📚</span> API Справочник
|
|
605
|
+
</h2>
|
|
606
|
+
|
|
607
|
+
<div class="grid md:grid-cols-2 gap-8">
|
|
608
|
+
<!-- Context Methods -->
|
|
609
|
+
<div>
|
|
610
|
+
<h3 class="text-xl font-bold mb-4 text-slate-800 border-b pb-2">Context (ctx)</h3>
|
|
611
|
+
<ul class="space-y-4">
|
|
612
|
+
<li>
|
|
613
|
+
<code
|
|
614
|
+
class="text-blue-600 font-bold bg-blue-50 px-2 py-1 rounded">ctx.reply(text, extra?)</code>
|
|
615
|
+
<p class="text-sm text-slate-600 mt-1">Отправить сообщение.</p>
|
|
616
|
+
</li>
|
|
617
|
+
<li>
|
|
618
|
+
<code
|
|
619
|
+
class="text-blue-600 font-bold bg-blue-50 px-2 py-1 rounded">ctx.answerCallback(text?, alert?)</code>
|
|
620
|
+
<p class="text-sm text-slate-600 mt-1">Ответить на нажатие кнопки (скрыть часики).</p>
|
|
621
|
+
</li>
|
|
622
|
+
<li>
|
|
623
|
+
<code
|
|
624
|
+
class="text-blue-600 font-bold bg-blue-50 px-2 py-1 rounded">ctx.editMessageText(text, extra?)</code>
|
|
625
|
+
<p class="text-sm text-slate-600 mt-1">Изменить текст текущего сообщения.</p>
|
|
626
|
+
</li>
|
|
627
|
+
<li>
|
|
628
|
+
<code
|
|
629
|
+
class="text-blue-600 font-bold bg-blue-50 px-2 py-1 rounded">ctx.deleteMessage()</code>
|
|
630
|
+
<p class="text-sm text-slate-600 mt-1">Удалить текущее сообщение.</p>
|
|
631
|
+
</li>
|
|
632
|
+
<li>
|
|
633
|
+
<code class="text-blue-600 font-bold bg-blue-50 px-2 py-1 rounded">ctx.session</code>
|
|
634
|
+
<p class="text-sm text-slate-600 mt-1">Объект сессии пользователя.</p>
|
|
635
|
+
</li>
|
|
636
|
+
<li>
|
|
637
|
+
<code class="text-blue-600 font-bold bg-blue-50 px-2 py-1 rounded">ctx.enter(sceneId)</code>
|
|
638
|
+
<p class="text-sm text-slate-600 mt-1">Войти в сцену.</p>
|
|
639
|
+
</li>
|
|
640
|
+
</ul>
|
|
641
|
+
</div>
|
|
642
|
+
|
|
643
|
+
<!-- TeleKit Methods -->
|
|
644
|
+
<div>
|
|
645
|
+
<h3 class="text-xl font-bold mb-4 text-slate-800 border-b pb-2">TeleKit (bot)</h3>
|
|
646
|
+
<ul class="space-y-4">
|
|
647
|
+
<li>
|
|
648
|
+
<code class="text-purple-600 font-bold bg-purple-50 px-2 py-1 rounded">bot.start()</code>
|
|
649
|
+
<p class="text-sm text-slate-600 mt-1">Запустить бота.</p>
|
|
650
|
+
</li>
|
|
651
|
+
<li>
|
|
652
|
+
<code
|
|
653
|
+
class="text-purple-600 font-bold bg-purple-50 px-2 py-1 rounded">bot.command(cmd, handler)</code>
|
|
654
|
+
<p class="text-sm text-slate-600 mt-1">Подписка на команду (/start).</p>
|
|
655
|
+
</li>
|
|
656
|
+
<li>
|
|
657
|
+
<code
|
|
658
|
+
class="text-purple-600 font-bold bg-purple-50 px-2 py-1 rounded">bot.onText(trigger, handler)</code>
|
|
659
|
+
<p class="text-sm text-slate-600 mt-1">Подписка на текст (строка или RegExp).</p>
|
|
660
|
+
</li>
|
|
661
|
+
<li>
|
|
662
|
+
<code
|
|
663
|
+
class="text-purple-600 font-bold bg-purple-50 px-2 py-1 rounded">bot.onCallback(trigger, handler)</code>
|
|
664
|
+
<p class="text-sm text-slate-600 mt-1">Подписка на инлайн-кнопку.</p>
|
|
665
|
+
</li>
|
|
666
|
+
</ul>
|
|
667
|
+
</div>
|
|
668
|
+
</div>
|
|
669
|
+
</section>
|
|
670
|
+
|
|
671
|
+
</div>
|
|
672
|
+
|
|
673
|
+
</body>
|
|
674
|
+
|
|
675
|
+
</html>
|