100к рублей за один день туториал
This commit is contained in:
parent
5de4881a59
commit
d0f857e4ba
3
START
3
START
|
@ -1 +1,2 @@
|
||||||
sudo -u www-data php mirzaev/telegram/registry/people/system/public/robot.php
|
sudo -u www-data php mirzaev/google_sheets/parser/system/public/synchronization.php
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "mirzaev/telegram-registry-people",
|
"name": "mirzaev/google_sheets-parser",
|
||||||
"type": "robot",
|
"type": "robot",
|
||||||
"require": {
|
|
||||||
"badfarm/zanzara": "^0.9.0"
|
|
||||||
},
|
|
||||||
"license": "WTFPL",
|
"license": "WTFPL",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"mirzaev\\telegram\\registry\\people\\": "mirzaev/telegram/registry/people/system/"
|
"mirzaev\\google_sheets\\parser\\": "mirzaev/google_sheets/parser/system/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"authors": [
|
"authors": [
|
||||||
|
@ -21,5 +18,11 @@
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"php-http/discovery": true
|
"php-http/discovery": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"flow-php/etl-adapter-google-sheet": "1.x@dev",
|
||||||
|
"webmozart/assert": "^1.11",
|
||||||
|
"triagens/arangodb": "^3.8",
|
||||||
|
"mirzaev/arangodb": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
||||||
|
storage/*
|
||||||
|
!storage
|
|
@ -0,0 +1,105 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Фреймворк ArangoDB
|
||||||
|
use mirzaev\arangodb\connection,
|
||||||
|
mirzaev\arangodb\collection,
|
||||||
|
mirzaev\arangodb\document;
|
||||||
|
|
||||||
|
// Библиотека для ArangoDB
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
// Фреймворк для Google Sheets
|
||||||
|
use Flow\ETL\Adapter\GoogleSheet\GoogleSheetRange,
|
||||||
|
Flow\ETL\Adapter\GoogleSheet\GoogleSheetExtractor,
|
||||||
|
Flow\ETL\Adapter\GoogleSheet\Columns,
|
||||||
|
Flow\ETL\Flow,
|
||||||
|
Flow\ETL\Config,
|
||||||
|
Flow\ETL\FlowContext,
|
||||||
|
Flow\ETL\Row\Entry,
|
||||||
|
Flow\ETL\Row,
|
||||||
|
Flow\ETL\DSL\To,
|
||||||
|
Flow\ETL\DSL\From;
|
||||||
|
|
||||||
|
// Фреймворк для Google API
|
||||||
|
use Google\Client,
|
||||||
|
Google\Service\Sheets,
|
||||||
|
Google\Service\Sheets\ValueRange;
|
||||||
|
|
||||||
|
require __DIR__ . '/../../../../../vendor/autoload.php';
|
||||||
|
|
||||||
|
$arangodb = new connection(require '../settings/arangodb.php');
|
||||||
|
|
||||||
|
function generateLabel(string $name): string
|
||||||
|
{
|
||||||
|
return match ($name) {
|
||||||
|
'id', 'ID', 'ТТ' => 'id',
|
||||||
|
'type', 'ТИП', 'Тип', 'тип' => 'type',
|
||||||
|
'director', 'ДИРЕКТОР', 'Директор', 'директор' => 'director',
|
||||||
|
'address', 'АДРЕС', 'Адрес', 'адрес' => 'address',
|
||||||
|
default => throw new exception("Неизвестный столбец: $name")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function degenerateLabel(string $name): string
|
||||||
|
{
|
||||||
|
return match ($name) {
|
||||||
|
'ID', 'id' => 'ID',
|
||||||
|
'ТИП', 'type' => 'ТИП',
|
||||||
|
'ДИРЕКТОР', 'director' => 'ДИРЕКТОР',
|
||||||
|
'АДРЕС', 'address' => 'АДРЕС',
|
||||||
|
default => throw new exception("Неизвестный столбец: $name")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(array $row, bool $reverse = false): array
|
||||||
|
{
|
||||||
|
$buffer = [];
|
||||||
|
|
||||||
|
foreach ($row as $key => $value) $buffer[(($reverse ? 'de' : null) . 'generateLabel')($key)] = $value ?? '';
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function sync(Row &$row, string $city = 'Красноярск'): void
|
||||||
|
{
|
||||||
|
global $arangodb;
|
||||||
|
|
||||||
|
$_row = init($row->entries()->toArray()['row']);
|
||||||
|
|
||||||
|
if ($_row['id'] !== null)
|
||||||
|
if (collection::init($arangodb->session, 'markets'))
|
||||||
|
if ($market = collection::search($arangodb->session, sprintf("FOR d IN markets FILTER d.id == '%s' RETURN d", $_row['id'])))
|
||||||
|
if ($_row === $new = array_diff_key($market->getAll(), ['_key' => true, 'created' => true, 'city' => true]));
|
||||||
|
else $row = $row->set((new Flow())->read(From::array([init($new, true)]))->fetch(1)[0]->get('row'));
|
||||||
|
else if (collection::search($arangodb->session, sprintf("FOR d IN markets FILTER d._id == '%s' RETURN d", document::write($arangodb->session, 'markets', $_row + ['city' => $city]))));
|
||||||
|
else throw new exception('Не удалось создать или найти созданного магазина');
|
||||||
|
else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = json_decode(require('../settings/markets/google.php'), true);
|
||||||
|
$document = require('../settings/markets/document.php');
|
||||||
|
$sheets = require('../settings/markets/sheets.php');
|
||||||
|
|
||||||
|
$client = new Client();
|
||||||
|
$client->setScopes(Sheets::SPREADSHEETS);
|
||||||
|
$client->setAuthConfig($settings);
|
||||||
|
$api = new Sheets($client);
|
||||||
|
|
||||||
|
foreach ($sheets as $sheet) {
|
||||||
|
$rows = (new Flow())->read(new GoogleSheetExtractor($api, $document, new Columns($sheet, 'A', 'D'), true, 1000, 'row'));
|
||||||
|
|
||||||
|
$i = 1;
|
||||||
|
foreach ($rows->fetch(100) as $row) {
|
||||||
|
++$i;
|
||||||
|
$buffer = $row;
|
||||||
|
sync($row, $sheet);
|
||||||
|
if ($buffer !== $row)
|
||||||
|
$api->spreadsheets_values->update(
|
||||||
|
$document,
|
||||||
|
"$sheet!A$i:D$i",
|
||||||
|
new ValueRange(['values' => [array_values($row->entries()->toArray()['row'])]]),
|
||||||
|
['valueInputOption' => 'USER_ENTERED']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,188 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Фреймворк ArangoDB
|
||||||
|
use mirzaev\arangodb\connection,
|
||||||
|
mirzaev\arangodb\collection,
|
||||||
|
mirzaev\arangodb\document;
|
||||||
|
|
||||||
|
// Библиотека для ArangoDB
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
// Фреймворк для Google Sheets
|
||||||
|
use Flow\ETL\Adapter\GoogleSheet\GoogleSheetRange,
|
||||||
|
Flow\ETL\Adapter\GoogleSheet\GoogleSheetExtractor,
|
||||||
|
Flow\ETL\Adapter\GoogleSheet\Columns,
|
||||||
|
Flow\ETL\Flow,
|
||||||
|
Flow\ETL\Config,
|
||||||
|
Flow\ETL\FlowContext,
|
||||||
|
Flow\ETL\Row\Entry,
|
||||||
|
Flow\ETL\Row,
|
||||||
|
Flow\ETL\DSL\To,
|
||||||
|
Flow\ETL\DSL\From;
|
||||||
|
|
||||||
|
// Фреймворк для Google API
|
||||||
|
use Google\Client,
|
||||||
|
Google\Service\Sheets,
|
||||||
|
Google\Service\Sheets\ValueRange;
|
||||||
|
|
||||||
|
require __DIR__ . '/../../../../../vendor/autoload.php';
|
||||||
|
|
||||||
|
$arangodb = new connection(require '../settings/arangodb.php');
|
||||||
|
|
||||||
|
function generateLabel(string $name): string
|
||||||
|
{
|
||||||
|
return match ($name) {
|
||||||
|
'id', 'ID', '1', '11', '111', '1111', '11111', 'index', 'Index', 'код', 'Код', 'ключ', 'Ключ', 'айди', 'Айди', 'идентификатор', 'Идентификатор' => 'id',
|
||||||
|
'name', 'ФИО', 'фио', 'ф.и.о.', 'ф. и. о.', 'Ф.И.О.', 'Ф. И. О.' => 'name',
|
||||||
|
'phone', 'Номер', 'номер', 'телефон', 'Телефон' => 'phone',
|
||||||
|
'birth', 'Дата рождения', 'дата рождения', 'Год рождения', 'год рождения', 'Год', 'год', 'День рождения', 'день рождения' => 'birth',
|
||||||
|
'address', 'Адрес регистрации', 'адрес регистрации', 'Адрес', 'адрес', 'Регистрация', 'регистрация' => 'address',
|
||||||
|
'commentary', 'Комментарий', 'ПРИМЕЧАНИЕ', 'Примечание', 'примечание' => 'commentary',
|
||||||
|
'activity', 'Работа', 'работа', 'Вид Работы', 'Вид работы', 'вид работы' => 'activity',
|
||||||
|
'passport', 'Паспорт', 'паспорт', 'серия и номер паспорта', 'Серия и номер паспорта' => 'passport',
|
||||||
|
'issued', 'Выдан', 'выдан' => 'issued',
|
||||||
|
'department', 'Код подразделения', 'код подразделения', 'Подразделение', 'подразделение' => 'department',
|
||||||
|
'hiring', 'Дата присоединения', 'Когда устроили', 'когда устроили' => 'hiring',
|
||||||
|
'district', 'Район', 'район' => 'district',
|
||||||
|
'requisites', 'Реквизиты', 'реквизиты' => 'requisites',
|
||||||
|
'fired', 'Дата увольнения', 'дата увольнения', 'Уволен', 'уволен', 'Увольнение', 'увольнение' => 'fired',
|
||||||
|
'payment', 'Оплата', 'оплата' => 'payment',
|
||||||
|
'tax', 'ИНН', 'инн' => 'tax',
|
||||||
|
default => throw new exception("Неизвестный столбец: $name")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function degenerateLabel(string $name): string
|
||||||
|
{
|
||||||
|
return match ($name) {
|
||||||
|
'ID', 'id' => 'ID',
|
||||||
|
'ФИО', 'name' => 'ФИО',
|
||||||
|
'Номер', 'phone' => 'Номер',
|
||||||
|
'Дата рождения', 'birth' => 'Дата рождения',
|
||||||
|
'Адрес регистрации', 'address' => 'Адрес регистрации',
|
||||||
|
'Комментарий', 'commentary' => 'Комментарий',
|
||||||
|
'Работа', 'activity' => 'Работа',
|
||||||
|
'Паспорт', 'passport' => 'Паспорт',
|
||||||
|
'Выдан', 'issued' => 'Выдан',
|
||||||
|
'Код подразделения', 'department' => 'Код подразделения',
|
||||||
|
'Дата присоединения', 'hiring' => 'Дата присоединения',
|
||||||
|
'Район', 'district' => 'Район',
|
||||||
|
'Реквизиты', 'requisites' => 'Реквизиты',
|
||||||
|
'Дата увольнения', 'fired' => 'Дата увольнения',
|
||||||
|
'Оплата', 'payment' => 'Оплата',
|
||||||
|
'ИНН', 'tax' => 'ИНН',
|
||||||
|
default => throw new exception("Неизвестный столбец: $name")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(array $row, bool $reverse = false): array
|
||||||
|
{
|
||||||
|
$buffer = [];
|
||||||
|
|
||||||
|
foreach ($row as $key => $value) $buffer[(($reverse ? 'de' : null) . 'generateLabel')($key)] = $value ?? '';
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect(_document $worker, _document $robot): void
|
||||||
|
{
|
||||||
|
global $arangodb;
|
||||||
|
|
||||||
|
if (
|
||||||
|
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(),
|
||||||
|
$robot->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' => $robot->getId()]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
// Инициализировано ребро: workers -> robot (любой)
|
||||||
|
|
||||||
|
// Активация
|
||||||
|
$robot->status = 'active';
|
||||||
|
document::update($arangodb->session, $robot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectAll(_document $worker): void
|
||||||
|
{
|
||||||
|
global $arangodb;
|
||||||
|
|
||||||
|
// Инициализация ребра: workers -> viber
|
||||||
|
if (
|
||||||
|
collection::init($arangodb->session, 'viber')
|
||||||
|
&& $viber = collection::search(
|
||||||
|
$arangodb->session,
|
||||||
|
sprintf(
|
||||||
|
"FOR d IN viber FILTER d.number == '%d' RETURN d",
|
||||||
|
$worker->phone
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) connect($worker, $viber);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function sync(Row &$row, string $city = 'Красноярск'): void
|
||||||
|
{
|
||||||
|
global $arangodb;
|
||||||
|
|
||||||
|
$_row = init($row->entries()->toArray()['row']);
|
||||||
|
|
||||||
|
if ($_row['id'] !== null)
|
||||||
|
if (collection::init($arangodb->session, 'workers'))
|
||||||
|
if ($worker = collection::search($arangodb->session, sprintf("FOR d IN workers FILTER d.id == '%s' RETURN d", $_row['id']))
|
||||||
|
?? collection::search($arangodb->session, sprintf("FOR d IN workers FILTER d._id == '%s' RETURN d", document::write($arangodb->session, 'workers', $_row + ['city' => $city])))
|
||||||
|
) {
|
||||||
|
// Инициализирован работник
|
||||||
|
|
||||||
|
// Реинициализация строки с актуальными записями (приоритет у базы данных)
|
||||||
|
if ($_row !== $new = array_diff_key($worker->getAll(), ['_key' => true, 'created' => true, 'city' => true]))
|
||||||
|
$row = $row->set((new Flow())->read(From::array([init($new, true)]))->fetch(1)[0]->get('row'));
|
||||||
|
|
||||||
|
// Подключение к чат-роботам
|
||||||
|
connectAll($worker);
|
||||||
|
} else throw new exception('Не удалось создать или найти созданного работника');
|
||||||
|
else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = json_decode(require('../settings/workers/google.php'), true);
|
||||||
|
$document = require('../settings/workers/document.php');
|
||||||
|
$sheets = require('../settings/workers/sheets.php');
|
||||||
|
|
||||||
|
$client = new Client();
|
||||||
|
$client->setScopes(Sheets::SPREADSHEETS);
|
||||||
|
$client->setAuthConfig($settings);
|
||||||
|
$api = new Sheets($client);
|
||||||
|
|
||||||
|
foreach ($sheets as $sheet) {
|
||||||
|
$rows = (new Flow())->read(new GoogleSheetExtractor($api, $document, new Columns($sheet, 'A', 'P'), true, 1000, 'row'));
|
||||||
|
|
||||||
|
$i = 1;
|
||||||
|
foreach ($rows->fetch(5000) as $row) {
|
||||||
|
++$i;
|
||||||
|
$buffer = $row;
|
||||||
|
sync($row, $sheet);
|
||||||
|
if ($buffer !== $row) {
|
||||||
|
$api->spreadsheets_values->update(
|
||||||
|
$document,
|
||||||
|
"$sheet!A$i:P$i",
|
||||||
|
new ValueRange(['values' => [array_values($row->entries()->toArray()['row'])]]),
|
||||||
|
['valueInputOption' => 'USER_ENTERED']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Фреймворк ArangoDB
|
||||||
|
use mirzaev\arangodb\connection,
|
||||||
|
mirzaev\arangodb\collection,
|
||||||
|
mirzaev\arangodb\document;
|
||||||
|
|
||||||
|
// Библиотека для ArangoDB
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
// Фреймворк для Google Sheets
|
||||||
|
use Flow\ETL\Adapter\GoogleSheet\GoogleSheetRange,
|
||||||
|
Flow\ETL\Adapter\GoogleSheet\GoogleSheetExtractor,
|
||||||
|
Flow\ETL\Adapter\GoogleSheet\Columns,
|
||||||
|
Flow\ETL\Flow,
|
||||||
|
Flow\ETL\Config,
|
||||||
|
Flow\ETL\FlowContext,
|
||||||
|
Flow\ETL\Row\Entry,
|
||||||
|
Flow\ETL\Row,
|
||||||
|
Flow\ETL\DSL\To,
|
||||||
|
Flow\ETL\DSL\From;
|
||||||
|
|
||||||
|
// Фреймворк для Google API
|
||||||
|
use Google\Client,
|
||||||
|
Google\Service\Sheets,
|
||||||
|
Google\Service\Sheets\ValueRange;
|
||||||
|
|
||||||
|
require __DIR__ . '/../../../../../vendor/autoload.php';
|
||||||
|
|
||||||
|
$arangodb = new connection(require '../settings/arangodb.php');
|
||||||
|
|
||||||
|
function generateLabel(string $name): string
|
||||||
|
{
|
||||||
|
return match ($name) {
|
||||||
|
'_id', 'ID' => '_id',
|
||||||
|
'market', 'Магазин' => 'market',
|
||||||
|
'worker', 'Сотрудник' => 'worker',
|
||||||
|
'work', 'Работа' => 'work',
|
||||||
|
'date', 'Дата' => 'date',
|
||||||
|
'start', 'Начало' => 'start',
|
||||||
|
'end', 'Конец' => 'end',
|
||||||
|
'confirmed', 'Подтверждено' => 'confirmed',
|
||||||
|
'commentary', 'Комментарий' => 'commentary',
|
||||||
|
'response', 'Ответ' => 'response',
|
||||||
|
default => throw new exception("Неизвестный столбец: $name")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function degenerateLabel(string $name): string
|
||||||
|
{
|
||||||
|
return match ($name) {
|
||||||
|
'ID', '_id' => 'ID',
|
||||||
|
'Магазин', 'market' => 'Магазин',
|
||||||
|
'Сотрудник', 'worker' => 'Сотрудник',
|
||||||
|
'Работа', 'work' => 'Работа',
|
||||||
|
'Дата', 'date' => 'Дата',
|
||||||
|
'Начало', 'start' => 'Начало',
|
||||||
|
'Конец', 'end' => 'Конец',
|
||||||
|
'Подтверждено', 'confirmed' => 'Подтверждено',
|
||||||
|
'Комментарий', 'commentary' => 'Комментарий',
|
||||||
|
'Ответ', 'response' => 'Ответ',
|
||||||
|
default => throw new exception("Неизвестный столбец: $name")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(array $row, bool $reverse = false): array
|
||||||
|
{
|
||||||
|
$buffer = [];
|
||||||
|
|
||||||
|
foreach ($row as $key => $value) $buffer[(($reverse ? 'de' : null) . 'generateLabel')($key)] = $value;
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function sync(Row &$row): void
|
||||||
|
{
|
||||||
|
global $arangodb;
|
||||||
|
|
||||||
|
$_row = init($row->entries()->toArray()['row']);
|
||||||
|
|
||||||
|
if (collection::init($arangodb->session, 'works'))
|
||||||
|
if (!empty($_row['_id']) && $work = collection::search($arangodb->session, sprintf("FOR d IN works FILTER d._id == '%s' RETURN d", $_row['_id']))) {
|
||||||
|
// Найдена запись работы (строки) в базе данных
|
||||||
|
|
||||||
|
// Очистка перед записью в таблицу
|
||||||
|
$new = array_diff_key($work->getAll(), ['_key' => true, 'created' => true]);
|
||||||
|
|
||||||
|
// Инициализация выбранного сотрудника
|
||||||
|
if (collection::init($arangodb->session, 'readinesses', true) && collection::init($arangodb->session, 'workers'))
|
||||||
|
$new = ['worker' => collection::search(
|
||||||
|
$arangodb->session,
|
||||||
|
sprintf(
|
||||||
|
"FOR d IN workers LET e = (FOR e IN readinesses FILTER e._to == '%s' RETURN e._from)[0] FILTER d._id == e RETURN d",
|
||||||
|
$_row['_id']
|
||||||
|
)
|
||||||
|
)->id ?? ''] + $new;
|
||||||
|
else throw new exception('Не удалось инициализировать коллекции');
|
||||||
|
|
||||||
|
// Инициализация магазина
|
||||||
|
if (collection::init($arangodb->session, 'requests', true) && collection::init($arangodb->session, 'markets'))
|
||||||
|
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",
|
||||||
|
$_row['_id']
|
||||||
|
)
|
||||||
|
)) $new = ['market' => $market->id] + $new;
|
||||||
|
else throw new exception('Не удалось найти магазин');
|
||||||
|
else throw new exception('Не удалось инициализировать коллекции');
|
||||||
|
|
||||||
|
// Запись идентификатора только что созданной записи в базе данных для записи в таблицу
|
||||||
|
$new = ['_id' => $work->getId()] + $new;
|
||||||
|
|
||||||
|
// Реинициализация строки с новыми данными по ссылке (приоритет из базы данных)
|
||||||
|
if ($_row !== $new) $row = $row->set((new Flow())->read(From::array([init($new, true)]))->fetch(1)[0]->get('row'));
|
||||||
|
} else if (
|
||||||
|
!empty($_row['market'])
|
||||||
|
&& collection::init($arangodb->session, 'requests', true) && collection::init($arangodb->session, 'markets')
|
||||||
|
&& ($market = collection::search($arangodb->session, sprintf("FOR d IN markets FILTER d.id == '%s' RETURN d", $_row['market'])))
|
||||||
|
&& $work = collection::search(
|
||||||
|
$arangodb->session,
|
||||||
|
sprintf(
|
||||||
|
"FOR d IN works FILTER d._id == '%s' RETURN d",
|
||||||
|
document::write($arangodb->session, 'works', array_diff_key($_row, ['_id' => true, 'market' => true, 'worker' => true]))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Не найдена запись работы (строки) в базе данных и была создана
|
||||||
|
|
||||||
|
// Инициализация ребра: market -> work (запрос магазина о работе)
|
||||||
|
if (collection::search(
|
||||||
|
$arangodb->session,
|
||||||
|
sprintf(
|
||||||
|
"FOR d IN requests FILTER d._id == '%s' RETURN d",
|
||||||
|
document::write($arangodb->session, 'requests', ['_from' => $market->getId(), '_to' => $work->getId()])
|
||||||
|
)
|
||||||
|
));
|
||||||
|
else throw new exception('Не удалось создать заявку магазина');
|
||||||
|
|
||||||
|
if (
|
||||||
|
!empty($_row['worker'])
|
||||||
|
&& collection::init($arangodb->session, 'readinesses', true) && collection::init($arangodb->session, 'workers')
|
||||||
|
&& ($worker = collection::search($arangodb->session, sprintf("FOR d IN workers FILTER d.id == '%s' RETURN d", $_row['worker'])))
|
||||||
|
) {
|
||||||
|
// Инициализация ребра: workers -> 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()])
|
||||||
|
)
|
||||||
|
));
|
||||||
|
else throw new exception('Не удалось создать готовность сотрудника');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Реинициализация строки с новыми данными по ссылке (приоритет из базы данных)
|
||||||
|
$row = $row->set((new Flow())->read(From::array([init(['_id' => $work->getId()] + $_row, true)]))->fetch(1)[0]->get('row'));
|
||||||
|
} else return;
|
||||||
|
else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = json_decode(require('../settings/works/google.php'), true);
|
||||||
|
$document = require('../settings/works/document.php');
|
||||||
|
$sheets = require('../settings/works/sheets.php');
|
||||||
|
|
||||||
|
$client = new Client();
|
||||||
|
$client->setScopes(Sheets::SPREADSHEETS);
|
||||||
|
$client->setAuthConfig($settings);
|
||||||
|
|
||||||
|
foreach ($sheets as $sheet) {
|
||||||
|
$sheets = new Sheets($client);
|
||||||
|
|
||||||
|
$rows = (new Flow())->read(new GoogleSheetExtractor($sheets, $document, new Columns($sheet, 'A', 'J'), true, 1000, 'row'));
|
||||||
|
|
||||||
|
$i = 1;
|
||||||
|
|
||||||
|
foreach ($rows->fetch(10000) as $row) {
|
||||||
|
++$i;
|
||||||
|
$buffer = $row;
|
||||||
|
sync($row);
|
||||||
|
if ($buffer !== $row)
|
||||||
|
$sheets->spreadsheets_values->update(
|
||||||
|
$document,
|
||||||
|
"$sheet!A$i:J$i",
|
||||||
|
new ValueRange(['values' => [array_values($row->entries()->toArray()['row'])]]),
|
||||||
|
['valueInputOption' => 'USER_ENTERED']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
*
|
||||||
|
!*/
|
||||||
|
!.gitignore
|
||||||
|
!*.sample
|
|
@ -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,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return <<<'JSON'
|
||||||
|
{
|
||||||
|
"type": "",
|
||||||
|
"project_id": "",
|
||||||
|
"private_key_id": "",
|
||||||
|
"private_key": "",
|
||||||
|
"client_email": "",
|
||||||
|
"client_id": "",
|
||||||
|
"auth_uri": "",
|
||||||
|
"token_uri": "",
|
||||||
|
"auth_provider_x509_cert_url": "",
|
||||||
|
"client_x509_cert_url": "",
|
||||||
|
"universe_domain": ""
|
||||||
|
}
|
||||||
|
JSON;
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'Ангарск',
|
||||||
|
'Байкальск',
|
||||||
|
'Бирюсинск',
|
||||||
|
'Братск',
|
||||||
|
'Вихоревка',
|
||||||
|
'Зима',
|
||||||
|
'Иркутск',
|
||||||
|
'Нижнеудинск',
|
||||||
|
'Юрты',
|
||||||
|
'Чунский',
|
||||||
|
'Саянск',
|
||||||
|
'Тулун',
|
||||||
|
'Черемхово',
|
||||||
|
'Абакан',
|
||||||
|
'Зыково',
|
||||||
|
'Берёзовка',
|
||||||
|
'Емельяново',
|
||||||
|
'Дивногорск',
|
||||||
|
'Железногорск',
|
||||||
|
'Канск',
|
||||||
|
'Усолье-Сибирское',
|
||||||
|
'Тайшет',
|
||||||
|
'Красноярск',
|
||||||
|
'Лесосибирск'
|
||||||
|
];
|
|
@ -0,0 +1,3 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
!*.sample
|
|
@ -0,0 +1,3 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return '';
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return <<<'JSON'
|
||||||
|
{
|
||||||
|
"type": "",
|
||||||
|
"project_id": "",
|
||||||
|
"private_key_id": "",
|
||||||
|
"private_key": "",
|
||||||
|
"client_email": "",
|
||||||
|
"client_id": "",
|
||||||
|
"auth_uri": "",
|
||||||
|
"token_uri": "",
|
||||||
|
"auth_provider_x509_cert_url": "",
|
||||||
|
"client_x509_cert_url": "",
|
||||||
|
"universe_domain": ""
|
||||||
|
}
|
||||||
|
JSON;
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'Красноярск'
|
||||||
|
];
|
|
@ -0,0 +1,3 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
!*.sample
|
|
@ -0,0 +1,3 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return '';
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return <<<'JSON'
|
||||||
|
{
|
||||||
|
"type": "",
|
||||||
|
"project_id": "",
|
||||||
|
"private_key_id": "",
|
||||||
|
"private_key": "",
|
||||||
|
"client_email": "",
|
||||||
|
"client_id": "",
|
||||||
|
"auth_uri": "",
|
||||||
|
"token_uri": "",
|
||||||
|
"auth_provider_x509_cert_url": "",
|
||||||
|
"client_x509_cert_url": "",
|
||||||
|
"universe_domain": ""
|
||||||
|
}
|
||||||
|
JSON;
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?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