Compare commits

...

7 Commits

3 changed files with 305 additions and 171 deletions

View File

@ -1,6 +1,5 @@
# Viber chat-robot # Telegram chat-robot for task registering
Sending requests from [mirzaev/spetsresurs-google_sheets-parser](https://git.mirzaev.sexy/mirzaev/spetsresurs-google_sheets-parser) to [mirzaev/arangodb](https://git.mirzaev.sexy/mirzaev/arangodb) and vice versa Chat-robot Telegram for accepting applications for employees from [mirzaev/ebala](https://git.mirzaev.sexy/mirzaev/ebala)
~~😼 Developed in 1 day for 100000 rubles ($1200)~~ shit happens
**DEVELOPMENT COMPLETED. PROJECT CLOSED.**

View File

@ -23,6 +23,41 @@ $arangodb = new connection(require __DIR__ . '/../settings/arangodb.php');
ini_set('display_errors', 1); ini_set('display_errors', 1);
ini_set('display_startup_errors', 1); */ ini_set('display_startup_errors', 1); */
function escape(string $text)
{
return str_replace(
[
'#',
'*',
'_',
'=',
'.',
'[',
']',
'(',
')',
'-',
'>',
'<'
],
[
'\#',
'\*',
'\_',
'\\=',
'\.',
'\[',
'\]',
'\(',
'\)',
'\-',
'\>',
'\<'
],
$text
);
}
/** /**
* Авторизация * Авторизация
* *
@ -79,7 +114,7 @@ function worker(string $id): _document|null|false
LIMIT 1 LIMIT 1
RETURN e RETURN e
) )
FILTER d._id == e[0]._to && d.active == true FILTER d._id == e[0]._to
SORT d.created DESC, d._key DESC SORT d.created DESC, d._key DESC
LIMIT 1 LIMIT 1
RETURN d RETURN d
@ -172,6 +207,13 @@ function generateMenu(Context $ctx): void
if ($account = authorization($ctx->getMessage()?->getFrom()?->getId() ?? $ctx->getCallbackQuery()->getFrom()->getId())) { if ($account = authorization($ctx->getMessage()?->getFrom()?->getId() ?? $ctx->getCallbackQuery()->getFrom()->getId())) {
// Успешная авторизация // Успешная авторизация
if (!$account->active) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else if ($account->banned) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else if (!($worker = worker($account->getId()))->active) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else if ($worker->fired) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else {
// Активен аккаунт
$ctx->sendMessage('👋 Здравствуйте, ' . preg_replace('/([._\-()!#])/', '\\\$1', $account->name['first']), [ $ctx->sendMessage('👋 Здравствуйте, ' . preg_replace('/([._\-()!#])/', '\\\$1', $account->name['first']), [
'reply_markup' => [ 'reply_markup' => [
'inline_keyboard' => [ 'inline_keyboard' => [
@ -186,6 +228,7 @@ function generateMenu(Context $ctx): void
}); });
} }
} }
}
/** /**
* Прочитать заявки из ArangoDB * Прочитать заявки из ArangoDB
@ -193,10 +236,11 @@ function generateMenu(Context $ctx): void
* @param int $amount Количество * @param int $amount Количество
* @param ?string $date За какую дату (unixtime) * @param ?string $date За какую дату (unixtime)
* @param int $page Страница * @param int $page Страница
* @param _document $worker Сотрудник
* *
* @return Cursor * @return Cursor
*/ */
function requests(int $amount = 5, ?string $date = null, int $page = 1): Cursor function requests(int $amount = 5, ?string $date = null, int $page = 1, _document $worker): Cursor
{ {
global $arangodb; global $arangodb;
@ -217,11 +261,12 @@ function requests(int $amount = 5, ?string $date = null, int $page = 1): Cursor
[ [
'query' => sprintf( 'query' => sprintf(
// d.date < %s там специально, не менять на <= // d.date < %s там специально, не менять на <=
"FOR d IN task FILTER ((d.date >= %s && d.date < %s && d.start >= '05:00') || (d.date >= %s && d.date < %s && d.start < '05:00')) && d.worker == null && d.market != null && d.confirmed != true && d.published == true && d.completed != true SORT d.created DESC, d._key DESC LIMIT %d, %d RETURN d", "FOR d IN task FILTER ((d.date >= %s && d.date < %s && d.start >= '05:00') || (d.date >= %s && d.date < %s && d.start < '05:00')) && d.worker == null && d.market != null && d.confirmed != true && d.published == true && d.completed != true && (FOR m IN market FILTER m.id == d.market && IS_ARRAY(m.bans) SORT m.created DESC, m._key DESC LIMIT 1 RETURN !POSITION(m.bans, \"%s\"))[0] SORT d.created DESC, d._key DESC LIMIT %d, %d RETURN d",
$from = (new DateTime("@$date"))->setTime(0, 0)->format('U'), $from = (new DateTime("@$date"))->setTime(0, 0)->format('U'),
$to = (new DateTime("@$date"))->modify('+1 day')->setTime(0, 0)->format('U'), $to = (new DateTime("@$date"))->modify('+1 day')->setTime(0, 0)->format('U'),
$to, $to,
(new DateTime("@$date"))->modify('+2 day')->setTime(0, 0)->format('U'), (new DateTime("@$date"))->modify('+2 day')->setTime(0, 0)->format('U'),
$worker->id,
$offset, $offset,
$amount + $offset - ($page > 0) $amount + $offset - ($page > 0)
), ),
@ -255,20 +300,58 @@ function requests_previous(Context $ctx): void
} }
function request_choose(Context $ctx): void function request_choose(Context $ctx): void
{
if (($account = authorization($ctx->getCallbackQuery()->getFrom()->getId())) instanceof _document) {
// Авторизован
if (!$account->active) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else if ($account->banned) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else if (!($worker = worker($account->getId()))->active) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else if ($worker->fired) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else {
// Активен аккаунт
// Инициализация ключа инстанции task в базе данных
preg_match('/\->\s#(\d+)\n/', $ctx->getCallbackQuery()->getMessage()->getText(), $matches);
// Запись ключа инстанции task (заявка на которую регистрируется сотрудник)
$ctx->setChatDataItem("request_confirmation_target", $matches[1]);
// Запрос подтверждения
$ctx->sendMessage("⚡ *Подтверждение записи*\n\n" . preg_replace('/(^[^:\s\n\r]+:)/m', '*$1*', preg_replace('/(\\\#\d+)/', '*$1*', escape($ctx->getCallbackQuery()->getMessage()->getText()))) . "\n\n*⚠️ Вы подтверждаете отправку запроса?*", [
'reply_markup' => [
'inline_keyboard' => [
[
['text' => 'Подтвердить', 'callback_data' => 'request_confirmed'],
['text' => 'Отменить', 'callback_data' => 'request_rejected']
]
]
]
])->then(function ($message) use ($ctx) {
// Запись сообщения в кеш (на случай необходимости его удаления)
$ctx->setChatDataItem("request_confirmation", $message);
});
}
}
}
function request_confirmed(Context $ctx): void
{ {
global $arangodb; global $arangodb;
if (($account = authorization($ctx->getCallbackQuery()->getFrom()->getId())) instanceof _document) { if (($account = authorization($ctx->getCallbackQuery()->getFrom()->getId())) instanceof _document) {
// Авторизован // Авторизован
// Инициализация ключа инстанции task в базе данных $ctx->getChatDataItem("request_confirmation_target")->then(function ($_key) use ($ctx, $arangodb, $account) {
preg_match('/\->\s#(\d+)\n/', $ctx->getCallbackQuery()->getMessage()->getText(), $matches); // Прочитана запрашиваемая заявка
$_key = $matches[1];
// Инициализация инстанции task в базе данных (выбранного задания) // Инициализация инстанции task в базе данных (выбранного задания)
$task = collection::search($arangodb->session, sprintf("FOR d IN task FILTER d._key == '%s' && d.published == true && d.completed != true RETURN d", $_key)); $task = collection::search($arangodb->session, sprintf("FOR d IN task FILTER d._key == '%s' && d.published == true && d.completed != true && worker == null RETURN d", $_key));
if ($worker = worker($account->getId())) { if ($task instanceof _document) {
// Найдена заявка (подразумевается, что не занята)
if ($worker ??= worker($account->getId())) {
// Найден сотрудник // Найден сотрудник
// Запись идентификатора нового сотрудника // Запись идентификатора нового сотрудника
@ -280,32 +363,67 @@ function request_choose(Context $ctx): void
if (document::update($arangodb->session, $task)) { if (document::update($arangodb->session, $task)) {
// Записано обновление в базу данных // Записано обновление в базу данных
$ctx->getChatDataItem("request_all")->then(function ($requests = []) use ($ctx, $_key) { $ctx->getChatDataItem("request_all")->then(function ($requests = []) use ($ctx) {
// Удаление сообщений связанных с запросом // Удаление сообщений связанных с запросом
foreach ($requests ?? [] as $_message) $ctx->deleteMessage($_message->getChat()->getId(), $_message->getMessageId()); foreach ($requests ?? [] as $_message) $ctx->deleteMessage($_message->getChat()->getId(), $_message->getMessageId());
});
$ctx->setChatDataItem("request_all", []); $ctx->setChatDataItem("request_all", []);
$ctx->sendMessage("✅ *Заявка принята:* \#$_key", ['reply_markup' => ['remove_keyboard' => true]])->then(function () use ($ctx) { $ctx->getChatDataItem("request_confirmation")->then(function ($message) use ($ctx) {
$ctx->deleteMessage($message->getChat()->getId(), $message->getMessageId());
});
$ctx->setChatDataItem("request_confirmation_target", null);
$ctx->sendMessage("✅ *Вы зарегистрировались на заявку:* \#$_key", ['reply_markup' => ['remove_keyboard' => true]])->then(function () use ($ctx) {
generateMenu($ctx);
});
// End of the process
$ctx->endConversation();
} else $ctx->sendMessage("❎ *Не удалось принять заявку:* \#$_key", ['reply_markup' => ['remove_keyboard' => true]])->then(function () use ($ctx) {
generateMenu($ctx);
});
} else $ctx->sendMessage("❎ *Не удалось принять заявку:* \#$_key", ['reply_markup' => ['remove_keyboard' => true]])->then(function () use ($ctx) {
generateMenu($ctx);
});
} else $ctx->sendMessage("❎ *Не удалось принять заявку:* \#$_key", ['reply_markup' => ['remove_keyboard' => true]])->then(function () use ($ctx) {
generateMenu($ctx);
});
});
}
}
function request_rejected(Context $ctx): void
{
$ctx->getChatDataItem("request_confirmation_target")->then(function ($_key) use ($ctx) {
// Прочитана запрашиваемая заявка
$ctx->getChatDataItem("request_confirmation")->then(function ($message) use ($ctx) {
$ctx->deleteMessage($message->getChat()->getId(), $message->getMessageId());
});
$ctx->setChatDataItem("request_confirmation_target", null);
$ctx->sendMessage("✅ *Вы отменили регистрацию на заявку:* \#$_key", ['reply_markup' => ['remove_keyboard' => true]])->then(function () use ($ctx) {
generateMenu($ctx); generateMenu($ctx);
}); });
// End of the process // End of the process
$ctx->endConversation(); $ctx->endConversation();
}); });
} else $ctx->sendMessage("❎ *Не удалось принять заявку:* \#$_key", ['reply_markup' => ['remove_keyboard' => true]])->then(function () use ($ctx) {
generateMenu($ctx);
});
} else $ctx->sendMessage("❎ *Не удалось принять заявку:* \#$_key", ['reply_markup' => ['remove_keyboard' => true]])->then(function () use ($ctx) {
generateMenu($ctx);
});
}
} }
function day(Context $ctx): void function day(Context $ctx): void
{ {
if (authorization($ctx->getMessage()?->getFrom()?->getId() ?? $ctx->getCallbackQuery()->getFrom()->getId()) instanceof _document) { if (($account = authorization($ctx->getMessage()?->getFrom()?->getId() ?? $ctx->getCallbackQuery()->getFrom()->getId())) instanceof _document) {
// Авторизован // Авторизован
if (!$account->active) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else if ($account->banned) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else if (!($worker = worker($account->getId()))->active) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else if ($worker->fired) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else {
// Активен аккаунт
// Инициализация буфера клавиатуры // Инициализация буфера клавиатуры
$keyboard = []; $keyboard = [];
@ -333,15 +451,23 @@ function day(Context $ctx): void
$ctx->nextStep("search"); $ctx->nextStep("search");
} }
} }
}
function search(Context $ctx): void function search(Context $ctx): void
{ {
global $arangodb; global $arangodb;
if (authorization($ctx->getMessage()?->getFrom()?->getId() ?? $ctx->getCallbackQuery()->getFrom()->getId()) instanceof _document) { if (($account = authorization($ctx->getMessage()?->getFrom()?->getId() ?? $ctx->getCallbackQuery()->getFrom()->getId())) instanceof _document) {
// Авторизован // Авторизован
$ctx->getChatDataItem('requests_page')->then(function ($page) use ($ctx, $arangodb) { if (!$account->active) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else if ($account->banned) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else if (!($worker = worker($account->getId()))->active) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else if ($worker->fired) $ctx->sendMessage('⚠️ Свяжитесь с оператором');
else {
// Активен аккаунт
$ctx->getChatDataItem('requests_page')->then(function ($page) use ($ctx, $arangodb, $worker) {
// Найдена текущая страница // Найдена текущая страница
// Значение страницы по умолчанию // Значение страницы по умолчанию
@ -350,9 +476,9 @@ function search(Context $ctx): void
$ctx->setChatDataItem('requests_page', 1); $ctx->setChatDataItem('requests_page', 1);
} }
$generate = function ($date) use ($ctx, $page, $arangodb) { $generate = function ($date) use ($ctx, $page, $arangodb, $worker) {
// Поиск заявок в ArangoDB // Поиск заявок в ArangoDB
$tasks = requests(4, (string) $date, $page); $tasks = requests(4, (string) $date, $page, $worker);
// Подсчёт количества прочитанных заявок из базы данных // Подсчёт количества прочитанных заявок из базы данных
$count = $tasks->getCount(); $count = $tasks->getCount();
@ -479,6 +605,7 @@ function search(Context $ctx): void
}); });
} }
} }
}
$config = new Config(); $config = new Config();
$config->setParseMode(Config::PARSE_MODE_MARKDOWN); $config->setParseMode(Config::PARSE_MODE_MARKDOWN);
@ -540,6 +667,8 @@ $bot->onCommand('start', function (Context $ctx) use ($stop): void {
$bot->onCbQueryData(['requests_next'], fn ($ctx) => requests_next($ctx)); $bot->onCbQueryData(['requests_next'], fn ($ctx) => requests_next($ctx));
$bot->onCbQueryData(['requests_previous'], fn ($ctx) => requests_previous($ctx)); $bot->onCbQueryData(['requests_previous'], fn ($ctx) => requests_previous($ctx));
$bot->onCbQueryData(['request_choose'], fn ($ctx) => request_choose($ctx)); $bot->onCbQueryData(['request_choose'], fn ($ctx) => request_choose($ctx));
$bot->onCbQueryData(['request_confirmed'], fn ($ctx) => request_confirmed($ctx));
$bot->onCbQueryData(['request_rejected'], fn ($ctx) => request_rejected($ctx));
$bot->onCommand('day', fn ($ctx) => day($ctx)); $bot->onCommand('day', fn ($ctx) => day($ctx));
$bot->onCbQueryData(['day'], fn ($ctx) => day($ctx)); $bot->onCbQueryData(['day'], fn ($ctx) => day($ctx));

View File

@ -1,6 +1,9 @@
[Unit] [Unit]
Description=Telegram-robot Description=Telegram-robot
Wants=network.target
After=syslog.target network-online.target
[Service] [Service]
ExecStart=sudo -u www-data /usr/bin/php /var/www/spetsresurs-telegram-registry-requests/mirzaev/spetsresurs/telegram/registry/requests/system/public/robot.php ExecStart=sudo -u www-data /usr/bin/php /var/www/spetsresurs-telegram-registry-requests/mirzaev/spetsresurs/telegram/registry/requests/system/public/robot.php
PIDFile=/var/run/php/telegram-robot.pid PIDFile=/var/run/php/telegram-robot.pid
@ -8,3 +11,6 @@ RemainAfterExit=no
RuntimeMaxSec=3600s RuntimeMaxSec=3600s
Restart=always Restart=always
RestartSec=5s RestartSec=5s
[Install]
WantedBy=multi-user.target