navalil normalno
This commit is contained in:
parent
5de4881a59
commit
e301abf6aa
|
@ -1,8 +1,6 @@
|
|||
# Telegram chat-robot implementing a register of people
|
||||
# Viber chat-robot
|
||||
|
||||
⚠️ Documentation and code commenting not added
|
||||
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
|
||||
|
||||
Simple, asynchronous, scalable, easy to update
|
||||
The robot sends messages in Russian, but they can easily be replaced with English
|
||||
😼 Developed in 1 days for 100000 rubles ($1200)
|
||||
|
||||
😼 Developed lazily in 2 days
|
||||
|
|
1
START
1
START
|
@ -1 +0,0 @@
|
|||
sudo -u www-data php mirzaev/telegram/registry/people/system/public/robot.php
|
|
@ -1,13 +1,16 @@
|
|||
{
|
||||
"name": "mirzaev/telegram-registry-people",
|
||||
"name": "mirzaev/spetsresurs-viber-registry-requests",
|
||||
"type": "robot",
|
||||
"require": {
|
||||
"badfarm/zanzara": "^0.9.0"
|
||||
"bogdaan/viber-bot-php": "^0.0.15",
|
||||
"triagens/arangodb": "^3.8",
|
||||
"mirzaev/arangodb": "^1.0",
|
||||
"monolog/monolog": "^3.3"
|
||||
},
|
||||
"license": "WTFPL",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"mirzaev\\telegram\\registry\\people\\": "mirzaev/telegram/registry/people/system/"
|
||||
"mirzaev\\spetsresurs\\viber\\registry\\requests\\": "mirzaev/spetsresurs/viber/registry/requests/system/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
|
@ -16,10 +19,5 @@
|
|||
"email": "arsen@mirzaev.sexy"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true
|
||||
}
|
||||
}
|
||||
"minimum-stability": "stable"
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
162
import.sql
162
import.sql
|
@ -1,162 +0,0 @@
|
|||
-- phpMyAdmin SQL Dump
|
||||
-- version 5.1.1deb5ubuntu1
|
||||
-- https://www.phpmyadmin.net/
|
||||
--
|
||||
-- Хост: localhost:3306
|
||||
-- Время создания: Июн 04 2023 г., 07:56
|
||||
-- Версия сервера: 10.6.12-MariaDB-0ubuntu0.22.04.1
|
||||
-- Версия PHP: 8.2.6
|
||||
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
START TRANSACTION;
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
|
||||
--
|
||||
-- База данных: `telegram-registry-people`
|
||||
--
|
||||
CREATE DATABASE IF NOT EXISTS `telegram-registry-people` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
USE `telegram-registry-people`;
|
||||
|
||||
DELIMITER $$
|
||||
--
|
||||
-- Функции
|
||||
--
|
||||
CREATE DEFINER=`root`@`localhost` FUNCTION `LEVENSHTEIN` (`s1` VARCHAR(255) CHARACTER SET utf8, `s2` VARCHAR(255) CHARACTER SET utf8) RETURNS INT(11) BEGIN
|
||||
DECLARE s1_len, s2_len, i, j, c, c_temp, cost INT;
|
||||
DECLARE s1_char CHAR CHARACTER SET utf8;
|
||||
-- max strlen=255 for this function
|
||||
DECLARE cv0, cv1 VARBINARY(256);
|
||||
|
||||
SET s1_len = CHAR_LENGTH(s1),
|
||||
s2_len = CHAR_LENGTH(s2),
|
||||
cv1 = 0x00,
|
||||
j = 1,
|
||||
i = 1,
|
||||
c = 0;
|
||||
|
||||
IF (s1 = s2) THEN
|
||||
RETURN (0);
|
||||
ELSEIF (s1_len = 0) THEN
|
||||
RETURN (s2_len);
|
||||
ELSEIF (s2_len = 0) THEN
|
||||
RETURN (s1_len);
|
||||
END IF;
|
||||
|
||||
WHILE (j <= s2_len) DO
|
||||
SET cv1 = CONCAT(cv1, CHAR(j)),
|
||||
j = j + 1;
|
||||
END WHILE;
|
||||
|
||||
WHILE (i <= s1_len) DO
|
||||
SET s1_char = SUBSTRING(s1, i, 1),
|
||||
c = i,
|
||||
cv0 = CHAR(i),
|
||||
j = 1;
|
||||
|
||||
WHILE (j <= s2_len) DO
|
||||
SET c = c + 1,
|
||||
cost = IF(s1_char = SUBSTRING(s2, j, 1), 0, 1);
|
||||
|
||||
SET c_temp = ORD(SUBSTRING(cv1, j, 1)) + cost;
|
||||
IF (c > c_temp) THEN
|
||||
SET c = c_temp;
|
||||
END IF;
|
||||
|
||||
SET c_temp = ORD(SUBSTRING(cv1, j+1, 1)) + 1;
|
||||
IF (c > c_temp) THEN
|
||||
SET c = c_temp;
|
||||
END IF;
|
||||
|
||||
SET cv0 = CONCAT(cv0, CHAR(c)),
|
||||
j = j + 1;
|
||||
END WHILE;
|
||||
|
||||
SET cv1 = cv0,
|
||||
i = i + 1;
|
||||
END WHILE;
|
||||
|
||||
RETURN (c);
|
||||
END$$
|
||||
|
||||
DELIMITER ;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Структура таблицы `accounts`
|
||||
--
|
||||
|
||||
CREATE TABLE `accounts` (
|
||||
`id` int(11) NOT NULL COMMENT 'Идентификатор',
|
||||
`id_telegram` int(11) NOT NULL COMMENT 'Идентификатор в телеграм',
|
||||
`status` varchar(20) NOT NULL DEFAULT 'active' COMMENT 'Статус',
|
||||
`admin` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Администратор?',
|
||||
`created` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Дата создания'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Структура таблицы `people`
|
||||
--
|
||||
|
||||
CREATE TABLE `people` (
|
||||
`id` int(11) NOT NULL COMMENT 'Идентификатор',
|
||||
`name` varchar(80) DEFAULT NULL COMMENT 'Имя',
|
||||
`surname` varchar(80) DEFAULT NULL COMMENT 'Фамилия',
|
||||
`patronymic` varchar(80) DEFAULT NULL COMMENT 'Отчество',
|
||||
`phone` bigint(20) DEFAULT NULL COMMENT 'Номер смартфона',
|
||||
`address` varchar(255) DEFAULT NULL COMMENT 'Адрес',
|
||||
`day` int(2) DEFAULT NULL COMMENT 'День рождения',
|
||||
`month` int(2) DEFAULT NULL COMMENT 'Месяц рождения',
|
||||
`year` int(4) DEFAULT NULL COMMENT 'Год рождения',
|
||||
`data` text DEFAULT NULL COMMENT 'Информация',
|
||||
`cover` varchar(255) DEFAULT NULL COMMENT 'Обложка',
|
||||
`created` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Дата создания',
|
||||
`updated` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Дата обновления'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
--
|
||||
-- Индексы сохранённых таблиц
|
||||
--
|
||||
|
||||
--
|
||||
-- Индексы таблицы `accounts`
|
||||
--
|
||||
ALTER TABLE `accounts`
|
||||
ADD UNIQUE KEY `id_2` (`id`),
|
||||
ADD KEY `id` (`id`);
|
||||
|
||||
--
|
||||
-- Индексы таблицы `people`
|
||||
--
|
||||
ALTER TABLE `people`
|
||||
ADD UNIQUE KEY `id_2` (`id`),
|
||||
ADD KEY `id` (`id`);
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT для сохранённых таблиц
|
||||
--
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT для таблицы `accounts`
|
||||
--
|
||||
ALTER TABLE `accounts`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Идентификатор';
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT для таблицы `people`
|
||||
--
|
||||
ALTER TABLE `people`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Идентификатор';
|
||||
COMMIT;
|
||||
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
|
@ -1,70 +0,0 @@
|
|||
-- Levenshtein function
|
||||
-- Source: https://openquery.com.au/blog/levenshtein-mysql-stored-function
|
||||
-- Levenshtein reference: http://en.wikipedia.org/wiki/Levenshtein_distance
|
||||
|
||||
-- Arjen note: because the levenshtein value is encoded in a byte array, distance cannot exceed 255;
|
||||
-- thus the maximum string length this implementation can handle is also limited to 255 characters.
|
||||
|
||||
DELIMITER $$
|
||||
DROP FUNCTION IF EXISTS LEVENSHTEIN $$
|
||||
CREATE FUNCTION LEVENSHTEIN(s1 VARCHAR(255) CHARACTER SET utf8, s2 VARCHAR(255) CHARACTER SET utf8)
|
||||
RETURNS INT
|
||||
DETERMINISTIC
|
||||
BEGIN
|
||||
DECLARE s1_len, s2_len, i, j, c, c_temp, cost INT;
|
||||
DECLARE s1_char CHAR CHARACTER SET utf8;
|
||||
-- max strlen=255 for this function
|
||||
DECLARE cv0, cv1 VARBINARY(256);
|
||||
|
||||
SET s1_len = CHAR_LENGTH(s1),
|
||||
s2_len = CHAR_LENGTH(s2),
|
||||
cv1 = 0x00,
|
||||
j = 1,
|
||||
i = 1,
|
||||
c = 0;
|
||||
|
||||
IF (s1 = s2) THEN
|
||||
RETURN (0);
|
||||
ELSEIF (s1_len = 0) THEN
|
||||
RETURN (s2_len);
|
||||
ELSEIF (s2_len = 0) THEN
|
||||
RETURN (s1_len);
|
||||
END IF;
|
||||
|
||||
WHILE (j <= s2_len) DO
|
||||
SET cv1 = CONCAT(cv1, CHAR(j)),
|
||||
j = j + 1;
|
||||
END WHILE;
|
||||
|
||||
WHILE (i <= s1_len) DO
|
||||
SET s1_char = SUBSTRING(s1, i, 1),
|
||||
c = i,
|
||||
cv0 = CHAR(i),
|
||||
j = 1;
|
||||
|
||||
WHILE (j <= s2_len) DO
|
||||
SET c = c + 1,
|
||||
cost = IF(s1_char = SUBSTRING(s2, j, 1), 0, 1);
|
||||
|
||||
SET c_temp = ORD(SUBSTRING(cv1, j, 1)) + cost;
|
||||
IF (c > c_temp) THEN
|
||||
SET c = c_temp;
|
||||
END IF;
|
||||
|
||||
SET c_temp = ORD(SUBSTRING(cv1, j+1, 1)) + 1;
|
||||
IF (c > c_temp) THEN
|
||||
SET c = c_temp;
|
||||
END IF;
|
||||
|
||||
SET cv0 = CONCAT(cv0, CHAR(c)),
|
||||
j = j + 1;
|
||||
END WHILE;
|
||||
|
||||
SET cv1 = cv0,
|
||||
i = i + 1;
|
||||
END WHILE;
|
||||
|
||||
RETURN (c);
|
||||
END $$
|
||||
|
||||
DELIMITER ;
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
// Фреймворк для Viber API
|
||||
use Viber\Client;
|
||||
|
||||
require __DIR__ . '/../../../../../../../vendor/autoload.php';
|
||||
|
||||
try {
|
||||
$client = new Client(['token' => require('../settings/key.php')]);
|
||||
$result = $client->setWebhook(require('../settings/url.php'));
|
||||
echo "Установлено!\n";
|
||||
} catch (Exception $e) {
|
||||
echo 'Ошибка: ' . $e->getMessage() . "\n";
|
||||
}
|
|
@ -0,0 +1,478 @@
|
|||
<?php
|
||||
|
||||
// Фреймворк ArangoDB
|
||||
use mirzaev\arangodb\connection,
|
||||
mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Библиотека для ArangoDB
|
||||
use ArangoDBClient\Document as _document,
|
||||
ArangoDBClient\Cursor,
|
||||
ArangoDBClient\Statement as _statement;
|
||||
|
||||
// Фреймворк для Viber API
|
||||
use Viber\Bot,
|
||||
Viber\Api\Sender,
|
||||
Viber\Api\Event,
|
||||
Viber\Api\Keyboard,
|
||||
Viber\Api\Keyboard\Button,
|
||||
Viber\Api\Message\Contact,
|
||||
Viber\Api\Event\Message,
|
||||
Viber\Api\Message\Text;
|
||||
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
|
||||
require __DIR__ . '/../../../../../../../vendor/autoload.php';
|
||||
|
||||
$arangodb = new connection(require '../settings/arangodb.php');
|
||||
|
||||
$botSender = new Sender([
|
||||
'name' => 'Requests register',
|
||||
'avatar' => 'https://developers.viber.com/img/favicon.ico',
|
||||
]);
|
||||
|
||||
$log = new Logger('bot');
|
||||
$log->pushHandler(new StreamHandler('../bot.log'));
|
||||
|
||||
/**
|
||||
* Авторизация
|
||||
*
|
||||
* @param string $id Идентификатор Viber
|
||||
*
|
||||
* @return _document|null|false (инстанция аккаунта, если подключен и авторизован; null, если не подключен; false, если подключен но неавторизован)
|
||||
*/
|
||||
function authorization(string $id): _document|null|false
|
||||
{
|
||||
global $arangodb;
|
||||
|
||||
if (collection::init($arangodb->session, 'viber'))
|
||||
if (
|
||||
($viber = collection::search($arangodb->session, sprintf("FOR d IN viber FILTER d.id == '%s' RETURN d", $id)))
|
||||
|| $viber = collection::search(
|
||||
$arangodb->session,
|
||||
sprintf(
|
||||
"FOR d IN viber FILTER d._id == '%s' RETURN d",
|
||||
document::write($arangodb->session, 'viber', ['id' => $id, 'status' => 'inactive'])
|
||||
)
|
||||
)
|
||||
)
|
||||
if ($viber->number === null) return null;
|
||||
else if (
|
||||
$viber->status === 'active'
|
||||
&& collection::init($arangodb->session, 'workers')
|
||||
&& $worker = collection::search(
|
||||
$arangodb->session,
|
||||
sprintf(
|
||||
"FOR d IN workers LET e = (FOR e IN connections FILTER e._to == '%s' RETURN e._from)[0] FILTER d._id == e RETURN d",
|
||||
$viber->getId()
|
||||
)
|
||||
)
|
||||
) return $worker;
|
||||
else return false;
|
||||
else throw new exception('Не удалось найти или создать аккаунт');
|
||||
else throw new exception('Не удалось инициализировать коллекцию');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function registration(string $id, string $number): bool
|
||||
{
|
||||
global $arangodb;
|
||||
|
||||
if (collection::init($arangodb->session, 'viber')) {
|
||||
// Инициализация аккаунта
|
||||
if ($viber = collection::search($arangodb->session, sprintf("FOR d IN viber FILTER d.id == '%s' RETURN d", $id))) {
|
||||
// Запись номера
|
||||
$viber->number = $number;
|
||||
if (!document::update($arangodb->session, $viber)) return false;
|
||||
} else if (!collection::search(
|
||||
$arangodb->session,
|
||||
sprintf(
|
||||
"FOR d IN viber FILTER d._id == '%s' RETURN d",
|
||||
document::write($arangodb->session, 'viber', ['id' => $id, 'status' => 'inactive', 'number' => $number])
|
||||
)
|
||||
)) return false;
|
||||
else throw new exception('Не удалось создать аккаунт или записать номер в существующий');
|
||||
|
||||
// Инициализация ребра: workers -> viber
|
||||
if (($worker = collection::search(
|
||||
$arangodb->session,
|
||||
sprintf(
|
||||
"FOR d IN workers FILTER d.phone == '%d' RETURN d",
|
||||
$viber->number
|
||||
)
|
||||
))
|
||||
&& collection::init($arangodb->session, 'connections', true)
|
||||
&& (collection::search(
|
||||
$arangodb->session,
|
||||
sprintf(
|
||||
"FOR d IN connections FILTER d._from == '%s' && d._to == '%s' RETURN d",
|
||||
$worker->getId(),
|
||||
$viber->getId()
|
||||
)
|
||||
)
|
||||
?? collection::search(
|
||||
$arangodb->session,
|
||||
sprintf(
|
||||
"FOR d IN connections FILTER d._id == '%s' RETURN d",
|
||||
document::write(
|
||||
$arangodb->session,
|
||||
'connections',
|
||||
['_from' => $worker->getId(), '_to' => $viber->getId()]
|
||||
)
|
||||
)
|
||||
))
|
||||
) {
|
||||
// Инициализировано ребро: workers -> viber
|
||||
|
||||
// Активация
|
||||
$viber->status = 'active';
|
||||
return document::update($arangodb->session, $viber);
|
||||
}
|
||||
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function generateMenuKeyboard(): Keyboard
|
||||
{
|
||||
return (new Keyboard())
|
||||
->setButtons([
|
||||
(new Button())
|
||||
->setBgColor('#97d446')
|
||||
->setActionType('reply')
|
||||
->setActionBody('btn-search')
|
||||
->setText('🔍 Активные заявки')
|
||||
]);
|
||||
}
|
||||
|
||||
function generateNumberKeyboard(): Keyboard
|
||||
{
|
||||
return (new Keyboard())
|
||||
->setButtons([
|
||||
(new Button())
|
||||
->setBgColor('#E9003A')
|
||||
->setTextSize('large')
|
||||
->setTextHAlign('center')
|
||||
->setActionType('share-phone')
|
||||
->setActionBody('reply')
|
||||
->setText('🔐 Аутентификация'),
|
||||
]);
|
||||
}
|
||||
|
||||
function requests(int $amount = 5): Cursor
|
||||
{
|
||||
global $arangodb;
|
||||
|
||||
return (new _statement(
|
||||
$arangodb->session,
|
||||
[
|
||||
'query' => sprintf(
|
||||
"FOR d IN works FILTER d.confirmed != 'да' LIMIT %d RETURN d",
|
||||
$amount
|
||||
),
|
||||
"batchSize" => 1000,
|
||||
"sanitize" => true
|
||||
]
|
||||
))->execute();
|
||||
}
|
||||
|
||||
try {
|
||||
$bot = new Bot(['token' => require('../settings/key.php')]);
|
||||
|
||||
$bot
|
||||
->onText('|btn-request-choose-*|s', function ($event) use ($bot, $botSender, $log) {
|
||||
global $arangodb;
|
||||
|
||||
$id = $event->getSender()->getId();
|
||||
|
||||
if (($worker = authorization($id)) instanceof _document) {
|
||||
// Авторизован
|
||||
|
||||
// Инициализация ключа инстанции works в базе данных
|
||||
preg_match('/btn-request-choose-(\d+)/', $event->getMessage()->getText(), $matches);
|
||||
$_key = $matches[1];
|
||||
|
||||
// Инициализация инстанции works в базе данных (выбранного задания)
|
||||
$work = collection::search($arangodb->session, sprintf("FOR d IN works FILTER d._key == '%s' RETURN d", $_key));
|
||||
|
||||
// Запись о том, что задание подтверждено (в будущем здесь будет отправка на потдверждение модераторам)
|
||||
$work->confirmed = 'да';
|
||||
|
||||
if (document::update($arangodb->session, $work)) {
|
||||
// Записано обновление в базу данных
|
||||
|
||||
if (collection::search(
|
||||
$arangodb->session,
|
||||
sprintf(
|
||||
"FOR d IN readinesses FILTER d._id == '%s' RETURN d",
|
||||
document::write($arangodb->session, 'readinesses', ['_from' => $worker->getId(), '_to' => $work->getId()])
|
||||
)
|
||||
)
|
||||
) {
|
||||
// Записано ребро: worker -> work (принятие заявки)
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText("✅ **Заявка принята:** #$_key")
|
||||
->setKeyboard(generateMenuKeyboard())
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if ($worker === null) {
|
||||
// Не подключен
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setMinApiVersion(3)
|
||||
->setText('⚠️ **Вы не подключили аккаунт**')
|
||||
->setKeyboard(generateNumberKeyboard($event))
|
||||
);
|
||||
} else {
|
||||
// Не авторизован
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText('⛔️ **Вы не авторизованы**')
|
||||
);
|
||||
}
|
||||
})
|
||||
->onText('|btn-search|s', function ($event) use ($bot, $botSender) {
|
||||
global $arangodb;
|
||||
|
||||
$id = $event->getSender()->getId();
|
||||
|
||||
if (($worker = authorization($id)) instanceof _document) {
|
||||
// Авторизован
|
||||
|
||||
$keyboard = [];
|
||||
|
||||
$requests = requests(5);
|
||||
|
||||
if ($requests->getCount() < 1) {
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText("📦 **Заявок нет**")
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($requests as $request) {
|
||||
// Перебор найденных заявок
|
||||
|
||||
if (($market = collection::search(
|
||||
$arangodb->session,
|
||||
sprintf(
|
||||
"FOR d IN markets LET e = (FOR e IN requests FILTER e._to == '%s' RETURN e._from)[0] FILTER d._id == e RETURN d",
|
||||
$request->getId()
|
||||
)
|
||||
)) instanceof _document) {
|
||||
// Найден магазин
|
||||
|
||||
// Отправка сообщения с данной заявки
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText("**#{$request->getKey()}**\n\n$request->date ($request->start - $request->end)\n**Работа:** \"$request->work\"\n\n**Город:** $market->city\n**Адрес:** $market->address")
|
||||
);
|
||||
|
||||
// Запись выбора заявки в клавиатуру
|
||||
$keyboard[] = (new Button())
|
||||
->setBgColor(sprintf("#%02x%02x%02x", mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255)))
|
||||
->setTextSize('small')
|
||||
->setActionType('reply')
|
||||
->setActionBody("btn-request-choose-{$request->getKey()}")
|
||||
->setText("#{$request->getKey()}");
|
||||
}
|
||||
}
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setMinApiVersion(3)
|
||||
->setText("🔍 Выберите заявку")
|
||||
->setKeyboard((new Keyboard())->setButtons($keyboard ?? []))
|
||||
);
|
||||
} else if ($worker === null) {
|
||||
// Не подключен
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setMinApiVersion(3)
|
||||
->setText('⚠️ **Вы не подключили аккаунт**')
|
||||
->setKeyboard(generateNumberKeyboard($event))
|
||||
);
|
||||
} else {
|
||||
// Не авторизован
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText('⛔️ **Вы не авторизованы**')
|
||||
);
|
||||
}
|
||||
})
|
||||
->onText('|.*|si', function ($event) use ($bot, $botSender) {
|
||||
$id = $event->getSender()->getId();
|
||||
|
||||
if (($worker = authorization($id)) instanceof _document) {
|
||||
// Авторизован
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText('👋 Здравствуйте, ' . $worker->name)
|
||||
->setKeyboard(generateMenuKeyboard($event))
|
||||
);
|
||||
} else if ($worker === null) {
|
||||
// Не подключен
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setMinApiVersion(3)
|
||||
->setText('⚠️ **Вы не подключили аккаунт**')
|
||||
->setKeyboard(generateNumberKeyboard($event))
|
||||
);
|
||||
} else {
|
||||
// Не авторизован
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText('⛔️ **Вы не авторизованы**')
|
||||
);
|
||||
}
|
||||
})
|
||||
->onConversation(function ($event) use ($bot, $botSender) {
|
||||
$id = $event->getUser()->getId();
|
||||
|
||||
if (($worker = authorization($id)) instanceof _document) {
|
||||
// Авторизован
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText('👋 Здравствуйте, ' . $worker->name)
|
||||
->setKeyboard(generateMenuKeyboard($event))
|
||||
);
|
||||
} else if ($worker === null) {
|
||||
// Не подключен
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setMinApiVersion(3)
|
||||
->setText('⚠️ **Вы не подключили аккаунт**')
|
||||
->setKeyboard(generateNumberKeyboard($event))
|
||||
);
|
||||
} else {
|
||||
// Не авторизован
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText('⛔️ **Вы не авторизованы**')
|
||||
);
|
||||
}
|
||||
})
|
||||
->onSubscribe(function ($event) use ($bot, $botSender) {
|
||||
$id = $event->getUser()->getId();
|
||||
|
||||
if (($worker = authorization($id)) instanceof _document) {
|
||||
// Авторизован
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText('Здравствуйте, ' . $worker->name)
|
||||
->setKeyboard(generateMenuKeyboard($event))
|
||||
);
|
||||
} else if ($worker === null) {
|
||||
// Не подключен
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setMinApiVersion(3)
|
||||
->setText('⚠️ **Вы не подключили аккаунт**')
|
||||
->setKeyboard(generateNumberKeyboard($event))
|
||||
);
|
||||
} else {
|
||||
// Не авторизован
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText('⛔️ **Вы не авторизованы**')
|
||||
);
|
||||
}
|
||||
})
|
||||
->on(function ($event) {
|
||||
return ($event instanceof Message && $event->getMessage() instanceof Contact);
|
||||
}, function ($event) use ($bot, $botSender, $log) {
|
||||
$id = $event->getSender()->getId();
|
||||
|
||||
if (registration($id, $event->getMessage()->getPhoneNumber())) {
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText('✅ **Аккаунт подключен**')
|
||||
);
|
||||
|
||||
if ($worker = authorization($id)) {
|
||||
// Авторизован
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText('👋 Здравствуйте, ' . $worker->name)
|
||||
->setKeyboard(generateMenuKeyboard($event))
|
||||
);
|
||||
} else {
|
||||
// Не авторизован
|
||||
|
||||
$bot->getClient()->sendMessage(
|
||||
(new Text())
|
||||
->setSender($botSender)
|
||||
->setReceiver($id)
|
||||
->setText('⛔️ **Вы не авторизованы**')
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
->run();
|
||||
} catch (Exception $e) {
|
||||
$log->warning('Exception: ' . $e->getMessage());
|
||||
if ($bot) {
|
||||
$log->warning('Actual sign: ' . $bot->getSignHeaderValue());
|
||||
$log->warning('Actual body: ' . $bot->getInputBody());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'endpoint' => 'unix:///var/run/arangodb3/arango.sock',
|
||||
'database' => '',
|
||||
'name' => '',
|
||||
'password' => ''
|
||||
];
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
return '';
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
return '';
|
|
@ -1 +0,0 @@
|
|||
storage
|
|
@ -1,706 +0,0 @@
|
|||
<?php
|
||||
|
||||
use DI\Container;
|
||||
use Zanzara\Zanzara;
|
||||
use Zanzara\Context;
|
||||
use Zanzara\Telegram\Type\Input\InputFile;
|
||||
use Zanzara\Config;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
require __DIR__ . '/../../../../../../vendor/autoload.php';
|
||||
|
||||
const KEY = require('../settings/key.php');
|
||||
const STORAGE = require('../settings/storage.php');
|
||||
|
||||
$config = new Config();
|
||||
$config->setParseMode(Config::PARSE_MODE_MARKDOWN);
|
||||
|
||||
$bot = new Zanzara(KEY, $config);
|
||||
|
||||
$pdo = new \PDO('mysql:host=localhost;port=3306;dbname=telegram-registry-people;charset=utf8', 'dolboeb', 'sosiska228');
|
||||
|
||||
function isAdmin(int $id): bool
|
||||
{
|
||||
global $pdo;
|
||||
|
||||
return ($pdo->query("SELECT `admin` FROM accounts WHERE id_telegram=$id")->fetch(PDO::FETCH_ASSOC)['admin'] ?? 0) === 1;
|
||||
}
|
||||
|
||||
function isActive(int $id): bool
|
||||
{
|
||||
global $pdo;
|
||||
|
||||
return ($pdo->query("SELECT `status` FROM accounts WHERE id_telegram=$id")->fetch(PDO::FETCH_ASSOC)['status'] ?? 'inactive') === 'active';
|
||||
}
|
||||
|
||||
function countEntries(): array
|
||||
{
|
||||
global $pdo;
|
||||
|
||||
$date = time();
|
||||
|
||||
$year = date('Y-m-d H:i:s', $date - 31556952);
|
||||
$month = date('Y-m-d H:i:s', $date - 2678400);
|
||||
$week = date('Y-m-d H:i:s', $date - 604800);
|
||||
$day = date('Y-m-d H:i:s', $date - 86400);
|
||||
|
||||
return $pdo->query(
|
||||
<<<SQL
|
||||
SELECT
|
||||
(SELECT COUNT(`id`) FROM `people`) AS 'total',
|
||||
(SELECT COUNT(`id`) FROM `people` WHERE `created` >= '$year') AS 'year',
|
||||
(SELECT COUNT(`id`) FROM `people` WHERE `created` >= '$month') AS 'month',
|
||||
(SELECT COUNT(`id`) FROM `people` WHERE `created` >= '$week') AS 'week',
|
||||
(SELECT COUNT(`id`) FROM `people` WHERE `created` >= '$day') AS 'day'
|
||||
SQL
|
||||
)->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
function lastUpdate(): string
|
||||
{
|
||||
global $pdo;
|
||||
|
||||
return date('Y\\\.m\\\.d H:i:s', strtotime($pdo->query('SELECT `updated` FROM people ORDER BY updated DESC LIMIT 1')->fetch(PDO::FETCH_ASSOC)['updated'] ?? 0));
|
||||
}
|
||||
|
||||
function initEntry(): ?int
|
||||
{
|
||||
global $pdo;
|
||||
|
||||
$pdo->query("INSERT INTO `people` () VALUES ()")->fetch();
|
||||
|
||||
return $pdo->lastInsertId();
|
||||
}
|
||||
|
||||
function generateMenu(Context $ctx): void
|
||||
{
|
||||
$keyboard = [
|
||||
'reply_markup' =>
|
||||
['inline_keyboard' => [
|
||||
[
|
||||
['callback_data' => 'read', 'text' => '🔍 Поиск'],
|
||||
]
|
||||
], 'resize_keyboard' => false]
|
||||
];
|
||||
|
||||
if (isAdmin($ctx->getMessage()?->getFrom()?->getId()) ?? $ctx->getCallbackQuery()->getFrom()->getId())
|
||||
$keyboard['reply_markup']['inline_keyboard'][0][] = ['callback_data' => 'write', 'text' => '📔 Записать'];
|
||||
|
||||
$lastUpdate = lastUpdate();
|
||||
$count = countEntries();
|
||||
|
||||
$ctx->sendMessage(
|
||||
<<<MARKDOWN
|
||||
🪄 *Главное меню*
|
||||
|
||||
*Записано за сутки:* {$count['day']}
|
||||
*Записано за неделю:* {$count['week']}
|
||||
*Записано за месяц:* {$count['month']}
|
||||
*Записано за год:* {$count['year']}
|
||||
*Записано за всё время:* {$count['total']}
|
||||
|
||||
*Последнее обновление:* $lastUpdate
|
||||
MARKDOWN,
|
||||
$keyboard
|
||||
);
|
||||
}
|
||||
|
||||
function createEntry(
|
||||
Context $ctx,
|
||||
?string $name = null,
|
||||
?string $surname = null,
|
||||
?string $patronymic = null,
|
||||
?int $phone = null,
|
||||
?string $address = null,
|
||||
?int $year = null,
|
||||
?int $month = null,
|
||||
?int $day = null,
|
||||
?string $data = null,
|
||||
?string $cover = null
|
||||
): void {
|
||||
$ctx->deleteUserDataItem('wait_for');
|
||||
|
||||
match (null) {
|
||||
$name => waitFor($ctx, 'name'),
|
||||
$surname => waitFor($ctx, 'surname'),
|
||||
$patronymic => waitFor($ctx, 'patronymic'),
|
||||
$phone => waitFor($ctx, 'phone'),
|
||||
$address => waitFor($ctx, 'address'),
|
||||
$year => waitFor($ctx, 'year'),
|
||||
$month => waitFor($ctx, 'month'),
|
||||
$day => waitFor($ctx, 'day'),
|
||||
$data => waitFor($ctx, 'data'),
|
||||
$cover => waitFor($ctx, 'cover'),
|
||||
default => (function () use ($ctx) {
|
||||
$ctx->sendMessage('📦 *Все поля заполнены и записаны в реестре*')->then(function () use ($ctx) {
|
||||
stopProcess($ctx)->then(function () use ($ctx) {
|
||||
generateMenu($ctx);
|
||||
});
|
||||
});
|
||||
})()
|
||||
};
|
||||
}
|
||||
|
||||
function readEntry(
|
||||
Context $ctx,
|
||||
?string $name = null,
|
||||
?string $surname = null,
|
||||
?string $patronymic = null,
|
||||
?int $phone = null,
|
||||
?string $address = null,
|
||||
?int $year = null,
|
||||
?int $month = null,
|
||||
?int $day = null,
|
||||
?string $data = null
|
||||
): PromiseInterface {
|
||||
$ctx->deleteUserDataItem('wait_for');
|
||||
|
||||
return match (null) {
|
||||
$name => waitFor($ctx, 'name'),
|
||||
$surname => waitFor($ctx, 'surname'),
|
||||
$patronymic => waitFor($ctx, 'patronymic'),
|
||||
$phone => waitFor($ctx, 'phone'),
|
||||
$address => waitFor($ctx, 'address'),
|
||||
$year => waitFor($ctx, 'year'),
|
||||
$month => waitFor($ctx, 'month'),
|
||||
$day => waitFor($ctx, 'day'),
|
||||
$data => waitFor($ctx, 'data'),
|
||||
default => (function () use ($ctx) {
|
||||
return $ctx->sendMessage('📦 *Все поля заполнены*');
|
||||
})()
|
||||
};
|
||||
}
|
||||
|
||||
function generateQueryStatus(
|
||||
Context $ctx,
|
||||
?string $name = null,
|
||||
?string $surname = null,
|
||||
?string $patronymic = null,
|
||||
?int $phone = null,
|
||||
?string $address = null,
|
||||
?int $year = null,
|
||||
?int $month = null,
|
||||
?int $day = null,
|
||||
?string $data = null
|
||||
): PromiseInterface {
|
||||
if (isset($name)) $name = preg_replace('/([._\-()!])/', '\\\$1', $name);
|
||||
if (isset($surname)) $surname = preg_replace('/([._\-()!])/', '\\\$1', $surname);
|
||||
if (isset($patronymic)) $patronymic = preg_replace('/([._\-()!])/', '\\\$1', $patronymic);
|
||||
if (isset($phone)) $phone = preg_replace('/([._\-()!])/', '\\\$1', $phone);
|
||||
if (isset($address)) $address = preg_replace('/([._\-()!])/', '\\\$1', $address);
|
||||
if (isset($year)) $year = preg_replace('/([._\-()!])/', '\\\$1', $year);
|
||||
if (isset($month)) $month = preg_replace('/([._\-()!])/', '\\\$1', $month);
|
||||
if (isset($day)) $day = preg_replace('/([._\-()!])/', '\\\$1', $day);
|
||||
if (isset($data)) $data = preg_replace('/([._\-()!])/', '\\\$1', $data);
|
||||
|
||||
$keyboard = generateFieldsButtons(
|
||||
...[
|
||||
'name' => true,
|
||||
'surname' => true,
|
||||
'patronymic' => true,
|
||||
'name' => true,
|
||||
'phone' => true,
|
||||
'address' => true,
|
||||
'year' => true,
|
||||
'month' => true,
|
||||
'day' => true,
|
||||
'data' => true
|
||||
]
|
||||
);
|
||||
|
||||
$keyboard['reply_markup']['inline_keyboard'][] = [
|
||||
['callback_data' => 'stop', 'text' => '❎ Отмена'],
|
||||
['callback_data' => 'complete', 'text' => '✅ Отправить']
|
||||
];
|
||||
|
||||
return $ctx->sendMessage(
|
||||
<<<MARKDOWN
|
||||
📝 *Настройка запроса*
|
||||
|
||||
*Имя:* $name
|
||||
*Фамилия:* $surname
|
||||
*Отчество:* $patronymic
|
||||
*Номер:* $phone
|
||||
*Адрес:* $address
|
||||
*Дата рождения:* $year $month $day
|
||||
*Дополнительно:* $data
|
||||
MARKDOWN,
|
||||
$keyboard
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function generateRequestLabel(string $target, string|int|null $value = null): string
|
||||
{
|
||||
$buffer = match ($target) {
|
||||
'name' => 'Введите имя',
|
||||
'surname' => 'Введите фамилию',
|
||||
'patronymic' => 'Введите отчество',
|
||||
'phone' => 'Введите номер телефона',
|
||||
'address' => 'Введите адрес',
|
||||
'year' => 'Введите год рождения',
|
||||
'month' => 'Введите номер месяца рождения',
|
||||
'day' => 'Введите номер дня рождения',
|
||||
'data' => 'Введите дополнительную информацию',
|
||||
'cover' => 'Отправьте обложку \(изображение\)',
|
||||
default => 'Введите данные для записи в реестр',
|
||||
};
|
||||
|
||||
if (isset($value)) $buffer .= "\n\n*Текущее значение:* " . preg_replace('/([._\-()!])/', '\\\$1', $value);
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
function generateLabel(string $target): string
|
||||
{
|
||||
return match ($target) {
|
||||
'name' => 'Имя',
|
||||
'surname' => 'Фамилия',
|
||||
'patronymic' => 'Отчество',
|
||||
'phone' => 'Номер',
|
||||
'address' => 'Адрес',
|
||||
'year' => 'Год',
|
||||
'month' => 'Месяц',
|
||||
'day' => 'День',
|
||||
'data' => 'Дополнительно',
|
||||
'cover' => 'Обложка',
|
||||
default => 'Информация',
|
||||
};
|
||||
}
|
||||
|
||||
function generateFieldsButtons(
|
||||
?string $name = null,
|
||||
?string $surname = null,
|
||||
?string $patronymic = null,
|
||||
?int $phone = null,
|
||||
?string $address = null,
|
||||
?int $year = null,
|
||||
?int $month = null,
|
||||
?int $day = null,
|
||||
?string $data = null,
|
||||
?string $cover = null
|
||||
): array {
|
||||
$buffer = [];
|
||||
$buffer2 = [];
|
||||
|
||||
if (isset($name)) count($buffer) < 4
|
||||
? $buffer[] = ['callback_data' => 'name', 'text' => generateLabel('name')]
|
||||
: $buffer2[] = ['callback_data' => 'name', 'text' => generateLabel('name')];
|
||||
if (isset($surname)) count($buffer) < 4
|
||||
? $buffer[] = ['callback_data' => 'surname', 'text' => generateLabel('surname')]
|
||||
: $buffer2[] = ['callback_data' => 'surname', 'text' => generateLabel('surname')];
|
||||
if (isset($patronymic)) count($buffer) < 4
|
||||
? $buffer[] = ['callback_data' => 'patronymic', 'text' => generateLabel('patronymic')]
|
||||
: $buffer2[] = ['callback_data' => 'patronymic', 'text' => generateLabel('patronymic')];
|
||||
if (isset($phone)) count($buffer) < 4
|
||||
? $buffer[] = ['callback_data' => 'phone', 'text' => generateLabel('phone')]
|
||||
: $buffer2[] = ['callback_data' => 'phone', 'text' => generateLabel('phone')];
|
||||
if (isset($address)) count($buffer) < 4
|
||||
? $buffer[] = ['callback_data' => 'address', 'text' => generateLabel('address')]
|
||||
: $buffer2[] = ['callback_data' => 'address', 'text' => generateLabel('address')];
|
||||
if (isset($year)) count($buffer) < 4
|
||||
? $buffer[] = ['callback_data' => 'year', 'text' => generateLabel('year')]
|
||||
: $buffer2[] = ['callback_data' => 'year', 'text' => generateLabel('year')];
|
||||
if (isset($month)) count($buffer) < 4
|
||||
? $buffer[] = ['callback_data' => 'month', 'text' => generateLabel('month')]
|
||||
: $buffer2[] = ['callback_data' => 'month', 'text' => generateLabel('month')];
|
||||
if (isset($day)) count($buffer) < 4
|
||||
? $buffer[] = ['callback_data' => 'day', 'text' => generateLabel('day')]
|
||||
: $buffer2[] = ['callback_data' => 'day', 'text' => generateLabel('day')];
|
||||
if (isset($data)) count($buffer) < 4
|
||||
? $buffer[] = ['callback_data' => 'data', 'text' => generateLabel('data')]
|
||||
: $buffer2[] = ['callback_data' => 'data', 'text' => generateLabel('data')];
|
||||
if (isset($cover)) count($buffer) < 4
|
||||
? $buffer[] = ['callback_data' => 'cover', 'text' => generateLabel('cover')]
|
||||
: $buffer2[] = ['callback_data' => 'cover', 'text' => generateLabel('cover')];
|
||||
|
||||
return ['reply_markup' => ['inline_keyboard' => [$buffer, $buffer2], 'resize_keyboard' => false]];
|
||||
}
|
||||
|
||||
function waitFor(Context $ctx, string $target): PromiseInterface
|
||||
{
|
||||
return $ctx->getUserDataItem('process')->then(function ($process) use ($ctx, $target) {
|
||||
if (isset($process))
|
||||
return $ctx->setUserDataItem("wait_for", $target)->then(function () use ($ctx, $target, $process) {
|
||||
return $ctx->sendMessage('⚠️ ' . generateRequestLabel($target, $process['data'][$target]), ['reply_markup' => ['inline_keyboard' => [[['callback_data' => 'delete_field', 'text' => 'Удалить']]], 'resize_keyboard' => false]]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateEntry(int $id, string $name, string|int $value): void
|
||||
{
|
||||
global $pdo;
|
||||
|
||||
try {
|
||||
$pdo->prepare("UPDATE `people` SET `$name` = :value WHERE `id` = :id")->execute([':value' => $value, ':id' => $id]);
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
function checkEntry(int $id, string $name, string|int $value): bool
|
||||
{
|
||||
global $pdo;
|
||||
|
||||
$query = $pdo->prepare("SELECT `$name` FROM people WHERE `id` = :id");
|
||||
|
||||
$query->execute([':id' => $id]);
|
||||
|
||||
return $query->fetch(PDO::FETCH_ASSOC)[$name] === $value;
|
||||
}
|
||||
|
||||
function startSearch(Context $ctx, string $order = 'updated', bool $desc = true, int $page = 1): PromiseInterface
|
||||
{
|
||||
return $ctx->getUserDataItem('process')->then(function ($process) use ($ctx, $order, $desc, $page) {
|
||||
if (empty($process)) return;
|
||||
|
||||
return stopProcess($ctx)->then(function () use ($ctx, $process, $order, $desc, $page) {
|
||||
return $ctx->sendMessage('⚙️ Запрос отправляется\.\.\.')->then(function () use ($ctx, $process, $order, $desc, $page) {
|
||||
foreach ($process['search']($order, $desc, 3, --$page, ...$process['data']) as $entry) {
|
||||
if (isset($entry['name'])) $entry['name'] = preg_replace('/([._\-()!])/', '\\\$1', $entry['name']);
|
||||
if (isset($entry['surname'])) $entry['surname'] = preg_replace('/([._\-()!])/', '\\\$1', $entry['surname']);
|
||||
if (isset($entry['patronymic'])) $entry['patronymic'] = preg_replace('/([._\-()!])/', '\\\$1', $entry['patronymic']);
|
||||
if (isset($entry['phone'])) $entry['phone'] = preg_replace('/([._\-()!])/', '\\\$1', $entry['phone']);
|
||||
if (isset($entry['address'])) $entry['address'] = preg_replace('/([._\-()!])/', '\\\$1', $entry['address']);
|
||||
if (isset($entry['year'])) $entry['year'] = preg_replace('/([._\-()!])/', '\\\$1', $entry['year']);
|
||||
if (isset($entry['month'])) $entry['month'] = preg_replace('/([._\-()!])/', '\\\$1', $entry['month']);
|
||||
if (isset($entry['day'])) $entry['day'] = preg_replace('/([._\-()!])/', '\\\$1', $entry['day']);
|
||||
if (isset($entry['data'])) $entry['data'] = preg_replace('/([._\-()!])/', '\\\$1', $entry['data']);
|
||||
|
||||
$text = "*Имя:* {$entry['name']}\n*Фамилия:* {$entry['surname']}\n*Отчество:* {$entry['patronymic']}\n*Номер:* {$entry['phone']}\n*Адрес:* {$entry['address']}\n*Дата рождения:* {$entry['year']} {$entry['month']} {$entry['day']}\n*Дополнительно:* {$entry['data']}";
|
||||
|
||||
$file = parse_url($entry['cover'])['path'];
|
||||
|
||||
if (file_exists($file)) $ctx->sendPhoto(new InputFile($file), ['caption' => $text, 'protect_content' => true]);
|
||||
else $ctx->sendMessage($text, ['protect_content' => true]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function searchSmartEntry(
|
||||
string $order = 'updated',
|
||||
bool $desc = true,
|
||||
int $limit = 3,
|
||||
int $page = 0,
|
||||
?string $name = null,
|
||||
?string $surname = null,
|
||||
?string $patronymic = null,
|
||||
?int $phone = null,
|
||||
?string $address = null,
|
||||
?int $year = null,
|
||||
?int $month = null,
|
||||
?int $day = null,
|
||||
?string $data = null
|
||||
): array {
|
||||
global $pdo;
|
||||
|
||||
if (
|
||||
empty($name)
|
||||
&& empty($surname)
|
||||
&& empty($patronymic)
|
||||
&& empty($phone)
|
||||
&& empty($address)
|
||||
&& empty($year)
|
||||
&& empty($month)
|
||||
&& empty($day)
|
||||
&& empty($data)
|
||||
) return [];
|
||||
|
||||
$query = 'SELECT * FROM `people` WHERE ';
|
||||
$args = [];
|
||||
$another = false;
|
||||
|
||||
if (isset($name)) {
|
||||
if ($another) $query .= ' && ';
|
||||
else $another = true;
|
||||
$query .= 'levenshtein(`name`, :name) < 3 && `name` != \'\'';
|
||||
$args[':name'] = $name;
|
||||
}
|
||||
|
||||
if (isset($surname)) {
|
||||
if ($another) $query .= ' && ';
|
||||
else $another = true;
|
||||
$query .= 'levenshtein(`surname`, :surname) < 3 && `surname` != \'\'';
|
||||
$args[':surname'] = $surname;
|
||||
}
|
||||
|
||||
if (isset($patronymic)) {
|
||||
if ($another) $query .= ' && ';
|
||||
else $another = true;
|
||||
$query .= 'levenshtein(`patronymic`, :patronymic) < 3 && `patronymic` != \'\'';
|
||||
$args[':patronymic'] = $patronymic;
|
||||
}
|
||||
|
||||
if (isset($phone)) {
|
||||
if ($another) $query .= ' && ';
|
||||
else $another = true;
|
||||
$query .= 'levenshtein(`phone`, :phone) < 2 && `phone` != \'\'';
|
||||
$args[':phone'] = $phone;
|
||||
}
|
||||
|
||||
if (isset($address)) {
|
||||
if ($another) $query .= ' && ';
|
||||
else $another = true;
|
||||
$query .= 'levenshtein(`address`, :address) < 4 && `address` != \'\'';
|
||||
$args[':address'] = $address;
|
||||
}
|
||||
|
||||
if (isset($year)) {
|
||||
if ($another) $query .= ' && ';
|
||||
else $another = true;
|
||||
$query .= '`year` == :year';
|
||||
$args[':year'] = $year;
|
||||
}
|
||||
|
||||
if (isset($month)) {
|
||||
if ($another) $query .= ' && ';
|
||||
else $another = true;
|
||||
$query .= '`month` == :month';
|
||||
$args[':month'] = $month;
|
||||
}
|
||||
|
||||
if (isset($day)) {
|
||||
if ($another) $query .= ' && ';
|
||||
else $another = true;
|
||||
$query .= '`day` == :day';
|
||||
$args[':day'] = $day;
|
||||
}
|
||||
|
||||
if (isset($data)) {
|
||||
if ($another) $query .= ' && ';
|
||||
else $another = true;
|
||||
$query .= 'levenshtein(`data`, :data) < 6 && `data` != \'\'';
|
||||
$args[':data'] = $data;
|
||||
}
|
||||
|
||||
$query .= " ORDER BY `$order` " . ($desc ? 'DESC' : 'ASC');
|
||||
|
||||
$offset = $page === 0 ? 0 : $limit * $page;
|
||||
$query .= " LIMIT $limit OFFSET $offset";
|
||||
|
||||
try {
|
||||
$instance = $pdo->prepare($query);
|
||||
if ($instance->execute($args)) return $instance->fetchAll(PDO::FETCH_ASSOC);
|
||||
else return [];
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$stop = false;
|
||||
|
||||
$bot->onUpdate(function (Context $ctx) use (&$stop): void {
|
||||
if (!isActive($ctx->getMessage()?->getFrom()?->getId() ?? $ctx->getCallbackQuery()->getFrom()->getId())) $stop = true;
|
||||
});
|
||||
|
||||
$bot->onCommand('start', function (Context $ctx) use ($stop): void {
|
||||
if ($stop) return;
|
||||
generateMenu($ctx);
|
||||
});
|
||||
|
||||
$bot->onMessage(function (Context $ctx) use ($stop): void {
|
||||
$text = $ctx->getMessage()->getText();
|
||||
|
||||
if (!empty($text) && $text[0] !== '/' || empty($text))
|
||||
$ctx->getUserDataItem('process')->then(function ($process) use ($ctx, $text) {
|
||||
if (empty($process)) return;
|
||||
|
||||
$ctx->getUserDataItem('wait_for')->then(function ($wait_for) use ($ctx, &$process, $text) {
|
||||
$target = match ($wait_for) {
|
||||
'phone', 'day', 'month', 'year' => (function () use ($ctx, $text) {
|
||||
preg_match_all('!\d+!', $text, $matches);
|
||||
return (int) implode('', $matches[0]);
|
||||
})(),
|
||||
default => $text
|
||||
};
|
||||
|
||||
if ($process['type'] === 'createEntry') {
|
||||
// Создание записи в реестре
|
||||
|
||||
if ($wait_for === 'cover') {
|
||||
if (!file_exists($path = 'storage/' . $process['id'])) mkdir($path, '0755', true);
|
||||
|
||||
$photos = $ctx->getMessage()->getPhoto();
|
||||
|
||||
$ctx->getFile(end($photos)->getFileId())->then(function ($file) use ($ctx, $wait_for, &$path, &$process, &$target) {
|
||||
$url = pathinfo($file->getFilePath());
|
||||
|
||||
if (!file_exists($path .= '/' . $url['dirname'])) mkdir($path, '0755', true);
|
||||
|
||||
file_put_contents($path .= '/' . $url['basename'], fopen('https://api.telegram.org/file/bot' . KEY . '/' . $file->getFilePath(), 'r'));
|
||||
updateEntry($process['id'], $wait_for, $path);
|
||||
|
||||
if (checkEntry($process['id'], $wait_for, $path)) {
|
||||
$process['data'][$wait_for] = $path;
|
||||
$ctx->setUserDataItem('process', $process)->then(function () use ($ctx, $path, $process) {
|
||||
$ctx->sendMessage("✏️ *Записано в реестр*\n\n" . generateLabel('cover') . ': ' . ($link = preg_replace('/([._\-()!])/', '\\\$1', STORAGE . '/' . $path)) . "\n[]($link)")->then(function () use ($ctx, $process) {
|
||||
// Запуск процесса создания
|
||||
createEntry($ctx, ...$process['data']);
|
||||
});
|
||||
});
|
||||
} else $ctx->sendMessage('🚫 Не удалось записать значение в реестр');
|
||||
});
|
||||
} else {
|
||||
updateEntry($process['id'], $wait_for, $target);
|
||||
|
||||
if (checkEntry($process['id'], $wait_for, $target)) {
|
||||
$process['data'][$wait_for] = $target;
|
||||
$ctx->setUserDataItem('process', $process)->then(function () use ($ctx, $target, $wait_for, $process) {
|
||||
$ctx->sendMessage("✏️ *Записано в реестр*\n\n" . generateLabel($wait_for) . ': ' . preg_replace('/([._\-()!])/', '\\\$1', $target))->then(function () use ($ctx, $process) {
|
||||
// Запуск процесса создания
|
||||
createEntry($ctx, ...$process['data']);
|
||||
});
|
||||
});
|
||||
} else $ctx->sendMessage('🚫 *Не удалось записать значение в реестр*');
|
||||
}
|
||||
} else if ($process['type'] === 'readEntry') {
|
||||
// Чтение записей в реестре
|
||||
|
||||
$process['data'][$wait_for] = $target;
|
||||
$ctx->setUserDataItem('process', $process)->then(function () use ($ctx, $process) {
|
||||
generateQueryStatus($ctx, ...$process['data'])->then(function () use ($ctx, $process) {
|
||||
readEntry($ctx, ...$process['data']);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function read(Context $ctx, bool $smart = false): void
|
||||
{
|
||||
global $stop;
|
||||
|
||||
if ($stop) return;
|
||||
|
||||
// Инициализация процесса в кеше
|
||||
$ctx->setUserDataItem('process', [
|
||||
'type' => 'readEntry',
|
||||
'search' => 'searchSmartEntry',
|
||||
'data' => $data = [
|
||||
'name' => null,
|
||||
'surname' => null,
|
||||
'patronymic' => null,
|
||||
'phone' => null,
|
||||
'address' => null,
|
||||
'year' => null,
|
||||
'month' => null,
|
||||
'day' => null,
|
||||
'data' => null
|
||||
]
|
||||
])->then(function () use ($ctx, $data) {
|
||||
$ctx->sendMessage("⚡ *Запущен процесс поиска*")->then(function () use ($ctx, $data) {
|
||||
generateQueryStatus($ctx, ...$data)->then(function () use ($ctx, $data) {
|
||||
// Запуск процесса создания поиска
|
||||
readEntry($ctx, ...$data);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function write(Context $ctx): void
|
||||
{
|
||||
global $stop;
|
||||
|
||||
if ($stop) return;
|
||||
|
||||
if (isAdmin($ctx->getMessage()?->getFrom()?->getId() ?? $ctx->getCallbackQuery()->getFrom()->getId())) {
|
||||
// Администратор
|
||||
|
||||
if ($id = initEntry()) {
|
||||
// Инициализирован человек в базе данных
|
||||
|
||||
// Инициализация процесса в кеше
|
||||
$ctx->setUserDataItem('process', [
|
||||
'type' => 'createEntry',
|
||||
'id' => $id,
|
||||
'data' => $data = [
|
||||
'name' => null,
|
||||
'surname' => null,
|
||||
'patronymic' => null,
|
||||
'phone' => null,
|
||||
'address' => null,
|
||||
'year' => null,
|
||||
'month' => null,
|
||||
'day' => null,
|
||||
'data' => null,
|
||||
'cover' => null
|
||||
]
|
||||
])->then(function () use ($ctx, $id, $data) {
|
||||
$ctx->sendMessage("⚡ *Запущен процесс создания записи*")->then(function () use ($ctx, $data, $id) {
|
||||
$ctx->sendMessage("📦 *Инициализирована запись в реестре:* $id")->then(function () use ($ctx, $data, $id) {
|
||||
// Запуск процесса создания
|
||||
createEntry($ctx, ...$data);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stopProcess(Context $ctx): PromiseInterface
|
||||
{
|
||||
return $ctx->deleteUserDataItem('process')->then(function () use ($ctx) {
|
||||
return $ctx->deleteUserDataItem('wait_for')->then(function () use ($ctx) {
|
||||
return $ctx->sendMessage('⛔ Процесс завершён');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deleteField(Context $ctx): void
|
||||
{
|
||||
$ctx->getUserDataItem('process')->then(function ($process) use ($ctx) {
|
||||
$ctx->getUserDataItem('wait_for')->then(function ($wait_for) use ($ctx, $process) {
|
||||
$process['data'][$wait_for] = null;
|
||||
$ctx->setUserDataItem('process', $process)->then(function () use ($ctx, $process, $wait_for) {
|
||||
$ctx->sendMessage('🗑️ *Удалено значение поля:* ' . mb_strtolower(generateLabel($wait_for)))->then(function () use ($ctx, $process) {
|
||||
generateQueryStatus($ctx, ...$process['data'])->then(function () use ($ctx, $process) {
|
||||
$process['type']($ctx, ...$process['data']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$bot->onCommand('write', fn ($ctx) => write($ctx));
|
||||
$bot->onCommand('read', fn ($ctx) => read($ctx));
|
||||
$bot->onCommand('read_smart', fn ($ctx) => read($ctx, true));
|
||||
|
||||
$bot->onCbQueryData(['write'], fn ($ctx) => write($ctx));
|
||||
$bot->onCbQueryData(['read'], fn ($ctx) => read($ctx));
|
||||
$bot->onCbQueryData(['read_smart'], fn ($ctx) => read($ctx, true));
|
||||
|
||||
$bot->onCommand('name', fn ($ctx) => waitFor($ctx, 'name'));
|
||||
$bot->onCommand('surname', fn ($ctx) => waitFor($ctx, 'surname'));
|
||||
$bot->onCommand('patronymic', fn ($ctx) => waitFor($ctx, 'patronymic'));
|
||||
$bot->onCommand('phone', fn ($ctx) => waitFor($ctx, 'phone'));
|
||||
$bot->onCommand('address', fn ($ctx) => waitFor($ctx, 'address'));
|
||||
$bot->onCommand('year', fn ($ctx) => waitFor($ctx, 'year'));
|
||||
$bot->onCommand('month', fn ($ctx) => waitFor($ctx, 'month'));
|
||||
$bot->onCommand('day', fn ($ctx) => waitFor($ctx, 'day'));
|
||||
$bot->onCommand('data', fn ($ctx) => waitFor($ctx, 'data'));
|
||||
$bot->onCommand('cover', fn ($ctx) => waitFor($ctx, 'cover'));
|
||||
|
||||
$bot->onCbQueryData(['name'], fn ($ctx) => waitFor($ctx, 'name'));
|
||||
$bot->onCbQueryData(['surname'], fn ($ctx) => waitFor($ctx, 'surname'));
|
||||
$bot->onCbQueryData(['patronymic'], fn ($ctx) => waitFor($ctx, 'patronymic'));
|
||||
$bot->onCbQueryData(['phone'], fn ($ctx) => waitFor($ctx, 'phone'));
|
||||
$bot->onCbQueryData(['address'], fn ($ctx) => waitFor($ctx, 'address'));
|
||||
$bot->onCbQueryData(['year'], fn ($ctx) => waitFor($ctx, 'year'));
|
||||
$bot->onCbQueryData(['month'], fn ($ctx) => waitFor($ctx, 'month'));
|
||||
$bot->onCbQueryData(['day'], fn ($ctx) => waitFor($ctx, 'day'));
|
||||
$bot->onCbQueryData(['data'], fn ($ctx) => waitFor($ctx, 'data'));
|
||||
$bot->onCbQueryData(['cover'], fn ($ctx) => waitFor($ctx, 'cover'));
|
||||
|
||||
$bot->onCbQueryData(['delete_field'], fn ($ctx) => deleteField($ctx));
|
||||
|
||||
$bot->onCommand('stop', fn ($ctx) => stopProcess($ctx));
|
||||
$bot->onCbQueryData(['stop'], fn ($ctx) => stopProcess($ctx));
|
||||
|
||||
$bot->onCommand('complete', fn ($ctx) => startSearch($ctx));
|
||||
$bot->onCbQueryData(['complete'], fn ($ctx) => startSearch($ctx));
|
||||
|
||||
$bot->run();
|
|
@ -1,3 +0,0 @@
|
|||
<?php
|
||||
|
||||
return '5999945001:AAHqjP8EugaIsYur65i4UPRt9ATxJndmJ2c';
|
|
@ -1,3 +0,0 @@
|
|||
<?php
|
||||
|
||||
return 'https://tg_storage.mirzaev.sexy';
|
Reference in New Issue