oira666_tg 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/apply_middlewares.js +47 -0
- package/index.js +29 -0
- package/middleware/apply_handler.js +236 -0
- package/middleware/auth.js +22 -0
- package/middleware/error_handler.js +230 -0
- package/middleware/keyboard.js +13 -0
- package/middleware/message_info.js +23 -0
- package/middleware/session.js +27 -0
- package/options.js +39 -0
- package/package.json +15 -0
- package/source/classes/MessageInfo.js +81 -0
- package/source/classes/MessageInfoManager.js +64 -0
- package/source/classes/TelegramSession.js +111 -0
- package/source/classes/TextButtons.js +21 -0
- package/source/classes/User.js +104 -0
- package/source/handlers/id.js +6 -0
- package/source/handlers/test.js +15 -0
- package/utils/_require.js +17 -0
- package/utils/find_in_handlers.js +12 -0
- package/utils/regexps.js +8 -0
- package/utils/send_empty_message.js +12 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const session = require('./middleware/session');
|
|
2
|
+
const message_info = require('./middleware/message_info');
|
|
3
|
+
const auth = require('./middleware/auth');
|
|
4
|
+
|
|
5
|
+
const keyboard = require('./middleware/keyboard');
|
|
6
|
+
const {
|
|
7
|
+
apply_handler_to_action,
|
|
8
|
+
apply_handler_to_expected_input,
|
|
9
|
+
apply_handler_to_command,
|
|
10
|
+
apply_handler_to_text_button,
|
|
11
|
+
apply_handler_to_unhandled_messages,
|
|
12
|
+
run_handler
|
|
13
|
+
} = require('./middleware/apply_handler');
|
|
14
|
+
const { error_handler } = require('./middleware/error_handler');
|
|
15
|
+
const { set_options } = require('./options');
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
* @param {*} bot
|
|
21
|
+
* @param {*} options
|
|
22
|
+
*/
|
|
23
|
+
const apply_middlewares = (bot, options) => {
|
|
24
|
+
set_options(bot, options);
|
|
25
|
+
|
|
26
|
+
bot.use(session);
|
|
27
|
+
bot.use(auth);
|
|
28
|
+
bot.use(keyboard);
|
|
29
|
+
|
|
30
|
+
// Порядок имеет значение
|
|
31
|
+
// Применяется только первый найденный обработчик,
|
|
32
|
+
// Остальные игнорируются
|
|
33
|
+
bot.action(/.*/, apply_handler_to_action);
|
|
34
|
+
bot.on('message', apply_handler_to_text_button);
|
|
35
|
+
bot.on('message', apply_handler_to_command);
|
|
36
|
+
bot.use(apply_handler_to_expected_input);
|
|
37
|
+
bot.use(apply_handler_to_unhandled_messages);
|
|
38
|
+
|
|
39
|
+
bot.use(message_info);
|
|
40
|
+
|
|
41
|
+
bot.use(run_handler);
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
bot.catch(error_handler);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
module.exports.apply_middlewares = apply_middlewares;
|
package/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { apply_middlewares } = require('./apply_middlewares');
|
|
2
|
+
const { redirect_handler, apply_handler_to_cron_task } = require('./middleware/apply_handler');
|
|
3
|
+
const { find_in_handlers } = require('./utils/find_in_handlers');
|
|
4
|
+
const { send_empty_message } = require('./utils/send_empty_message');
|
|
5
|
+
const { error_handler, notifyAdmins, error_403_handler, sendErrorToUser } = require('./middleware/error_handler');
|
|
6
|
+
|
|
7
|
+
const { TelegramSession } = require('../source/classes/TelegramSession');
|
|
8
|
+
const { User } = require('../source/classes/User');
|
|
9
|
+
const { MessageInfoManager } = require('../source/classes/MessageInfoManager');
|
|
10
|
+
|
|
11
|
+
// apply middlwares
|
|
12
|
+
module.exports.apply_middlewares = apply_middlewares;
|
|
13
|
+
|
|
14
|
+
// handle handlers
|
|
15
|
+
module.exports.redirect_handler = redirect_handler;
|
|
16
|
+
module.exports.apply_handler_to_cron_task = apply_handler_to_cron_task;
|
|
17
|
+
module.exports.find_in_handlers = find_in_handlers;
|
|
18
|
+
module.exports.send_empty_message = send_empty_message;
|
|
19
|
+
|
|
20
|
+
// error handling
|
|
21
|
+
module.exports.error_handler = error_handler;
|
|
22
|
+
module.exports.notifyAdmins = notifyAdmins;
|
|
23
|
+
module.exports.error_403_handler = error_403_handler;
|
|
24
|
+
module.exports.sendErrorToUser = sendErrorToUser;
|
|
25
|
+
|
|
26
|
+
// classes
|
|
27
|
+
module.exports.TelegramSession = TelegramSession;
|
|
28
|
+
module.exports.User = User;
|
|
29
|
+
module.exports.MessageInfoManager = MessageInfoManager;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { regexps } = require('../utils/regexps');
|
|
4
|
+
const { _require } = require('../utils/_require');
|
|
5
|
+
const { options } = require('../options');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const MAX_handler_history_LENGTH = 5;
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Отправляет необработанные сообщения на обработчик unhandled_message.js
|
|
13
|
+
*/
|
|
14
|
+
const apply_handler_to_unhandled_messages = async (ctx, next) => {
|
|
15
|
+
// Если ввод уже был обработан другим обработчиком -
|
|
16
|
+
// ничего не делаем
|
|
17
|
+
if (!ctx.handler?.name) {
|
|
18
|
+
_add_handler_to_ctx(ctx, 'unhandled_message', 'unhandled_message');
|
|
19
|
+
}
|
|
20
|
+
await next();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Есди бот ожидает произовльного сообщения от пользователя
|
|
26
|
+
* в session.expected_input записывается имя обработчика,
|
|
27
|
+
* который готов обработать это сообщение.
|
|
28
|
+
* записывает его в ctx.handlername и применяет
|
|
29
|
+
*/
|
|
30
|
+
const apply_handler_to_expected_input = async (ctx, next) => {
|
|
31
|
+
// Если ввод уже был обработан другим обработчиком -
|
|
32
|
+
// ничего не делаем
|
|
33
|
+
if (!ctx.handler?.name) {
|
|
34
|
+
|
|
35
|
+
if (ctx.updateType === 'message') {
|
|
36
|
+
_add_handler_to_ctx(ctx, ctx.expected_input, 'expected_input');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
ctx.expected_input = null;
|
|
41
|
+
|
|
42
|
+
await next();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const apply_handler_to_inline_query = async (ctx, next) => {
|
|
46
|
+
// Если ввод уже был обработан другим обработчиком -
|
|
47
|
+
// ничего не делаем
|
|
48
|
+
if (!ctx.handler?.name) {
|
|
49
|
+
_add_handler_to_ctx(ctx, 'inline_query', 'inline_query');
|
|
50
|
+
}
|
|
51
|
+
await next();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const apply_handler_to_chosen_inline_result = async (ctx, next) => {
|
|
55
|
+
// Если ввод уже был обработан другим обработчиком -
|
|
56
|
+
// ничего не делаем
|
|
57
|
+
if (!ctx.handler?.name) {
|
|
58
|
+
_add_handler_to_ctx(ctx, 'chosen_inline_result', 'chosen_inline_result');
|
|
59
|
+
}
|
|
60
|
+
await next();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
const apply_handler_to_command = async (ctx, next) => {
|
|
65
|
+
// Если ввод уже был обработан другим обработчиком -
|
|
66
|
+
// ничего не делаем
|
|
67
|
+
if (!ctx.handler?.name) {
|
|
68
|
+
const is_command = (ctx?.message?.text || '').charAt(0) === '/';
|
|
69
|
+
if (is_command) {
|
|
70
|
+
const handlername = (ctx.message.text || '').slice(1);
|
|
71
|
+
const commandname = handlername.replace(/^(\w+).*$/i, '$1');
|
|
72
|
+
if (['admin', 'developer'].includes(ctx.user.role) || ~options.allowed_user_commands.indexOf(commandname)) {
|
|
73
|
+
_add_handler_to_ctx(ctx, handlername, 'command');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await next();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* При создании текстовой кнопки в session.keyboard
|
|
83
|
+
* записывается соответствие текста и имени обработчика
|
|
84
|
+
* находит имя обработчика, соответствующее полученному тексту,
|
|
85
|
+
* записывает его в ctx.handlername и применяет
|
|
86
|
+
*/
|
|
87
|
+
const apply_handler_to_text_button = async (ctx, next) => {
|
|
88
|
+
// Если ввод уже был обработан другим обработчиком -
|
|
89
|
+
// ничего не делаем
|
|
90
|
+
if (!ctx.handler?.name) {
|
|
91
|
+
const button = _.find((ctx.keyboard || []), {caption: ctx.message.text}) || {};
|
|
92
|
+
|
|
93
|
+
_add_handler_to_ctx(ctx, button.action, 'text_button');
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
await next();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Имя действия обратного вызова является именем обработчика,
|
|
101
|
+
* записывает его в ctx.handlername и применяет
|
|
102
|
+
*/
|
|
103
|
+
const apply_handler_to_action = async (ctx, next) => {
|
|
104
|
+
// Если ввод уже был обработан другим обработчиком -
|
|
105
|
+
// ничего не делаем
|
|
106
|
+
if (!ctx.handler?.name) {
|
|
107
|
+
_add_handler_to_ctx(ctx, ctx.match.input, 'callback_action');
|
|
108
|
+
};
|
|
109
|
+
await ctx.telegram.answerCbQuery(ctx.update.callback_query.id);
|
|
110
|
+
await next();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Применяет новый обработчик, предыдущие записывает в историю
|
|
115
|
+
* @param {Context} ctx
|
|
116
|
+
* @param {String} handlername
|
|
117
|
+
*/
|
|
118
|
+
const apply_handler_to_cron_task = async (ctx, handlername) => {
|
|
119
|
+
_add_handler_to_ctx(ctx, handlername, 'cron');
|
|
120
|
+
await run_handler(ctx);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Применяет новый обработчик, предыдущие записывает в историю
|
|
126
|
+
* @param {Context} ctx
|
|
127
|
+
* @param {String} handlername
|
|
128
|
+
*/
|
|
129
|
+
const redirect_handler = async (ctx, handlername) => {
|
|
130
|
+
const current_handler = ctx.handler;
|
|
131
|
+
_add_handler_to_ctx(ctx, handlername, 'redirect');
|
|
132
|
+
await run_handler(ctx);
|
|
133
|
+
_add_handler_to_ctx(ctx, current_handler.name, current_handler.type);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
const run_handler = async (ctx, next) => {
|
|
138
|
+
if (typeof ctx?.handler?.runner === 'function') {
|
|
139
|
+
await ctx.handler.runner(ctx);
|
|
140
|
+
}
|
|
141
|
+
if (typeof next === 'function') await next();
|
|
142
|
+
|
|
143
|
+
_add_handler_history(ctx);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
*
|
|
148
|
+
* @param {Object} ctx
|
|
149
|
+
* добавляет текущий обработчик в историю обработчиков
|
|
150
|
+
* которая сохраняется в сессии
|
|
151
|
+
*/
|
|
152
|
+
const _add_handler_history = (ctx) => {
|
|
153
|
+
if (!ctx.handler) return;
|
|
154
|
+
if (! ctx.handler_history) ctx.handler_history = [];
|
|
155
|
+
|
|
156
|
+
ctx.handler_history.unshift({
|
|
157
|
+
name: ctx.handler.name,
|
|
158
|
+
type: ctx.handler.type,
|
|
159
|
+
args: ctx.handler.args,
|
|
160
|
+
data: ctx.handler_history_data,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (ctx.handler_history.length > MAX_handler_history_LENGTH) {
|
|
164
|
+
ctx.handler_history.splice(MAX_handler_history_LENGTH);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* применяет обработчик из ctx.handler
|
|
170
|
+
* если такого обработчика нет - ничего не делает
|
|
171
|
+
*/
|
|
172
|
+
const _add_handler_to_ctx = (ctx, handlername, handlertype) => {
|
|
173
|
+
const current = _parse_handlername(handlername, handlertype);
|
|
174
|
+
if (!current.name) return;
|
|
175
|
+
|
|
176
|
+
// user paths
|
|
177
|
+
const user_handler_filepath = path.resolve(options.handlers_path, current.name + '.js');
|
|
178
|
+
const user_handler_filepath_main = path.resolve(options.handlers_path, current.name + '/main.js');
|
|
179
|
+
const user_handler_filepath_index = path.resolve(options.handlers_path, current.name + '/index.js');
|
|
180
|
+
|
|
181
|
+
// default paths
|
|
182
|
+
const handler_filepath = path.resolve(__dirname, '../source/handlers/', current.name + '.js');
|
|
183
|
+
const handler_filepath_main = path.resolve(__dirname, '../source/handlers/', current.name + '/main.js');
|
|
184
|
+
const handler_filepath_index = path.resolve(__dirname, '../source/handlers/', current.name + '/index.js');
|
|
185
|
+
const handler = _require(
|
|
186
|
+
user_handler_filepath,
|
|
187
|
+
user_handler_filepath_main,
|
|
188
|
+
user_handler_filepath_index,
|
|
189
|
+
handler_filepath,
|
|
190
|
+
handler_filepath_main,
|
|
191
|
+
handler_filepath_index
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
if (typeof handler?.default === 'function') {
|
|
196
|
+
if (!ctx.handler) ctx.handler = {};
|
|
197
|
+
|
|
198
|
+
ctx.handler = { ...current };
|
|
199
|
+
|
|
200
|
+
ctx.handler.runner = handler.default;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Handlername имеет вид: "path/to/handler arg1 arg2 arg3"
|
|
208
|
+
* Current handler (вывод) имеет вид: { name, type, args: [] }
|
|
209
|
+
* @param {String} handlername
|
|
210
|
+
* @param {String} handlertype
|
|
211
|
+
* @returns Object - current handler
|
|
212
|
+
*/
|
|
213
|
+
const _parse_handlername = (handlername = '', handlertype = '') => {
|
|
214
|
+
if (!handlername) return {};
|
|
215
|
+
|
|
216
|
+
const parts = handlername.split(' ');
|
|
217
|
+
const parsedname = regexps.handlername.test(parts[0]) ? parts[0] : null;
|
|
218
|
+
parts.shift();
|
|
219
|
+
|
|
220
|
+
return ({
|
|
221
|
+
name: parsedname,
|
|
222
|
+
type: handlertype || null,
|
|
223
|
+
args: parts,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports.redirect_handler = redirect_handler;
|
|
228
|
+
module.exports.run_handler = run_handler;
|
|
229
|
+
module.exports.apply_handler_to_unhandled_messages = apply_handler_to_unhandled_messages;
|
|
230
|
+
module.exports.apply_handler_to_expected_input = apply_handler_to_expected_input;
|
|
231
|
+
module.exports.apply_handler_to_inline_query = apply_handler_to_inline_query;
|
|
232
|
+
module.exports.apply_handler_to_chosen_inline_result = apply_handler_to_chosen_inline_result;
|
|
233
|
+
module.exports.apply_handler_to_command = apply_handler_to_command;
|
|
234
|
+
module.exports.apply_handler_to_text_button = apply_handler_to_text_button;
|
|
235
|
+
module.exports.apply_handler_to_action = apply_handler_to_action;
|
|
236
|
+
module.exports.apply_handler_to_cron_task = apply_handler_to_cron_task;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { User } = require('../source/classes/User');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module.exports = async (ctx, next) => {
|
|
5
|
+
if (!ctx.from) {
|
|
6
|
+
await next();
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const user = new User(ctx.from.id);
|
|
10
|
+
|
|
11
|
+
await user.update(ctx.from, { isTelegtramUserObject: true });
|
|
12
|
+
await user.load();
|
|
13
|
+
|
|
14
|
+
if (!user.id) {
|
|
15
|
+
await user.insert(ctx.from);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
await user.updateChats(ctx.from?.id, ctx.chat?.id);
|
|
19
|
+
|
|
20
|
+
ctx.user = user;
|
|
21
|
+
await next();
|
|
22
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
const { options } = require('../options');
|
|
2
|
+
const db = options.db;
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// можно бросать в воздухе не дожидаясь обработки
|
|
6
|
+
const error_handler = async (err = {}, ctx = {}, handled = false) => {
|
|
7
|
+
try {
|
|
8
|
+
if (err.code === 'ETIMEDOUT') throw err;
|
|
9
|
+
|
|
10
|
+
if (ctx.from?.id && err?.on?.payload?.chat_id === ctx.from?.id) {
|
|
11
|
+
const handled403 = await error_403_handler(err, ctx.from?.id);
|
|
12
|
+
if (handled403) return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
switch(ctx.project_section){
|
|
16
|
+
case 'cron':
|
|
17
|
+
console.error('===== cron error begin =====');
|
|
18
|
+
logError(err);
|
|
19
|
+
console.error('handler: ', ctx.handler);
|
|
20
|
+
if (handled) console.error('Ошибка была обработана и проглочена приложением');
|
|
21
|
+
console.error('===== cron error end =====');
|
|
22
|
+
break;
|
|
23
|
+
case 'unhandledRejection':
|
|
24
|
+
console.error('=== UNHANDLED REJECTION ===');
|
|
25
|
+
logError(err);
|
|
26
|
+
console.error('=== END UNHANDLED REJECTION ===');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
break;
|
|
29
|
+
default:
|
|
30
|
+
console.error('===== error begin =====');
|
|
31
|
+
logError(err);
|
|
32
|
+
console.error('message: ', ctx.message);
|
|
33
|
+
console.error('session: ', ctx.session);
|
|
34
|
+
console.error('user: ', ctx.user);
|
|
35
|
+
console.error('handler: ', ctx.handler);
|
|
36
|
+
if (handled) console.error('Ошибка была обработана и проглочена приложением');
|
|
37
|
+
console.error('===== error end =====');
|
|
38
|
+
}
|
|
39
|
+
await sendErrorToDevelopers(err, ctx, handled);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
console.error('===== error of error begin =====');
|
|
43
|
+
logError(err);
|
|
44
|
+
console.error('===== error of error end =====');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// можно бросать в воздухе не дожидаясь обработки
|
|
49
|
+
const error_403_handler = async (err = {}, user_id) => {
|
|
50
|
+
try {
|
|
51
|
+
if (err?.response?.error_code === 403) {
|
|
52
|
+
await db.update('user', { unsubscribed: 1, dt_unsubscribe: new Date() }, { user_id: user_id, unsubscribed: 0 });
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.error('===== error of error_403_handler begin =====');
|
|
59
|
+
logError(err);
|
|
60
|
+
console.error('===== error of error_403_handler end =====');
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// можно бросать в воздухе не дожидаясь обработки
|
|
66
|
+
const notifyAdmins = async (message_text, ctx) => {
|
|
67
|
+
try {
|
|
68
|
+
if (!message_text) return;
|
|
69
|
+
const _sql = `SELECT * FROM user WHERE role IN ('admin', 'developer') AND unsubscribed = 0`;
|
|
70
|
+
const admins = await db.query(_sql);
|
|
71
|
+
if (!admins || !admins.length) return;
|
|
72
|
+
|
|
73
|
+
for (const admin of admins) {
|
|
74
|
+
try {
|
|
75
|
+
await ctx.telegram.sendMessage(
|
|
76
|
+
admin.user_id,
|
|
77
|
+
message_text,
|
|
78
|
+
{
|
|
79
|
+
parse_mode: 'HTML',
|
|
80
|
+
disable_notification: true,
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
const handled403 = await error_403_handler(err, admin.user_id);
|
|
86
|
+
if (!handled403) throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
console.error('===== error of notifyAdmins begin =====');
|
|
92
|
+
logError(err);
|
|
93
|
+
console.error('===== error of notifyAdmins end =====');
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// send the error to developers
|
|
98
|
+
const sendErrorToDevelopers = async (err = {}, ctx = {}, handled) => {
|
|
99
|
+
const _sql = `SELECT * FROM user WHERE role = 'developer' AND unsubscribed = 0`;
|
|
100
|
+
const developers = await db.query(_sql);
|
|
101
|
+
if (!developers || !developers.length) return;
|
|
102
|
+
const message = getErrorMessageToSend(err, ctx, handled);
|
|
103
|
+
for (const developer of developers) {
|
|
104
|
+
try {
|
|
105
|
+
await ctx.telegram.sendMessage(
|
|
106
|
+
developer.user_id,
|
|
107
|
+
message,
|
|
108
|
+
{
|
|
109
|
+
parse_mode: 'HTML',
|
|
110
|
+
disable_notification: true,
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
const handled403 = await error_403_handler(err, developer.user_id);
|
|
116
|
+
if (!handled403) throw err;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
const getErrorMessageToSend = (err = {}, ctx = {}, handled) => {
|
|
123
|
+
let message = '<b>Ошибка</b>';
|
|
124
|
+
if (ctx.project_section) message += ` в ${ctx.project_section}`;
|
|
125
|
+
if (ctx.handler?.name) message += `\n${ctx.handler?.name}`;
|
|
126
|
+
if (ctx.user) message += `\n<b>Пользователь</b> ${ctx.user.id} ${ctx.user.username || ''} ${ctx.user.first_name || ''} ${ctx.user.last_name || ''}`;
|
|
127
|
+
|
|
128
|
+
if (err.message) message += `\n\n${err.message}`;
|
|
129
|
+
|
|
130
|
+
if (err && err.stack) {
|
|
131
|
+
const _cleanStack = cleanStack(err, { show_system_lines: false, max_lines: 6 });
|
|
132
|
+
message += `\n\n<code>${_cleanStack}</code>`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (handled) message += '\n\n<i>Ошибка была обработана и проглочена приложением</i>';
|
|
136
|
+
|
|
137
|
+
return message;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
const logError = (err) => {
|
|
142
|
+
if (err && err.stack) {
|
|
143
|
+
const _cleanStack = cleanStack(err, { show_system_lines: true });
|
|
144
|
+
console.error('error: ', err.message);
|
|
145
|
+
console.error('stack: ', _cleanStack);
|
|
146
|
+
} else {
|
|
147
|
+
console.error('error: ', err);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
const cleanStack = (err, { show_system_lines = true, max_lines }) => {
|
|
153
|
+
if(!err?.stack) return '';
|
|
154
|
+
|
|
155
|
+
const originalStack = err.stack.split('\n');
|
|
156
|
+
let _cleanStack = [];
|
|
157
|
+
|
|
158
|
+
let inNodeModulesBlock = false;
|
|
159
|
+
let inNodeInternalBlock = false;
|
|
160
|
+
let inUtilsDbBlock = false;
|
|
161
|
+
let nodeModulesLineCount = 0;
|
|
162
|
+
let nodeInternalLineCount = 0;
|
|
163
|
+
let utilsDbLineCount = 0;
|
|
164
|
+
|
|
165
|
+
for (const line of originalStack) {
|
|
166
|
+
if (max_lines && _cleanStack.length >= max_lines) break;
|
|
167
|
+
|
|
168
|
+
if (line.includes('processTicksAndRejections')) {
|
|
169
|
+
_cleanStack.push(line);
|
|
170
|
+
} else if (line.includes('node_modules')) {
|
|
171
|
+
if (show_system_lines) {
|
|
172
|
+
nodeModulesLineCount++;
|
|
173
|
+
if (!inNodeModulesBlock) {
|
|
174
|
+
inNodeModulesBlock = true;
|
|
175
|
+
_cleanStack.push(` at node_modules (${nodeModulesLineCount} lines)....`);
|
|
176
|
+
} else {
|
|
177
|
+
_cleanStack[_cleanStack.length - 1] = ` at node_modules (${nodeModulesLineCount} lines)....`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} else if (line.includes('(node:')) {
|
|
181
|
+
if (show_system_lines) {
|
|
182
|
+
nodeInternalLineCount++;
|
|
183
|
+
if (!inNodeInternalBlock) {
|
|
184
|
+
inNodeInternalBlock = true;
|
|
185
|
+
_cleanStack.push(` at node (${nodeInternalLineCount} lines)....`);
|
|
186
|
+
} else {
|
|
187
|
+
_cleanStack[_cleanStack.length - 1] = ` at node (${nodeInternalLineCount} lines)....`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} else if (line.includes('utils/db.js')) {
|
|
191
|
+
if (show_system_lines) {
|
|
192
|
+
utilsDbLineCount++;
|
|
193
|
+
if (!inUtilsDbBlock) {
|
|
194
|
+
inUtilsDbBlock = true;
|
|
195
|
+
_cleanStack.push(` at utils/db.js (${utilsDbLineCount} lines)....`);
|
|
196
|
+
} else {
|
|
197
|
+
_cleanStack[_cleanStack.length - 1] = ` at utils/db.js (${utilsDbLineCount} lines)....`;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
inNodeModulesBlock = false;
|
|
202
|
+
inNodeInternalBlock = false;
|
|
203
|
+
inUtilsDbBlock = false;
|
|
204
|
+
nodeModulesLineCount = 0;
|
|
205
|
+
nodeInternalLineCount = 0;
|
|
206
|
+
utilsDbLineCount = 0;
|
|
207
|
+
_cleanStack.push(line);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
_cleanStack = _cleanStack.join('\n');
|
|
212
|
+
return _cleanStack;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const sendErrorToUser = async (ctx, message_text) => {
|
|
216
|
+
const _message_text = message_text ? message_text : ctx.t`Что-то пошло не так, попробуй еще раз`;
|
|
217
|
+
await ctx.telegram.sendMessage(
|
|
218
|
+
ctx.chat.id,
|
|
219
|
+
_message_text,
|
|
220
|
+
{
|
|
221
|
+
parse_mode: 'HTML',
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
module.exports.sendErrorToUser = sendErrorToUser;
|
|
227
|
+
module.exports.notifyAdmins = notifyAdmins;
|
|
228
|
+
module.exports.error_403_handler = error_403_handler;
|
|
229
|
+
module.exports.error_handler = error_handler;
|
|
230
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const { MessageInfoManager } = require('../source/classes/MessageInfoManager');
|
|
3
|
+
|
|
4
|
+
// How to use:
|
|
5
|
+
// const messageInfo = await ctx.messageInfo.message(message_id); // load data
|
|
6
|
+
// messageInfo.fieldname = { ... }; // set data
|
|
7
|
+
// messageInfo.clean(); // delete data
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export default async (ctx, next) => {
|
|
11
|
+
if (!ctx.from) {
|
|
12
|
+
await next();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const messageInfoManager = new MessageInfoManager({ from_id: ctx.from.id, chat_id: ctx.chat?.id || 'inline' });
|
|
17
|
+
ctx.messageInfo = messageInfoManager;
|
|
18
|
+
|
|
19
|
+
await next();
|
|
20
|
+
|
|
21
|
+
await ctx.messageInfo.save();
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const { TelegramSession } = require('../source/classes/TelegramSession');
|
|
3
|
+
|
|
4
|
+
export default async (ctx, next) => {
|
|
5
|
+
if (!ctx.from) {
|
|
6
|
+
await next();
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const session = new TelegramSession(ctx.from.id, ctx.chat?.id || 'inline');
|
|
11
|
+
await session.load();
|
|
12
|
+
|
|
13
|
+
ctx.session = session.data;
|
|
14
|
+
ctx.keyboard = session.keyboard;
|
|
15
|
+
ctx.handler_history = session.handler_history;
|
|
16
|
+
ctx.expected_input = session.expected_input;
|
|
17
|
+
|
|
18
|
+
await next();
|
|
19
|
+
|
|
20
|
+
session.data = ctx.session;
|
|
21
|
+
session.keyboard = ctx.keyboard;
|
|
22
|
+
session.handler_history = ctx.handler_history;
|
|
23
|
+
session.expected_input = ctx.expected_input;
|
|
24
|
+
|
|
25
|
+
await session.save();
|
|
26
|
+
}
|
|
27
|
+
|
package/options.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const _default_options = {
|
|
2
|
+
db: 'required', // oira666_db instance
|
|
3
|
+
handlers_path: 'required', // path to the handlers folder
|
|
4
|
+
bot: 'required', // telegraf bot instance
|
|
5
|
+
Markup: 'required', // telegraf Markup instance
|
|
6
|
+
|
|
7
|
+
allowed_user_commands: [ // commands allowed for normal users
|
|
8
|
+
'start',
|
|
9
|
+
'help',
|
|
10
|
+
'id',
|
|
11
|
+
],
|
|
12
|
+
|
|
13
|
+
expected_input_lifetime: 60 * 1000, // expected input lifetime
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const _options = { ..._default_options };
|
|
17
|
+
|
|
18
|
+
const set_options = (options = {}) => {
|
|
19
|
+
const errors = [];
|
|
20
|
+
|
|
21
|
+
for (let key in _default_options) {
|
|
22
|
+
if (options[key] !== undefined) {
|
|
23
|
+
_options[key] = options[key];
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
_options[key] = _default_options[key];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (_options[key] === 'required') {
|
|
30
|
+
errors.push(`Option ${key} is required`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (errors.length) throw new Error(errors.join('\n'));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
module.exports.set_options = set_options;
|
|
38
|
+
|
|
39
|
+
module.exports.options = _options;
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "oira666_tg",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "tg framework for oira666",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"author": "oira666",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"lodash": "^4.17.21",
|
|
13
|
+
"moment": "^2.30.1"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const { options } = require('../options');
|
|
3
|
+
const db = options.db;
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* НЕ ИСПОЛЬЗОВАТЬ НАПРЯМУЮ,
|
|
8
|
+
* только через прокси: MessageInfoManager
|
|
9
|
+
*
|
|
10
|
+
* @class MessageInfo
|
|
11
|
+
* @property {number} from_id
|
|
12
|
+
* @property {number} chat_id
|
|
13
|
+
* @property {number} message_id
|
|
14
|
+
*/
|
|
15
|
+
class MessageInfo {
|
|
16
|
+
constructor ({ from_id, chat_id, message_id }) {
|
|
17
|
+
// при изменении этих полей, нужно менять прокси-класс в MessageInfoManager
|
|
18
|
+
this.from_id = +from_id || 0;
|
|
19
|
+
this.chat_id = +chat_id || 0;
|
|
20
|
+
this.message_id = +message_id || 0;
|
|
21
|
+
this._data = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
clean() {
|
|
25
|
+
this._data = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async save() {
|
|
29
|
+
if (!this.chat_id || !this.message_id) return;
|
|
30
|
+
|
|
31
|
+
if (_.isEmpty(this._data)) {
|
|
32
|
+
await this._deleteMessageInfo();
|
|
33
|
+
} else {
|
|
34
|
+
await this._saveMessageInfo();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async _saveMessageInfo() {
|
|
39
|
+
if (!this.chat_id || !this.message_id) return;
|
|
40
|
+
|
|
41
|
+
const query = `
|
|
42
|
+
INSERT INTO message_info (message_id, chat_id, user_id, data, dt_create)
|
|
43
|
+
VALUES (:message_id, :chat_id, :user_id, :data, :dt_create)
|
|
44
|
+
ON DUPLICATE KEY UPDATE
|
|
45
|
+
data = VALUES(data)
|
|
46
|
+
`;
|
|
47
|
+
const params = {
|
|
48
|
+
message_id: this.message_id,
|
|
49
|
+
chat_id: this.chat_id,
|
|
50
|
+
user_id: this.from_id,
|
|
51
|
+
data: JSON.stringify(this._data),
|
|
52
|
+
dt_create: new Date()
|
|
53
|
+
}
|
|
54
|
+
await db.query(query, params);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async _deleteMessageInfo() {
|
|
58
|
+
if (!this.chat_id || !this.message_id) return;
|
|
59
|
+
|
|
60
|
+
const query = `DELETE FROM message_info WHERE message_id = :message_id AND chat_id = :chat_id`;
|
|
61
|
+
await db.query(query, { message_id: this.message_id, chat_id: this.chat_id});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async load() {
|
|
65
|
+
if (!this.chat_id || !this.message_id) return;
|
|
66
|
+
|
|
67
|
+
const query = `
|
|
68
|
+
SELECT *
|
|
69
|
+
FROM message_info
|
|
70
|
+
WHERE message_id = :message_id AND chat_id = :chat_id
|
|
71
|
+
`;
|
|
72
|
+
const data = await db.query(query, { message_id: this.message_id, chat_id: this.chat_id});
|
|
73
|
+
|
|
74
|
+
if (!_.isEmpty(data)) {
|
|
75
|
+
this._data = data[0].data ? JSON.parse(data[0].data) : null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports.MessageInfo = MessageInfo;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const { MessageInfo } = require('./MessageInfo');
|
|
3
|
+
|
|
4
|
+
class MessageInfoManager {
|
|
5
|
+
constructor(params = {}) {
|
|
6
|
+
this.chat_id = +params.chat_id || 0;
|
|
7
|
+
this.from_id = +params.from_id || 0;
|
|
8
|
+
this._messages = {};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async message(message_id) {
|
|
12
|
+
const message_key = message_id + "_" + this.chat_id;
|
|
13
|
+
|
|
14
|
+
if (this._messages[message_key]) return this._messages[message_key];
|
|
15
|
+
|
|
16
|
+
this._messages[message_key] = this.classProxy(
|
|
17
|
+
new MessageInfo({
|
|
18
|
+
message_id,
|
|
19
|
+
chat_id: this.chat_id,
|
|
20
|
+
from_id: this.from_id,
|
|
21
|
+
})
|
|
22
|
+
);
|
|
23
|
+
await this._messages[message_key].load();
|
|
24
|
+
return this._messages[message_key];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async save() {
|
|
28
|
+
const tasks = [];
|
|
29
|
+
for (const message_key in this._messages) {
|
|
30
|
+
tasks.push(this._messages[message_key].save());
|
|
31
|
+
}
|
|
32
|
+
if (tasks.length) await Promise.all(tasks);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
classProxy(instance) {
|
|
36
|
+
const selfProps = ['chat_id', 'from_id', 'message_id', '_data'];
|
|
37
|
+
|
|
38
|
+
return new Proxy(instance, {
|
|
39
|
+
get(target, prop, receiver) {
|
|
40
|
+
if (typeof target[prop] === "function") {
|
|
41
|
+
return Reflect.get(...arguments);
|
|
42
|
+
}
|
|
43
|
+
if (selfProps.includes(prop)) {
|
|
44
|
+
return target[prop];
|
|
45
|
+
}
|
|
46
|
+
if (target._data) {
|
|
47
|
+
return target._data[prop];
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
},
|
|
51
|
+
set(target, prop, value) {
|
|
52
|
+
if (selfProps.includes(prop)) {
|
|
53
|
+
target[prop] = value;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (!target._data) target._data = {};
|
|
57
|
+
target._data[prop] = value;
|
|
58
|
+
return true;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports.MessageInfoManager = MessageInfoManager;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const moment = require('moment');
|
|
3
|
+
const { options } = require('../options');
|
|
4
|
+
const db = options.db;
|
|
5
|
+
|
|
6
|
+
class TelegramSession {
|
|
7
|
+
constructor (from_id, chat_id) {
|
|
8
|
+
this.session_key = `${from_id}:${chat_id}`;
|
|
9
|
+
this.user_id = +from_id || 0;
|
|
10
|
+
this._data = null;
|
|
11
|
+
this._handler_history = null;
|
|
12
|
+
this._expected_input = null;
|
|
13
|
+
this._keyboard = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get data() {
|
|
17
|
+
return !_.isEmpty(this._data) ? this._data : {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get keyboard() {
|
|
21
|
+
return !_.isEmpty(this._keyboard) ? this._keyboard : [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get handler_history() {
|
|
25
|
+
return !_.isEmpty(this._handler_history) ? this._handler_history : [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get expected_input() {
|
|
29
|
+
return +moment() - +moment(this._expected_input?.dt_create) <= options.expected_input_lifetime ?
|
|
30
|
+
this._expected_input?.action || null : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set data(value) {
|
|
34
|
+
this._data = !_.isEmpty(value) ? value : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
set keyboard(value) {
|
|
38
|
+
this._keyboard = Array.isArray(value) && !_.isEmpty(value) ? value : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set handler_history(value) {
|
|
42
|
+
this._handler_history = !_.isEmpty(value) ? value : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
set expected_input(value) {
|
|
46
|
+
this._expected_input = value ? { action: value, dt_create: moment().format('YYYY-MM-DD HH:mm:ss') } : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async save() {
|
|
50
|
+
if (! this.session_key) return;
|
|
51
|
+
|
|
52
|
+
if (_.isEmpty(this._data) && _.isEmpty(this._handler_history) && _.isEmpty(this._expected_input)) {
|
|
53
|
+
await this._deleteSession();
|
|
54
|
+
} else {
|
|
55
|
+
await this._saveSession();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async _saveSession() {
|
|
60
|
+
if (! this.session_key) return;
|
|
61
|
+
|
|
62
|
+
const query = `
|
|
63
|
+
INSERT INTO session (session_key, user_id, data, keyboard, handler_history, expected_input, dt_access)
|
|
64
|
+
VALUES (:session_key, :user_id, :data, :keyboard, :handler_history, :expected_input, :dt_access)
|
|
65
|
+
ON DUPLICATE KEY UPDATE
|
|
66
|
+
data = VALUES(data),
|
|
67
|
+
keyboard = VALUES(keyboard),
|
|
68
|
+
handler_history = VALUES(handler_history),
|
|
69
|
+
expected_input = VALUES(expected_input),
|
|
70
|
+
dt_access = VALUES(dt_access)
|
|
71
|
+
`;
|
|
72
|
+
const params = {
|
|
73
|
+
session_key: this.session_key,
|
|
74
|
+
user_id: this.user_id,
|
|
75
|
+
data: _.isEmpty(this._data) ? null : JSON.stringify(this._data),
|
|
76
|
+
keyboard: _.isEmpty(this._keyboard) ? null : JSON.stringify(this._keyboard),
|
|
77
|
+
handler_history: _.isEmpty(this._handler_history) ? null : JSON.stringify(this._handler_history),
|
|
78
|
+
expected_input: _.isEmpty(this._expected_input) ? null : JSON.stringify(this._expected_input),
|
|
79
|
+
dt_access: new Date()
|
|
80
|
+
}
|
|
81
|
+
await db.query(query, params);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async _deleteSession() {
|
|
85
|
+
if (! this.session_key) return;
|
|
86
|
+
|
|
87
|
+
const query = `DELETE FROM telegram_session WHERE session_key = :session_key`;
|
|
88
|
+
await db.query(query, {session_key: this.session_key});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async load() {
|
|
92
|
+
if (! this.session_key) return;
|
|
93
|
+
|
|
94
|
+
const query = `
|
|
95
|
+
SELECT *
|
|
96
|
+
FROM session
|
|
97
|
+
WHERE session_key = :session_key
|
|
98
|
+
`;
|
|
99
|
+
const data = await db.query(query, {session_key: this.session_key});
|
|
100
|
+
|
|
101
|
+
if (!_.isEmpty(data)) {
|
|
102
|
+
this._data = data[0].data ? JSON.parse(data[0].data) : null;
|
|
103
|
+
this._keyboard = data[0].keyboard ? JSON.parse(data[0].keyboard) : null;
|
|
104
|
+
this._handler_history = data[0].handler_history ? JSON.parse(data[0].handler_history) : null;
|
|
105
|
+
this._expected_input = data[0].expected_input ? JSON.parse(data[0].expected_input) : null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports.TelegramSession = TelegramSession;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { options } = require('../options');
|
|
2
|
+
const Markup = options.Markup;
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TextButtons {
|
|
6
|
+
constructor (ctx) {
|
|
7
|
+
this.ctx = ctx;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
add(caption, action) {
|
|
11
|
+
this.ctx.keyboard.push({ caption, action });
|
|
12
|
+
return Markup.button.text(caption);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
clean() {
|
|
16
|
+
this.ctx.keyboard = [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports.TextButtons = TextButtons;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
const { options } = require('../options');
|
|
3
|
+
const db = options.db;
|
|
4
|
+
|
|
5
|
+
class User {
|
|
6
|
+
constructor (user_id) {
|
|
7
|
+
this.params = {};
|
|
8
|
+
this.params.user_id = parseInt(user_id) || 0;
|
|
9
|
+
this.user = {};
|
|
10
|
+
this.settings = {};
|
|
11
|
+
this.loaded = false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get id() {return this.user.user_id || 0}
|
|
15
|
+
get user_id() {return this.user.user_id || 0}
|
|
16
|
+
get is_bot() {return Boolean(this.user.is_bot)}
|
|
17
|
+
get role() {return this.user.role}
|
|
18
|
+
get unsubscribed() {return Boolean(Number(this.user.unsubscribed))}
|
|
19
|
+
get is_new() {return Boolean(this.user.is_new)}
|
|
20
|
+
get is_private_member() {return Boolean(this.user.is_private_member)}
|
|
21
|
+
get username() {return this.user.username || null}
|
|
22
|
+
get display_name() {return this.user.display_name || null}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async load() {
|
|
26
|
+
const user = await db.query(`
|
|
27
|
+
SELECT
|
|
28
|
+
user.*,
|
|
29
|
+
IF(chats.user_id IS NULL, 0, 1) AS is_private_member,
|
|
30
|
+
|
|
31
|
+
@full_name := NULLIF(TRIM(CONCAT_WS(' ', user.last_name, user.first_name)), '') AS full_name,
|
|
32
|
+
COALESCE(@full_name, user.username) AS display_name
|
|
33
|
+
|
|
34
|
+
FROM user
|
|
35
|
+
LEFT JOIN user_chats AS chats ON (chats.user_id = user.user_id AND chats.chat_id = user.user_id)
|
|
36
|
+
WHERE user.user_id = :user_id
|
|
37
|
+
`, {user_id: this.params.user_id});
|
|
38
|
+
this.user = _.isEmpty(user) ? {} : user[0];
|
|
39
|
+
this.loaded = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async insert(newuser = {}) {
|
|
43
|
+
this.params.user_id = parseInt(newuser.id) || 0;
|
|
44
|
+
if (!this.params.user_id) return null;
|
|
45
|
+
const params = {
|
|
46
|
+
user_id: this.params.user_id,
|
|
47
|
+
is_bot: newuser.is_bot ? 1 : 0,
|
|
48
|
+
language_code: newuser.language_code || null,
|
|
49
|
+
username: newuser.username || null,
|
|
50
|
+
first_name: newuser.first_name || null,
|
|
51
|
+
last_name: newuser.last_name || null,
|
|
52
|
+
dt_create: new Date(),
|
|
53
|
+
dt_update: new Date(),
|
|
54
|
+
dt_unsubscribe: null,
|
|
55
|
+
unsubscribed: 0,
|
|
56
|
+
}
|
|
57
|
+
await db.insert('user', params, { ignore: true });
|
|
58
|
+
this.user = {
|
|
59
|
+
...params,
|
|
60
|
+
is_new: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async update(params = {}, { isTelegtramUserObject = false }) {
|
|
65
|
+
let _params = params;
|
|
66
|
+
if (!this.params.user_id) return null;
|
|
67
|
+
if (isTelegtramUserObject) {
|
|
68
|
+
_params = {
|
|
69
|
+
user_id: this.params.user_id,
|
|
70
|
+
is_bot: params.is_bot ? 1 : 0,
|
|
71
|
+
language_code: params.language_code || null,
|
|
72
|
+
username: params.username || null,
|
|
73
|
+
first_name: params.first_name || null,
|
|
74
|
+
last_name: params.last_name || null,
|
|
75
|
+
dt_update: new Date(),
|
|
76
|
+
dt_unsubscribe: null,
|
|
77
|
+
unsubscribed: 0,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await db.update('user', _params, { user_id: this.params.user_id });
|
|
82
|
+
this.user = {
|
|
83
|
+
...this.user,
|
|
84
|
+
...params,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async unsubscribe() {
|
|
89
|
+
await this.update({ unsubscribed : 1, dt_unsubscribe: new Date() });
|
|
90
|
+
this.user.is_resubscribed = 0;
|
|
91
|
+
this.user.unsubscribed = 1;
|
|
92
|
+
this.user.dt_unsubscribe = new Date();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async updateChats(user_id, chat_id) {
|
|
96
|
+
if (!user_id || !chat_id) return;
|
|
97
|
+
await db.insert('user_chats',
|
|
98
|
+
{ user_id, chat_id, dt_create: new Date() },
|
|
99
|
+
{ ignore: true }
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports.User = User;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default async (ctx) => {
|
|
2
|
+
try {
|
|
3
|
+
await ctx.telegram.sendMessage(
|
|
4
|
+
ctx.chat.id,
|
|
5
|
+
ctx.t`Hello, world!`,
|
|
6
|
+
{
|
|
7
|
+
parse_mode: 'HTML',
|
|
8
|
+
}
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
console.log(JSON.parse(JSON.stringify(err)));
|
|
13
|
+
console.log(err?.on?.payload?.chat_id);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const _require = (...filepaths) => {
|
|
5
|
+
let result;
|
|
6
|
+
|
|
7
|
+
for (let i=0; i<filepaths.length; i++) {
|
|
8
|
+
if (filepaths[i] && fs.existsSync(filepaths[i])) {
|
|
9
|
+
result = require(filepaths[i]);
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (result || {});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports._require = _require;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const find_in_handlers = (ctx, name) => {
|
|
5
|
+
const reg = new RegExp('(^|/)' + name + '(/|\s|$)');
|
|
6
|
+
|
|
7
|
+
if (reg.test(ctx.handler.name)) return true;
|
|
8
|
+
if (~_.findIndex((ctx.handler.hystory || []), o => reg.test(o.name || '') )) return true;
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports.find_in_handlers = find_in_handlers;
|
package/utils/regexps.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const send_empty_message = async (ctx, message_text) => {
|
|
2
|
+
const message = await ctx.telegram.sendMessage(
|
|
3
|
+
ctx.chat.id,
|
|
4
|
+
message_text,
|
|
5
|
+
{
|
|
6
|
+
parse_mode: 'HTML',
|
|
7
|
+
}
|
|
8
|
+
);
|
|
9
|
+
return message.message_id;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
module.exports.send_empty_message = send_empty_message;
|