-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
425 lines (372 loc) · 23.2 KB
/
index.js
File metadata and controls
425 lines (372 loc) · 23.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
// Подключение библиотеки, для получения доступа к переменной окружения
require('dotenv').config();
// Импорт классов из библиотеки Grammy
// Класс Bot - главный класс, необходимый для разработки
// Также добавляю классы для работы с обработкой ошибок. GrammyError и HttpError
const {Bot, GrammyError, HttpError} = require('grammy');
// Создаем экземпляр бота. передаем ему название файла и название переменной с ключом
//const bot = new Bot(process.env.bot_api_key)
// Переменные логики:
let waiting_file_status = 0
/*
Или же, вариант чтения ключа из файла.
Проект будет загружен на гитхаб, поэтому нельзя в нем оставить ключ
*/
// Подключение файловой системы, для работы с файлами
const fileSys = require('fs');
const { error } = require('console');
// Переменная, хранящая путь к файлу с ключом:
//const pathToFile = '../js_tg_bot_token.txt';
// Чтение файла, сохранение текста в переменную. Параметры после пути необязательны.
// Второй параметр - кодировка файла
// Третий параметр - флаг (чтение/чтение и запись/тп).
// r - открывает файл для чтения. Возникает ошибка, если файла нет.
// а - открыть файл для добавления данных. Файл создается, если он не существует
//const data = fileSys.readFileSync(pathToFile, {encoding: 'utf8', flag: 'r'});
//stringFromFile = stringFromFile.replace(/\r\n/q, '1'); // Удаление переносов строк и каретки. Особо не нужно, но вдруг.
const bot = new Bot(process.env.bot_api_key);
// Главная функция бота - это отвечать на сообщения и команды пользователя
// В Grammy, ответ на сообщения и команды, реализован с помощью добавления обработчиков (слушателей определенных событий)
/*
Виды обработчиков в grammy:
1) Ответ на команды (bot.command).
Команда - любая последовательность знаков без пробелов после / (только английские буквы)
*/
// Также, пользователю можно выдать список доступных команд:
bot.api.setMyCommands([
{
command: 'start',
description: 'Запуск бота',
},
{
command: 'currencies',
description: 'Вывод списка валют',
},
{
command: 'currencies_save',
description: 'Сохранение списка валют на сервере',
},
{
command: 'download_html',
description: 'Сохранить html страницу сайта банка на сервер',
},
/* Команды через кэмел или снейк кейс, не работают в меню
{
command: 'hello_Friend',
description: 'Приветствие',
},
*/
// Благодаря этой инструкции, в левом нижнем углу бота в тг, появилась кнопка со списком команд
]);
// Если просто написать bot.start();, бот запустится, но никак не будет реагировать на сообщения
// Чтобы бот реагировал, нужно добавить соответствующий код на команду start и любое сообщение пользователя
// Для добавления обработчика команды, на bot нужно повесить слушателя:
// Первый каргумент - название команды. Второй - запускаемая колбек функция. Она должна быть асинхронной.
// ctx (context) - отдельный объект, который например, позволяет отвечать пользователю с помощью метода reply
bot.command('start', async (ctx) => {
await ctx.reply('Рад приветствовать! Это бот для сохранения закладок. Пиши сюда текстовые сообщения или файлы, я сохраню их на компьютере.\nОграничение по длине текстового сообщения - 500 символов\nОграничение по размеру файла - 20мб')
});
//----------------------------------------------------------------------------------------------------------------------------------------//
// Работа с валютами
const url = "https://www.banki.ru/products/currency/cb/";
// axios для работы с запросами
const axios = require('axios');
// Сам парсер
const cheerio = require('cheerio');
// Переменная для хранения html файла
html_file = null;
// Получение файла html
async function getHtml(url) {
fetch(url)
.then(response => response.text())
.then(html => html_file = html);
}
// Функция для сохранения Html Страницы на сервере
async function downloadHtml() {
console.log('Запуск функции для скачивания файла');
var now = new Date();
const day = now.getDate();
const month = now.getMonth() + 1;
const year = now.getFullYear();
const hour = now.getHours();
const minut = now.getMinutes();
const second = now.getSeconds();
//
// Путь для сохранения скачиваемого файла
const local_file_path = `./save_html/${day}_${month}_${year}__${second}:${minut}:${hour}.html`
//
const response = await axios.get(url);
fileSys.writeFileSync(local_file_path, response.data);
console.log(`HTML сохранен в ${local_file_path}`);
}
// Функция для извлечения данных о валютах из HTML
function extractCurrencies(html) {
console.log("%cЗапуск функции извлечения курсов валют", "color: green");
const $ = cheerio.load(html);
const currencies = [];
const dolar = $('html body div.Layoutstyled__StyledRoot-sc-u5jf65-0.jTVCda div.Layoutstyled__StyledContent-sc-u5jf65-1.gmqNuN div.LayoutWrapper__sc-k10h92-0.lfqBOi div div div.GridRow__sc-1e0lykf-0.idhGrO div.GridCol__sc-n5ivvz-0.jnxKZK div div:nth-child(2) div div:nth-child(1) section div div div:nth-child(2) div div.Text__sc-vycpdy-0.gJTmbP').text().trim().replace('₽', '');
const evro = $('html body div.Layoutstyled__StyledRoot-sc-u5jf65-0.jTVCda div.Layoutstyled__StyledContent-sc-u5jf65-1.gmqNuN div.LayoutWrapper__sc-k10h92-0.lfqBOi div div div.GridRow__sc-1e0lykf-0.idhGrO div.GridCol__sc-n5ivvz-0.jnxKZK div div:nth-child(2) div div:nth-child(2) section div div div:nth-child(2) div div.Text__sc-vycpdy-0.gJTmbP').text().trim().replace('₽', '');
const real = $('html body div.Layoutstyled__StyledRoot-sc-u5jf65-0.jTVCda div.Layoutstyled__StyledContent-sc-u5jf65-1.gmqNuN div.LayoutWrapper__sc-k10h92-0.lfqBOi div div div.GridRow__sc-1e0lykf-0.idhGrO div.GridCol__sc-n5ivvz-0.jnxKZK div div:nth-child(2) div div:nth-child(3) section div div div:nth-child(2) div div.Text__sc-vycpdy-0.gJTmbP').text().trim().replace('₽', '');
const zlotu = $('html body div.Layoutstyled__StyledRoot-sc-u5jf65-0.jTVCda div.Layoutstyled__StyledContent-sc-u5jf65-1.gmqNuN div.LayoutWrapper__sc-k10h92-0.lfqBOi div div div.GridRow__sc-1e0lykf-0.idhGrO div.GridCol__sc-n5ivvz-0.jnxKZK div div:nth-child(2) div div:nth-child(4) section div div div:nth-child(2) div div.Text__sc-vycpdy-0.gJTmbP').text().trim().replace('₽', '');
const grivna = $('html body div.Layoutstyled__StyledRoot-sc-u5jf65-0.jTVCda div.Layoutstyled__StyledContent-sc-u5jf65-1.gmqNuN div.LayoutWrapper__sc-k10h92-0.lfqBOi div div div.GridRow__sc-1e0lykf-0.idhGrO div.GridCol__sc-n5ivvz-0.jnxKZK div div:nth-child(2) div div:nth-child(5) section div div div:nth-child(2) div div.Text__sc-vycpdy-0.gJTmbP').text().trim().replace('₽', '');
const lary = $('html body div.Layoutstyled__StyledRoot-sc-u5jf65-0.jTVCda div.Layoutstyled__StyledContent-sc-u5jf65-1.gmqNuN div.LayoutWrapper__sc-k10h92-0.lfqBOi div div div.GridRow__sc-1e0lykf-0.idhGrO div.GridCol__sc-n5ivvz-0.jnxKZK div div:nth-child(2) div div:nth-child(6) section div div div:nth-child(2) div div.Text__sc-vycpdy-0.gJTmbP').text().trim().replace('₽', '');
console.log('Курсы валют:');
console.log('USD:', dolar);
console.log('EUR:', evro);
console.log('BRL:', real);
console.log('PLN:', zlotu);
console.log('UAH:', grivna);
console.log('GEL:', lary);
currencies.push(['USD', dolar]);
currencies.push(['EUR', evro]);
currencies.push(['BRL', real]);
currencies.push(['PLN', zlotu]);
currencies.push(['UAH', grivna]);
currencies.push(['GEL', lary]);
return currencies;
}
bot.command('download_html', async (ctx) => {
downloadHtml();
await ctx.reply('Html страница сайта банка с акутальным курсом сохранена на сервере')
});
bot.command('currencies', async (ctx) => {
getHtml(url);
currencies = extractCurrencies(html_file)
output_string = ''
for (const el of currencies) {
output_string = output_string + '\n' + el[0] + ' ' + el[1]
}
await ctx.reply(`Вывод списка валют: \n${output_string}`)
});
bot.command('currencies_save', async (ctx) => {
getHtml(url);
currencies = extractCurrencies(html_file)
output_string = ''
for (const el of currencies) {
output_string = output_string + '\n' + el[0] + ' ' + el[1]
}
//
var now = new Date();
const day = now.getDate();
const month = now.getMonth() + 1;
const year = now.getFullYear();
const hour = now.getHours();
const minut = now.getMinutes();
const second = now.getSeconds();
//
// Путь для сохранения скачиваемого файла
const local_file_path = `./save_currencies/${day}_${month}_${year}__${second}_${minut}_${hour}.txt`
// Сохранение файла
fileSys.writeFileSync(local_file_path, output_string);
await ctx.reply('Список валют с акутальным курсом сохранен на сервере')
});
//----------------------------------------------------------------------------------------------------------------------------------------//
/*
// Также можно передать массив команд вместо одной.
bot.command(['hello', 'hi', 'helloFriend'], async (ctx) => {
await ctx.reply('Йоу! как день?')
});
*/
// Функция для скачивания файла
async function downloadFile(url, filePath, fs) {
return new Promise((resolve, reject) => {
const fileStream = fs.createWriteStream(filePath); // Создаем поток для записи файла
const https = require("https");
https.get(url, (response) => {
response.pipe(fileStream); // Передаем данные в файл
fileStream.on("finish", () => {
fileStream.close();
resolve();
});
fileStream.on("error", (err) => {
fs.unlink(filePath, () => {}); // Удаляем файл в случае ошибки
reject(err);
});
}).on("error", (err) => {
fs.unlink(filePath, () => {}); // Удаляем файл в случае ошибки
reject(err);
});
});
}
// Событие получения документа. Сохранение полученного файла в папку
bot.on(':file', async (ctx) => {
await ctx.reply("Вы отправили файл.");
// Получение размера файла в битах до его скачивания
const file_size = ctx.message.document.file_size;
ctx.reply(`Получен файл размером ${file_size} байт`);
if (file_size > 20000000) {
console.log("Получен файл больше 20мб. Отмена обработки.");
ctx.reply("Получен файл больше 20мб. Отмена обработки.");
//throw new error;
}
else {
console.log("Получен файл меньше 20 мб.")
// Переменная хранящая ид файла
const dock_id = ctx.message.document.file_id;
// Переменная хранящая имя файла
const dock_name = ctx.message.document.file_name;
// Путь??
const path = require("path");
// Переменая хранящая полученный файл
const user_dock = await ctx.api.getFile(dock_id);
// Формируем url файла для скачивания
const dock_url = `https://api.telegram.org/file/bot${bot.token}/${user_dock.file_path}`;
// Ответ пользователю
await ctx.reply("Вы отправили файл. \r\nИд файла: " + dock_id + ".\r\nИмя файла: " + dock_name);
var now = new Date();
const day = now.getDate();
const month = now.getMonth() + 1;
const year = now.getFullYear();
// Путь для сохранения скачиваемого файла
const local_file_path = `./documents/${day}_${month}_${year}`
const fileSys = require('fs');
// Создание папки для хранения документов за текцщую дату
try {
if (!fileSys.existsSync(local_file_path)) {
fileSys.mkdirSync(local_file_path);
console.log(`Создана папка для хранения файлов за текущую дату: (${day}_${month}_${year})`)
}
else {console.log(`Папка для хранения файлов за текущую дату уже существует: (${day}_${month}_${year})`);}
try {
console.log(path.join(local_file_path, dock_name));
const time = `${now.getHours()}_${now.getMinutes()}_${now.getSeconds()}_${now.getMilliseconds()}`;
await downloadFile(dock_url, path.join(local_file_path, `${ctx.message.from.username}_${time}_${dock_name}`), fileSys);
console.log(`Файл записан: ${dock_name}`);
await ctx.reply("Файл сохранен.");
}
catch {
console.log(`Ошибка записи файла`);
await ctx.reply("Ошибка сохранения файла.");
}
}
catch (err) {
console.log(`Ошибка создания папки для хранения файлов за текущую дату: ${err}`);
}
}
})
// // Неудачная попытка создать событие для сохранения файлов. Может пригодиться
// // Событие получения документа. Сохранение полученного файла в папку
// bot.on('message:document', async (ctx) => {
// await ctx.reply("Вы отправили документ.");
// // Переменная хранящая ид файла
// const dock_id = ctx.message.document.file_id;
// // Переменная хранящая имя файла
// const dock_name = ctx.message.document.file_name;
// // Переменая хранящая полученный файл
// const user_dock = await ctx.api.getFile(dock_id);
// // Еще один вариант
// const file = await ctx.getFile();
// // Формируем url файла для скачивания
// const dock_url = `https://api.telegram.org/file/bot${bot.token}/${user_dock.file_path}`;
// // Ответ пользователю
// await ctx.reply("Вы отправили файл. \r\nИд файла: " + dock_id + ".\r\nИмя файла: " + dock_name);
// // Создание папки для хранения документов за текцщую дату
// var now = new Date();
// const day = now.getDate();
// const month = now.getMonth() + 1;
// const year = now.getFullYear();
// // Путь для сохранения скачиваемого файла
// const local_file_path = `./documents/${day}_${month}_${year}`
// const fileSys = require('fs');
// try {
// if (!fileSys.existsSync(local_file_path)) {
// fileSys.mkdirSync(local_file_path);
// console.log(`Создана папка для хранения файлов за текущую дату: (${day}_${month}_${year})`)
// }
// else {console.log(`Папка для хранения файлов за текущую дату уже существует: (${day}_${month}_${year})`);}
// try {
// fileSys.writeFile(`${local_file_path}/file.txt`, file);
// console.log(`Файл записан: ${dock_name}`);
// await ctx.reply("Файл сохранен.");
// }
// catch {
// console.log(`Ошибка записи файла`);
// await ctx.reply("Ошибка сохранения файла.");
// }
// }
// catch (err) {
// console.log(`Ошибка создания папки для хранения файлов за текущую дату: ${err}`);
// }
// })
// Также, можно использовать встроенный функционал grammy для фильтрации сообщений, вместо ифов
bot.on('message:text', async (ctx) => {
let lenStr = ctx.message.text.length;
if (lenStr > 500) {
await ctx.reply('Сообщение слишком большое. Максимум 500 символов.');
if (!(e instanceof Error)) {e = new Error(e);}
}
var now = new Date();
const day = now.getDate();
const month = now.getMonth() + 1;
const year = now.getFullYear();
console.log(`Получено сообщение с текстом: ${ctx.message.text}. От: ${ctx.message.from.username}. Время: ${'Год: ' + year + ' Месяц: ' + month + ' День: ' + day + ' Время: ' + now.getHours() + '.' + now.getMinutes()}`);
// Чтение файла с заметками за текущую дату. Если файла нет, он будет создан
const fileSys = require('fs');
const pathToFile = `./notes/${day + "_" + month + "_" + year + "_Notes"}.txt`;
console.log(pathToFile);
/* Шпоргалка чтения файла в массив строк
const data = fileSys.readFileSync(pathToFile, {encoding: 'utf8', flag: 'r'});
// Переводим полученную единую строку в массив строк. Делим по разделителю строк
let dataArr = data.split('\r\n');
// Убираем пустые строки (хотя в файле их быть не должно)
dataArr = dataArr.filter(line => line.trim() !== '');
*/
// Добавление текста сообщения в файл с заметками за текцщую дату. Если файла нет, он будет создан в формате "09_02_2025_Notes.txt"
fileSys.writeFileSync(pathToFile, ctx.message.text + ' (Время: ' + now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds() + ' От: ' + ctx.message.from.username + ' id:' + ctx.message.from.id + ')' + '\r\n', {encoding: 'utf8', flag: 'a'});
// Отправка статуса получения сообщения
await ctx.reply('Сообщение получено. Добавлено в закладки за текущую дату.');
});
/* Варианты работы с событиями в grammy:
// Можно комбинировать события
bot.on('message:photo').on('message:voice', async (ctx) => {
//await ctx.reply('Сообщение получено.');
var now = new Date();
console.log(`Получено голосовое сообщение и фото: ${ctx.message.text}. Время: ${'Год: ' + now.getFullYear() + ' Месяц: ' + (now.getMonth() + 1) + ' День: ' + now.getDate() + ' Время: ' + now.getHours() + '.' + now.getMinutes()}`);
});
bot.on('message:entities:url', async (ctx) => {
//await ctx.reply('Сообщение получено.');
var now = new Date();
console.log(`Получено сообщение с сылкой: ${ctx.message.text}. Время: ${'Год: ' + now.getFullYear() + ' Месяц: ' + (now.getMonth() + 1) + ' День: ' + now.getDate() + ' Время: ' + now.getHours() + '.' + now.getMinutes()}`);
});
// Бот может отреагировать на любое сообщение, например:
bot.on('message', async (ctx) => {
if (ctx.message.text == 1) {
await ctx.reply('Первый вариант');
console.log(`Получено сообщение: ${ctx.message.text}`);
}
else if (ctx.message.text == 2) {
await ctx.reply('Второй вариант');
console.log(`Получено сообщение: ${ctx.message.text}`);
}
else {
await ctx.reply('Сообщение получено.');
console.log(`Получено сообщение: ${ctx.message.text}`);
}
});
*/
// Работа с обработкой ошибок
bot.catch((err) => {
// Создание переменной с информацией о ошибке
const ctx = err.ctx;
// Вывод сообщения о возникновении ошибки в консоль
console.error(`Error while handling update ${ctx.update.update_id}:`);
// Получение самой ошибки
const er = err.error;
// Теперь нужно проверить, какого типа эта ошибка
// Используя GrammyError и HttpError, можно понять, к какому инстансу относится ошибка
if (er instanceof GrammyError) {
// Значит, скорее всего, ошибка в запросе
//console.error("Error in request:", e.description)
}
else if (er instanceof HttpError) {
// Значит, что мы скорее всего, не можем связаться с телеграммом
console.error("Could not contract telegram:", er);
}
else {
// Значит, неизвестная ошибка
console.error("Unknown error:", er);
}
});
// Все обработчики событий должны быть описаны до строки старта бота
// Запуск бота
bot.start();