-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
302 lines (262 loc) · 11.1 KB
/
index.js
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
// Подкючаем пакет для работы с API Telegram
const TelegramBot = require('node-telegram-bot-api')
// process.env - это переменные окружения
// Устанавливаем адрес сервера, где запущено приложение
const url = process.env.APP_URL || 'https://edu-telegrambot.herokuapp.com/'
// Устанавливаем токен, который выдавал нам бот.
const token = process.env.TOKEN || require('./token.js')
// Включить опрос сервера
const bot = new TelegramBot(
token,
// Делаем проверку, если токен приходит с сервера, значит приложение запущено удаленно, и тогда мы меняем конфигурацию запуска
process.env.TOKEN
? {
webHook: {
// Порт на котором запущено наше приложение
port: process.env.PORT || 8000,
},
}
: {
polling: true,
}
)
// Передаем нашему боту хук, если запус с удаленного сервера
process.env.TOKEN && bot.setWebHook(`${url}/bot${token}`)
// query - переменная запроса, где мы храним данные запросов для всех пользователей
let query = {}
// индекс последнейго вопроса
const lastIndex = 4
// Максимальное число элементов для допольнительной загрузки
// Больше нельзя, т.к. телеграм ограничивает
const maxElements = 40
// Ассинхронная функция, ожидаем пока получим данные (переменная data) для вопросов
require('./src/getInfoForButton.js')(function(data) {
// Функция выводит сообщение по команде /help
bot.onText(/\/help/, function(msg, match) {
chatId = msg.from.id
bot.sendMessage(
chatId,
`📧 Если у Вас есть вопросы и предложения, <a href="https://telegram.me/Trickster22">свяжитесь со мной</a>`,
{
parse_mode: 'html',
}
)
})
// Запускает процесс, при вводе пользователя команды /start
bot.onText(/\/start/, function(msg, match) {
// Переменная msg содержит инфомацию о получателе и отправителе приходит с сервера, пример:
// const msg = {
// message_id: 308,
// from: {
// id: 144755140,
// is_bot: false,
// first_name: 'Alexander',
// last_name: 'Shtykov',
// username: 'shtbik',
// language_code: 'ru-RU',
// },
// chatId: {
// id: 144755140,
// first_name: 'Alexander',
// last_name: 'Shtykov',
// username: 'shtbik',
// type: 'private',
// },
// date: 1517752501,
// text: '/start',
// entities: [{ offset: 0, length: 6, type: 'bot_command' }],
// }
// Сбрасываем значения от предыдущих владельцев
clearUserData(msg)
// По умолчанию индекс вопроса 0, выводим кнопки с предметами
newQuestion(msg, 0)
})
// Функция берет необходимые данные для кнопок по индексу
function getQuestion(indexQuestion) {
// Данные (data) приходят из файла ./src/getInfoForButton.js
// indexQuestion - хранится в каждой кнопке
return data[indexQuestion]
}
// Функция вывода вопроса в чат с отрпавителем
function newQuestion(msg, indexQuestion) {
// Получаем нужный вопрос по индексу
const question = getQuestion(indexQuestion)
// Получаем заголовок вопроса
const text = question.title
// составляем архитектуру вывода кнопок для ответа
const options = {
reply_markup: JSON.stringify({
inline_keyboard: question.buttons,
parse_mode: 'Markdown',
}),
}
// Получаем id чата, куда отправить сообщение
chatId = msg.from.id
// Отправляем сообщение в чат с параметрами
bot.sendMessage(chatId, text, options)
}
// Выводит олимпиады по собранным критериям
// url - для запроса
// cnow - счетчик для допольнительной подгрузки
function getOlympiadsInfo(url, msg, cnow = undefined) {
require('./src/getInfoAboutOlimpiades.js')(url, function(data, commonCount = ['']) {
// Регулярное выражение для получения общего числа олимпиад
const countOlmp = commonCount[0].replace(/\D*\s+\S+/g, '') || 0
// Получаем id чата
chatId = msg.from.id
// Добавляем кнопки, после вывода олимпиад
function additionalButton(loadmoreFlag) {
const addButton = [{ text: '↪ Начать заново', callback_data: 'action_repeat' }]
// Если счетчик cnow переполнен или countOlmp не достаточно большой
// или loadmoreFlag = false, то не выводим доп. кнопку
loadmoreFlag &&
parseInt(countOlmp) > 20 &&
cnow !== false &&
addButton.unshift({
text: '↙ Загрузить еще',
callback_data: `action_l_${url}_${countOlmp}`,
})
const options = {
reply_markup: JSON.stringify({
inline_keyboard: [addButton],
parse_mode: 'Markdown',
}),
}
// Отправляем сообщение
bot.sendMessage(chatId, 'Выберите действие: ⬇', options)
}
// Проверка, получили ли мы олимпиады
data.length
? data.forEach(function(olympiad, index) {
bot
.sendMessage(
chatId,
`${olympiad.classes ? `<b>ℹ ${olympiad.classes}</b>\n\n` : ''}<a href="${
olympiad.link
}">🔗 ${olympiad.title}</a>${
olympiad.description ? `\n\n<b>📚 ${olympiad.description}</b>` : ''
}${olympiad.rating ? `\n\n<b>⭐ ${olympiad.rating} - рейтинг</b>` : ''}`,
{
parse_mode: 'html',
// disable_web_page_preview: true,
}
)
// callback функцкия, когда выводить доп.кнопки
.then(() => {
if (index === data.length - 1) {
additionalButton(true)
}
})
})
: (function() {
bot.sendMessage(chatId, 'К сожалению, по данному запросу мы не нашли олимпиад').then(() => {
additionalButton(false)
})
})()
})
}
// Функция поиска результата и формирования запроса к парсингу
function searchResult(msg) {
// Получаем пользовательский запрос
const userData = query[`user-${msg.from.id}`]
const { subject = {}, period = {}, type = {}, classNumber = {}, dist = {} } = userData
const queryTitle = Object.keys(userData).map(function(key, index) {
return userData[key].title
})
// console.log('Result: ', queryTitile.join(', '))
// Формируем url
const url = `${subject.value}=on${dist.value ? `&dist=${dist.value}&` : ''}${
type.value ? `&type=${type.value}` : ''
}${classNumber.value ? `&class=${classNumber.value}` : ''}${
period.value ? `&period=${period.value}` : ''
}`
chatId = msg.from.id
// Отправляем подготовленные данные
getOlympiadsInfo(url, msg)
// Чистим данные пользовательской сессии
// clearUserData(msg)
}
// Сборщик мусора, удаляем данные пользователя, когда выполнено целевое действие
function clearUserData(msg) {
delete query[`user-${msg.from.id}`]
}
// Вспомогательная функция (helper) - для преобразования параметров url в формат JSON
function getUrlVars(url) {
let hash
let myJson = {}
const hashes = url.slice(url.indexOf('?') + 1).split('&')
for (let i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=')
myJson[hash[0]] = hash[1]
}
return myJson
}
// Функция проверки данных для пользователя
function checkUserData(msg) {
return (query[`user-${msg.from.id}`] =
query[`user-${msg.from.id}`] === undefined ? {} : query[`user-${msg.from.id}`])
}
// Функция срабатыващая при нажатии на кнопки бота
bot.on('callback_query', function(msg) {
// Вытаскиваю параметры из кнопки
const answer = msg.data.split('_')
const index = answer[0]
const button = answer[1]
const value = answer[2]
const param = answer[3]
// Выводит попап с выбранным значением
// bot.answerCallbackQuery(msg.id, 'Вы выбрали: ' + value, true)
// Провека, если уже данные пользователя в памяти
const queryUser = checkUserData(msg)
// Смотрю тип действия
if (index === 'action') {
switch (button) {
case 'search':
return searchResult(msg)
case 'repeat':
// Чищу данные, если нажата кнопка "Начать заново"
clearUserData(msg)
return newQuestion(msg, 0)
// case: 'loadmore' потому, что я превысил лимит в 64 байта для передаваемых параметров
case 'l':
// Получаю параметры пользователя в формате JSON
let urlParams = getUrlVars(value)
// cnow - счетчик выведенных данных
let { cnow = 0 } = urlParams
cnow = parseInt(cnow)
// Логика доп. подгрузки олимпиад
if (cnow) {
if (cnow <= param - 20 && cnow <= maxElements) {
cnow = cnow + 20
} else {
cnow = false
}
} else {
cnow = 20
}
// Преобразование параметров JSON в формак url query
let urlString = Object.entries({ ...urlParams, cnow: cnow })
.map(e => e[0] + '=' + e[1])
// .map(e => encodeURIComponent(e[0]) + '=' + encodeURIComponent(e[1]))
.join('&')
// Отправляю данные в функцию получения олимпиады
return getOlympiadsInfo(urlString, msg, cnow)
default:
}
} else if (index == lastIndex) {
queryUser[param] = {
title: value,
value: button,
}
// Отправляю данные в функцию для поиска результатов
return searchResult(msg)
}
// Добавляю новое значение в запрос пользователя
queryUser[param] = {
title: value,
value: button,
}
// Вызываю функцию, которая выводит новый вопрос
newQuestion(msg, parseInt(index) + 1)
})
})