Merge branch 'tg_webhook'
This commit is contained in:
@@ -1,5 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Bastion\ApplicationFactory;
|
||||||
|
use Openguru\OpenCartFramework\Logger\OpenCartLogAdapter;
|
||||||
|
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
|
||||||
|
use Openguru\OpenCartFramework\OpenCart\OcConfigDecorator;
|
||||||
|
|
||||||
|
$sysLibPath = rtrim(DIR_SYSTEM, '/') . '/library/oc_telegram_shop';
|
||||||
|
$basePath = rtrim(DIR_APPLICATION, '/') . '/..';
|
||||||
|
if (is_readable($sysLibPath . '/oc_telegram_shop.phar')) {
|
||||||
|
require_once "phar://{$sysLibPath}/oc_telegram_shop.phar/vendor/autoload.php";
|
||||||
|
} elseif (is_dir("$basePath/oc_telegram_shop")) {
|
||||||
|
require_once "$basePath/oc_telegram_shop/vendor/autoload.php";
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException('Unable to locate application directory.');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property Document $document
|
* @property Document $document
|
||||||
* @property Loader $load
|
* @property Loader $load
|
||||||
@@ -163,6 +178,30 @@ class ControllerExtensionModuleTgshop extends Controller
|
|||||||
$this->response->setOutput($this->load->view('extension/module/tgshop_init', $data));
|
$this->response->setOutput($this->load->view('extension/module/tgshop_init', $data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$app = ApplicationFactory::create([
|
||||||
|
'base_url' => HTTPS_SERVER,
|
||||||
|
'public_url' => HTTPS_CATALOG,
|
||||||
|
'telegram' => [
|
||||||
|
'bot_token' => $this->config->get('module_tgshop_bot_token'),
|
||||||
|
'chat_id' => $this->config->get('module_tgshop_chat_id'),
|
||||||
|
'owner_notification_template' => $this->config->get('module_tgshop_owner_notification_template'),
|
||||||
|
'customer_notification_template' => $this->config->get('module_tgshop_customer_notification_template'),
|
||||||
|
],
|
||||||
|
'logs' => [
|
||||||
|
'path' => DIR_LOGS,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$app->bind(OcRegistryDecorator::class, fn () => new OcRegistryDecorator($this->registry));
|
||||||
|
|
||||||
|
$app
|
||||||
|
->withLogger(fn () => new OpenCartLogAdapter($this->log, 'TeleCartAdmin'))
|
||||||
|
->bootAndHandleRequest();
|
||||||
|
}
|
||||||
|
|
||||||
protected function validate(): bool
|
protected function validate(): bool
|
||||||
{
|
{
|
||||||
if (! $this->user->hasPermission('modify', 'extension/module/tgshop')) {
|
if (! $this->user->hasPermission('modify', 'extension/module/tgshop')) {
|
||||||
|
|||||||
@@ -146,6 +146,7 @@
|
|||||||
|
|
||||||
{# ChatID #}
|
{# ChatID #}
|
||||||
{% elseif item['type'] == 'chatid' %}
|
{% elseif item['type'] == 'chatid' %}
|
||||||
|
{% if module_tgshop_bot_token %}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button id="{{ settingKey }}-btn" class="btn btn-primary" type="button">
|
<button id="{{ settingKey }}-btn" class="btn btn-primary" type="button">
|
||||||
@@ -161,44 +162,37 @@
|
|||||||
/>
|
/>
|
||||||
<script>
|
<script>
|
||||||
$('#{{ settingKey }}-btn').click(function () {
|
$('#{{ settingKey }}-btn').click(function () {
|
||||||
|
const $resultLabel = $('#{{ settingKey }}-result-label');
|
||||||
const telegramToken = $('#module_tgshop_bot_token').val().trim(); // fetch from input
|
const telegramToken = $('#module_tgshop_bot_token').val().trim(); // fetch from input
|
||||||
if (! telegramToken) {
|
if (! telegramToken) {
|
||||||
alert('Сначала введите Telegram Bot Token!');
|
alert('Сначала введите Telegram Bot Token!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(`https://api.telegram.org/bot${telegramToken}/getUpdates`)
|
fetch('/admin/index.php?route=extension/module/tgshop/handle&api_action=getChatId&user_token={{ user_token }}')
|
||||||
.then(res => res.json())
|
.then(async (res) => {
|
||||||
.then(data => {
|
const data = await res.json().catch(() => null);
|
||||||
if (!data.ok || !data.result.length) {
|
|
||||||
alert('Не удалось получить обновления от бота. Убедитесь, что вы написали боту нужное кодовое слово.');
|
if (!res.ok) {
|
||||||
return;
|
throw new Error(`Ошибка ${res.status}: ${data.message || res.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ищем последнее сообщение с chat_id
|
$('#{{ settingKey }}').val(data.data.chat_id);
|
||||||
const lastMessage = data.result.reverse().find(update => update.message && update.message.chat);
|
$resultLabel
|
||||||
if (!lastMessage) {
|
.text('✅ ChatID успешно получен и подставлен в поле. Не забудьте сохранить настройки!')
|
||||||
alert('Не найдено сообщений с chat_id.');
|
.css('color', 'green');
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastMessage.message.text !== 'opencart_get_chatid') {
|
|
||||||
alert('Ошибка. Последнее сообщение у бота не содержит правильного кодового слова.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const chatId = lastMessage.message.chat.id;
|
|
||||||
$('#{{ settingKey }}').val(chatId); // подставляем в поле
|
|
||||||
alert('ChatID успешно получен и подставлен в поле.')
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
alert('Ошибка при получении chat_id. Проверьте токен и соединение.');
|
alert(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="{{ settingKey }}-result-label"></div>
|
||||||
|
|
||||||
<button class="btn btn-link btn-xs" type="button" data-toggle="collapse" data-target="#{{ settingKey }}-collapse" aria-expanded="false" aria-controls="collapseExample">
|
<button class="btn btn-link btn-xs" type="button" data-toggle="collapse" data-target="#{{ settingKey }}-collapse" aria-expanded="false" aria-controls="collapseExample">
|
||||||
Инструкция как получить ChatID.
|
Инструкция как получить ChatID.
|
||||||
</button>
|
</button>
|
||||||
@@ -212,7 +206,11 @@
|
|||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>BotToken</strong> не указан. Пожалуйста, введите корректный BotToken и сохраните настройки. После этого здесь станет доступна настройка ChatID.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% elseif item['type'] == 'tg_message_template' %}
|
{% elseif item['type'] == 'tg_message_template' %}
|
||||||
<div style="margin-bottom: 10px;">
|
<div style="margin-bottom: 10px;">
|
||||||
<textarea name="{{ settingKey }}"
|
<textarea name="{{ settingKey }}"
|
||||||
@@ -340,6 +338,7 @@
|
|||||||
const $input = $('#{{ settingKey }}');
|
const $input = $('#{{ settingKey }}');
|
||||||
const $resultLabel = $('#{{ settingKey }}-result-label');
|
const $resultLabel = $('#{{ settingKey }}-result-label');
|
||||||
const botToken = $input.val();
|
const botToken = $input.val();
|
||||||
|
const url = '/admin/index.php?route=extension/module/tgshop/handle&api_action=configureBotToken&user_token={{ user_token }}';
|
||||||
|
|
||||||
if (botToken.trim().length === 0) {
|
if (botToken.trim().length === 0) {
|
||||||
$resultLabel
|
$resultLabel
|
||||||
@@ -348,34 +347,46 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$input.attr('disabled', true);
|
$input.attr('readonly', true);
|
||||||
$resultLabel.text('Проверяю...');
|
$resultLabel.text('Проверяю...');
|
||||||
|
|
||||||
$.ajax({
|
fetch(url, {
|
||||||
url: `https://api.telegram.org/bot${botToken}/getMe`,
|
method: 'POST',
|
||||||
method: 'GET',
|
headers: {
|
||||||
dataType: 'json',
|
'Content-Type': 'application/json',
|
||||||
success: function (resp) {
|
},
|
||||||
if (resp.ok) {
|
body: JSON.stringify({ botToken }),
|
||||||
const user = resp.result;
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
const response = await res.json().catch(() => null);
|
||||||
|
|
||||||
|
if (res.status === 422) {
|
||||||
|
console.error(res, response);
|
||||||
$resultLabel
|
$resultLabel
|
||||||
.text(`✅ Бот: @${user.username} (id: ${user.id})`)
|
.text(`❌ Ошибка: ${response.error}`)
|
||||||
|
.css('color', 'red');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Ошибка ${response.error || res.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! response.id) {
|
||||||
|
throw new Error(`bot token is not found in server response.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultLabel
|
||||||
|
.text(`✅ Бот: @${response.username} (id: ${response.id}) webhook: ${response.webhook_url}`)
|
||||||
.css('color', 'green');
|
.css('color', 'green');
|
||||||
} else {
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
$resultLabel
|
$resultLabel
|
||||||
.text(`❌ Ошибка: ${resp.description || 'неверный токен'}`)
|
.text(`❌ Ошибка проверки BotToken.`)
|
||||||
.css('color', 'red');
|
.css('color', 'red');
|
||||||
}
|
})
|
||||||
},
|
.finally(() => $input.attr('readonly', false))
|
||||||
error: function (xhr) {
|
|
||||||
$resultLabel
|
|
||||||
.text(`❌ Ошибка соединения (${xhr.status})`)
|
|
||||||
.css('color', 'red');
|
|
||||||
},
|
|
||||||
complete: function () {
|
|
||||||
$input.attr('disabled', false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
use App\Adapters\OcModelCatalogProductAdapter;
|
use App\Adapters\OcModelCatalogProductAdapter;
|
||||||
use App\ApplicationFactory;
|
use App\ApplicationFactory;
|
||||||
use App\Decorators\OcRegistryDecorator;
|
|
||||||
use Cart\Cart;
|
use Cart\Cart;
|
||||||
use Cart\Currency;
|
use Cart\Currency;
|
||||||
use Cart\Tax;
|
use Cart\Tax;
|
||||||
use Openguru\OpenCartFramework\ImageTool\ImageTool;
|
use Openguru\OpenCartFramework\ImageTool\ImageTool;
|
||||||
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
|
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
|
||||||
use Openguru\OpenCartFramework\Logger\OpenCartLogAdapter;
|
use Openguru\OpenCartFramework\Logger\OpenCartLogAdapter;
|
||||||
|
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
|
||||||
|
|
||||||
$sysLibPath = rtrim(DIR_SYSTEM, '/') . '/library/oc_telegram_shop';
|
$sysLibPath = rtrim(DIR_SYSTEM, '/') . '/library/oc_telegram_shop';
|
||||||
$basePath = rtrim(DIR_APPLICATION, '/') . '/..';
|
$basePath = rtrim(DIR_APPLICATION, '/') . '/..';
|
||||||
@@ -17,7 +17,7 @@ if (is_readable($sysLibPath . '/oc_telegram_shop.phar')) {
|
|||||||
} elseif (is_dir("$basePath/oc_telegram_shop")) {
|
} elseif (is_dir("$basePath/oc_telegram_shop")) {
|
||||||
require_once "$basePath/oc_telegram_shop/vendor/autoload.php";
|
require_once "$basePath/oc_telegram_shop/vendor/autoload.php";
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException('Unable to locate bulk products directory.');
|
throw new RuntimeException('Unable to locate application directory.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,6 +52,7 @@ class Controllerextensiontgshophandle extends Controller
|
|||||||
'theme_dark' => $this->config->get('module_tgshop_theme_dark'),
|
'theme_dark' => $this->config->get('module_tgshop_theme_dark'),
|
||||||
'mainpage_products' => $this->config->get('module_tgshop_mainpage_products'),
|
'mainpage_products' => $this->config->get('module_tgshop_mainpage_products'),
|
||||||
'featured_products' => $this->config->get('module_tgshop_featured_products'),
|
'featured_products' => $this->config->get('module_tgshop_featured_products'),
|
||||||
|
'base_url' => HTTPS_SERVER,
|
||||||
'ya_metrika_enabled' => ! empty(trim($this->config->get('module_tgshop_yandex_metrika'))),
|
'ya_metrika_enabled' => ! empty(trim($this->config->get('module_tgshop_yandex_metrika'))),
|
||||||
'telegram' => [
|
'telegram' => [
|
||||||
'bot_token' => $this->config->get('module_tgshop_bot_token'),
|
'bot_token' => $this->config->get('module_tgshop_bot_token'),
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Bastion;
|
||||||
|
|
||||||
|
use App\ServiceProviders\AppServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\Application;
|
||||||
|
use Openguru\OpenCartFramework\Cache\CacheServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\QueryBuilderServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\Router\RouteServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
use Openguru\OpenCartFramework\Telegram\TelegramServiceProvider;
|
||||||
|
|
||||||
|
class ApplicationFactory
|
||||||
|
{
|
||||||
|
public static function create(array $config): Application
|
||||||
|
{
|
||||||
|
$defaultConfig = require __DIR__ . '/config.php';
|
||||||
|
$routes = require __DIR__ . '/routes.php';
|
||||||
|
|
||||||
|
return (new Application(Arr::mergeArraysRecursively($defaultConfig, $config)))
|
||||||
|
->withRoutes(fn () => $routes)
|
||||||
|
->withServiceProviders([
|
||||||
|
QueryBuilderServiceProvider::class,
|
||||||
|
RouteServiceProvider::class,
|
||||||
|
AppServiceProvider::class,
|
||||||
|
CacheServiceProvider::class,
|
||||||
|
TelegramServiceProvider::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bastion\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class BotTokenConfiguratorException extends Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bastion\Handlers;
|
||||||
|
|
||||||
|
use Bastion\Exceptions\BotTokenConfiguratorException;
|
||||||
|
use Bastion\Services\BotTokenConfigurator;
|
||||||
|
use Exception;
|
||||||
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
|
use Openguru\OpenCartFramework\Http\Request;
|
||||||
|
use Openguru\OpenCartFramework\Http\Response;
|
||||||
|
|
||||||
|
class SettingsHandler
|
||||||
|
{
|
||||||
|
private BotTokenConfigurator $botTokenConfigurator;
|
||||||
|
|
||||||
|
public function __construct(BotTokenConfigurator $botTokenConfigurator)
|
||||||
|
{
|
||||||
|
$this->botTokenConfigurator = $botTokenConfigurator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureBotToken(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$data = $this->botTokenConfigurator->configure(trim($request->json('botToken', '')));
|
||||||
|
return new JsonResponse($data);
|
||||||
|
} catch (BotTokenConfiguratorException $e) {
|
||||||
|
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bastion\Handlers;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Openguru\OpenCartFramework\Cache\CacheInterface;
|
||||||
|
use Openguru\OpenCartFramework\Config\Settings;
|
||||||
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
|
use Openguru\OpenCartFramework\Http\Response;
|
||||||
|
use Openguru\OpenCartFramework\Router\Router;
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
use Openguru\OpenCartFramework\Telegram\TelegramService;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class TelegramHandler
|
||||||
|
{
|
||||||
|
private CacheInterface $cache;
|
||||||
|
|
||||||
|
public function __construct(CacheInterface $cache)
|
||||||
|
{
|
||||||
|
$this->cache = $cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChatId(): JsonResponse
|
||||||
|
{
|
||||||
|
$message = $this->cache->get('tg_latest_msg');
|
||||||
|
|
||||||
|
if (! $message) {
|
||||||
|
return new JsonResponse([
|
||||||
|
'message' => 'Сообщение не найдено. Убедитесь что отправили кодовое слово в чат с ботом и повторите через 10 секунд. У Вас есть 60 секунд после отправки сообщения в чат, чтобы нажать на кнопку! Это сделано в целях безопасности.'
|
||||||
|
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$chatId = Arr::get($message, 'chat.id');
|
||||||
|
|
||||||
|
if (! $chatId) {
|
||||||
|
return new JsonResponse([
|
||||||
|
'message' => 'ChatID не найден. Убедитесь что отправили кодовое слово в чат с ботом и повторите через 10 секунд.'
|
||||||
|
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'data' => [
|
||||||
|
'chat_id' => $chatId,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Bastion\Services;
|
||||||
|
|
||||||
|
use Bastion\Exceptions\BotTokenConfiguratorException;
|
||||||
|
use Exception;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
|
use Openguru\OpenCartFramework\Config\Settings;
|
||||||
|
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
||||||
|
use Openguru\OpenCartFramework\Router\Router;
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
use Openguru\OpenCartFramework\Telegram\TelegramClientException;
|
||||||
|
use Openguru\OpenCartFramework\Telegram\TelegramService;
|
||||||
|
|
||||||
|
class BotTokenConfigurator
|
||||||
|
{
|
||||||
|
private TelegramService $telegramService;
|
||||||
|
private Settings $settings;
|
||||||
|
private Router $router;
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
TelegramService $telegramService,
|
||||||
|
Settings $settings,
|
||||||
|
Router $router,
|
||||||
|
LoggerInterface $logger
|
||||||
|
) {
|
||||||
|
$this->telegramService = $telegramService;
|
||||||
|
$this->settings = $settings;
|
||||||
|
$this->router = $router;
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws BotTokenConfiguratorException
|
||||||
|
*/
|
||||||
|
public function configure(string $botToken): array
|
||||||
|
{
|
||||||
|
$this->telegramService->setBotToken($botToken);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$me = $this->telegramService->exec('getMe');
|
||||||
|
$webhookUrl = $this->telegramService->getWebhookUrl();
|
||||||
|
|
||||||
|
if (! $webhookUrl) {
|
||||||
|
$this->telegramService->exec('setWebhook', [
|
||||||
|
'url' => $this->getWebhookUrl(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$webhookUrl = $this->telegramService->getWebhookUrl();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'first_name' => Arr::get($me, 'result.first_name'),
|
||||||
|
'username' => Arr::get($me, 'result.username'),
|
||||||
|
'id' => Arr::get($me, 'result.id'),
|
||||||
|
'webhook_url' => $webhookUrl,
|
||||||
|
];
|
||||||
|
} catch (TelegramClientException $exception) {
|
||||||
|
$this->logger->logException($exception);
|
||||||
|
if ($exception->getCode() === 404 || $exception->getCode() === 401) {
|
||||||
|
throw new BotTokenConfiguratorException(
|
||||||
|
'Telegram сообщает, что BotToken не верный. Проверьте корректность.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BotTokenConfiguratorException($exception->getMessage());
|
||||||
|
} catch (Exception|GuzzleException $exception) {
|
||||||
|
$this->logger->logException($exception);
|
||||||
|
throw new BotTokenConfiguratorException($exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getWebhookUrl(): string
|
||||||
|
{
|
||||||
|
$publicUrl = rtrim($this->settings->get('public_url'), '/');
|
||||||
|
|
||||||
|
if (! $publicUrl) {
|
||||||
|
throw new BotTokenConfiguratorException('Public URL is not set in configuration.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$webhook = $this->router->url($this->settings->get('tg_webhook_handler', 'webhook'));
|
||||||
|
|
||||||
|
return $publicUrl . $webhook;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
module/oc_telegram_shop/upload/oc_telegram_shop/bastion/config.php
Executable file
20
module/oc_telegram_shop/upload/oc_telegram_shop/bastion/config.php
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'config_timezone' => 'UTC',
|
||||||
|
'lang' => 'en-gb',
|
||||||
|
'language_id' => 1,
|
||||||
|
'auth_user_id' => 0,
|
||||||
|
'base_url' => '/',
|
||||||
|
|
||||||
|
'db' => [
|
||||||
|
'host' => 'localhost',
|
||||||
|
'database' => 'not_set',
|
||||||
|
'username' => 'not_set',
|
||||||
|
'password' => 'not_set',
|
||||||
|
],
|
||||||
|
|
||||||
|
'logs' => [
|
||||||
|
'path' => 'not_set',
|
||||||
|
],
|
||||||
|
];
|
||||||
9
module/oc_telegram_shop/upload/oc_telegram_shop/bastion/routes.php
Executable file
9
module/oc_telegram_shop/upload/oc_telegram_shop/bastion/routes.php
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Bastion\Handlers\SettingsHandler;
|
||||||
|
use Bastion\Handlers\TelegramHandler;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'configureBotToken' => [SettingsHandler::class, 'configureBotToken'],
|
||||||
|
'getChatId' => [TelegramHandler::class, 'getChatId'],
|
||||||
|
];
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Openguru\\OpenCartFramework\\": "framework/",
|
"Openguru\\OpenCartFramework\\": "framework/",
|
||||||
"App\\": "src/",
|
"App\\": "src/",
|
||||||
|
"Bastion\\": "bastion/",
|
||||||
"Tests\\": "tests/"
|
"Tests\\": "tests/"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ class Application extends Container
|
|||||||
private static array $events = [];
|
private static array $events = [];
|
||||||
private array $serviceProviders = [];
|
private array $serviceProviders = [];
|
||||||
private array $middlewareStack = [];
|
private array $middlewareStack = [];
|
||||||
|
private array $routes = [];
|
||||||
|
|
||||||
private LoggerInterface $logger;
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
public function __construct(array $config)
|
public function __construct(array $config)
|
||||||
@@ -146,4 +148,16 @@ class Application extends Container
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withRoutes(callable $callback): Application
|
||||||
|
{
|
||||||
|
$this->routes = $callback();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRoutes(): array
|
||||||
|
{
|
||||||
|
return $this->routes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,14 @@
|
|||||||
|
|
||||||
namespace Openguru\OpenCartFramework\Cache;
|
namespace Openguru\OpenCartFramework\Cache;
|
||||||
|
|
||||||
use Openguru\OpenCartFramework\Config\Settings;
|
|
||||||
use Openguru\OpenCartFramework\Container\Container;
|
|
||||||
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
|
||||||
|
|
||||||
class CacheServiceProvider extends ServiceProvider
|
class CacheServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
$this->container->singleton(CacheInterface::class, function (Container $container) {
|
$this->container->singleton(CacheInterface::class, function () {
|
||||||
return new DatabaseCache(
|
return new SymfonyFilesystemCache('app.cache', 0, DIR_CACHE);
|
||||||
$container->get(ConnectionInterface::class),
|
|
||||||
$container->get(Settings::class)->get('tables.cache'),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Cache;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||||
|
|
||||||
|
class SymfonyFilesystemCache implements CacheInterface
|
||||||
|
{
|
||||||
|
private FilesystemAdapter $cache;
|
||||||
|
|
||||||
|
public function __construct(string $namespace = 'app.cache', int $defaultLifetime = 0, ?string $cacheDir = null)
|
||||||
|
{
|
||||||
|
$this->cache = new FilesystemAdapter($namespace, $defaultLifetime, $cacheDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $key)
|
||||||
|
{
|
||||||
|
$item = $this->cache->getItem($key);
|
||||||
|
|
||||||
|
return $item->isHit() ? $item->get() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(string $key, $value, ?int $ttlSeconds = null): void
|
||||||
|
{
|
||||||
|
$item = $this->cache->getItem($key);
|
||||||
|
$item->set($value);
|
||||||
|
|
||||||
|
if ($ttlSeconds) {
|
||||||
|
$item->expiresAfter($ttlSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cache->save($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(string $key): void
|
||||||
|
{
|
||||||
|
$this->cache->deleteItem($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clear(): void
|
||||||
|
{
|
||||||
|
$this->cache->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Decorators;
|
namespace Openguru\OpenCartFramework\OpenCart\Decorators;
|
||||||
|
|
||||||
use Cart\Cart;
|
use Cart\Cart;
|
||||||
use Cart\Currency;
|
use Cart\Currency;
|
||||||
use Config;
|
use Config;
|
||||||
use Loader;
|
use Loader;
|
||||||
|
use ModelCatalogProduct;
|
||||||
|
use ModelSettingSetting;
|
||||||
use Registry;
|
use Registry;
|
||||||
use Session;
|
use Session;
|
||||||
|
|
||||||
@@ -15,7 +17,8 @@ use Session;
|
|||||||
* @property Session $session
|
* @property Session $session
|
||||||
* @property Currency $currency
|
* @property Currency $currency
|
||||||
* @property Config $config
|
* @property Config $config
|
||||||
* @property \ModelCatalogProduct $model_catalog_product
|
* @property ModelCatalogProduct $model_catalog_product
|
||||||
|
* @property ModelSettingSetting $model_setting_setting
|
||||||
*/
|
*/
|
||||||
class OcRegistryDecorator
|
class OcRegistryDecorator
|
||||||
{
|
{
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Openguru\OpenCartFramework\Router;
|
namespace Openguru\OpenCartFramework\Router;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Application;
|
||||||
use Openguru\OpenCartFramework\Container\Container;
|
use Openguru\OpenCartFramework\Container\Container;
|
||||||
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||||
|
|
||||||
@@ -10,7 +11,9 @@ class RouteServiceProvider extends ServiceProvider
|
|||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
$this->container->singleton(Router::class, function (Container $container) {
|
$this->container->singleton(Router::class, function (Container $container) {
|
||||||
return new Router();
|
$routes = Application::getInstance()->getRoutes();
|
||||||
|
|
||||||
|
return new Router($routes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Telegram;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class TelegramClientException extends Exception
|
||||||
|
{
|
||||||
|
public function __construct($message, $code = 0, Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,16 +3,17 @@
|
|||||||
namespace Openguru\OpenCartFramework\Telegram;
|
namespace Openguru\OpenCartFramework\Telegram;
|
||||||
|
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
|
||||||
class TelegramService
|
class TelegramService
|
||||||
{
|
{
|
||||||
private Client $client;
|
|
||||||
private ?string $botToken;
|
private ?string $botToken;
|
||||||
|
|
||||||
public function __construct(?string $botToken = null)
|
public function __construct(?string $botToken = null)
|
||||||
{
|
{
|
||||||
$this->botToken = $botToken;
|
$this->botToken = $botToken;
|
||||||
$this->client = $this->createGuzzleClient("https://api.telegram.org/bot{$botToken}/");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function escapeTelegramMarkdownV2(string $text): string
|
public function escapeTelegramMarkdownV2(string $text): string
|
||||||
@@ -37,13 +38,15 @@ class TelegramService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$client = $this->createGuzzleClient("https://api.telegram.org/bot{$this->botToken}/");
|
||||||
|
|
||||||
$query = [
|
$query = [
|
||||||
'chat_id' => $chatId,
|
'chat_id' => $chatId,
|
||||||
'text' => $text,
|
'text' => $text,
|
||||||
'parse_mode' => 'MarkdownV2',
|
'parse_mode' => 'MarkdownV2',
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->client->get('sendMessage', [
|
$client->get('sendMessage', [
|
||||||
'query' => $query,
|
'query' => $query,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -62,4 +65,52 @@ class TelegramService
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command used to exec telegram api methods
|
||||||
|
* @see https://core.telegram.org/bots/api
|
||||||
|
*
|
||||||
|
* @throws GuzzleException
|
||||||
|
* @throws TelegramClientException
|
||||||
|
* @throws \JsonException
|
||||||
|
*/
|
||||||
|
public function exec(string $methodName, array $params = []): array
|
||||||
|
{
|
||||||
|
if (! $this->botToken) {
|
||||||
|
throw new TelegramClientException('BotToken is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
$client = $this->createGuzzleClient("https://api.telegram.org/bot{$this->botToken}/");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $client->post($methodName, [
|
||||||
|
'json' => $params,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$json = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
if (! $json['ok']) {
|
||||||
|
throw new TelegramClientException(
|
||||||
|
$json['code'] ?? 'Unknown Code' . ': ' . $json['description'] ?? 'Unknown Error'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $json;
|
||||||
|
} catch (ClientException $exception) {
|
||||||
|
$response = $exception->getResponse()->getBody()->getContents();
|
||||||
|
$decoded = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
throw new TelegramClientException(
|
||||||
|
$decoded['description'] ?? $exception->getMessage(),
|
||||||
|
$decoded['code'] ?? $exception->getCode(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWebhookUrl(): string
|
||||||
|
{
|
||||||
|
$webhookInfo = $this->exec('getWebhookInfo');
|
||||||
|
|
||||||
|
return Arr::get($webhookInfo, 'result.url');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class TelegramValidateInitDataMiddleware
|
|||||||
private array $excluded = [
|
private array $excluded = [
|
||||||
'testTgMessage',
|
'testTgMessage',
|
||||||
'manifest',
|
'manifest',
|
||||||
|
'webhook',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(SignatureValidator $signatureValidator)
|
public function __construct(SignatureValidator $signatureValidator)
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ class ApplicationFactory
|
|||||||
public static function create(array $config): Application
|
public static function create(array $config): Application
|
||||||
{
|
{
|
||||||
$defaultConfig = require __DIR__ . '/config.php';
|
$defaultConfig = require __DIR__ . '/config.php';
|
||||||
|
$routes = require __DIR__ . '/routes.php';
|
||||||
|
|
||||||
return (new Application(Arr::mergeArraysRecursively($defaultConfig, $config)))
|
return (new Application(Arr::mergeArraysRecursively($defaultConfig, $config)))
|
||||||
|
->withRoutes(fn () => $routes)
|
||||||
->withServiceProviders([
|
->withServiceProviders([
|
||||||
QueryBuilderServiceProvider::class,
|
QueryBuilderServiceProvider::class,
|
||||||
CacheServiceProvider::class,
|
CacheServiceProvider::class,
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Handlers;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Cache\CacheInterface;
|
||||||
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
|
use Openguru\OpenCartFramework\Http\Request;
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
|
||||||
|
class TelegramHandler
|
||||||
|
{
|
||||||
|
private CacheInterface $cache;
|
||||||
|
|
||||||
|
public function __construct(CacheInterface $cache)
|
||||||
|
{
|
||||||
|
$this->cache = $cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function webhook(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$message = Arr::get($request->json(), 'message', []);
|
||||||
|
|
||||||
|
$this->cache->set('tg_latest_msg', $message, 60);
|
||||||
|
|
||||||
|
return new JsonResponse([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,13 +5,11 @@ namespace App\ServiceProviders;
|
|||||||
use App\Exceptions\CustomExceptionHandler;
|
use App\Exceptions\CustomExceptionHandler;
|
||||||
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||||
use Openguru\OpenCartFramework\Contracts\ExceptionHandlerInterface;
|
use Openguru\OpenCartFramework\Contracts\ExceptionHandlerInterface;
|
||||||
use Openguru\OpenCartFramework\Router\Router;
|
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
$this->container->get(Router::class)->loadRoutesFromFile(__DIR__ . '/../routes.php');
|
|
||||||
$this->container->singleton(ExceptionHandlerInterface::class, function () {
|
$this->container->singleton(ExceptionHandlerInterface::class, function () {
|
||||||
return new CustomExceptionHandler();
|
return new CustomExceptionHandler();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Decorators\OcRegistryDecorator;
|
|
||||||
use Cart\Cart;
|
use Cart\Cart;
|
||||||
|
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
|
||||||
|
|
||||||
class CartService
|
class CartService
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Decorators\OcRegistryDecorator;
|
|
||||||
use App\Exceptions\OrderValidationFailedException;
|
use App\Exceptions\OrderValidationFailedException;
|
||||||
use Cassandra\Date;
|
use Cassandra\Date;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Openguru\OpenCartFramework\Config\Settings;
|
use Openguru\OpenCartFramework\Config\Settings;
|
||||||
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
||||||
|
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
||||||
use Openguru\OpenCartFramework\Support\Arr;
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
use Openguru\OpenCartFramework\Telegram\TelegramService;
|
use Openguru\OpenCartFramework\Telegram\TelegramService;
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Adapters\OcModelCatalogProductAdapter;
|
use App\Adapters\OcModelCatalogProductAdapter;
|
||||||
use App\Decorators\OcRegistryDecorator;
|
|
||||||
use Cart\Currency;
|
use Cart\Currency;
|
||||||
use Cart\Tax;
|
use Cart\Tax;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Openguru\OpenCartFramework\Config\Settings;
|
use Openguru\OpenCartFramework\Config\Settings;
|
||||||
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
|
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
|
||||||
|
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
||||||
use Openguru\OpenCartFramework\Support\Arr;
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
|||||||
@@ -2,32 +2,11 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'config_timezone' => 'UTC',
|
'config_timezone' => 'UTC',
|
||||||
|
|
||||||
'lang' => 'en-gb',
|
'lang' => 'en-gb',
|
||||||
'language_id' => 1,
|
'language_id' => 1,
|
||||||
'auth_user_id' => 0,
|
'auth_user_id' => 0,
|
||||||
|
|
||||||
'base_url' => 'http://localhost',
|
'base_url' => 'http://localhost',
|
||||||
|
|
||||||
'search_cache_seconds' => 60,
|
|
||||||
|
|
||||||
'chunk_size' => 100,
|
|
||||||
|
|
||||||
'retry_count' => 3,
|
|
||||||
|
|
||||||
'activity_log_retention_limit' => 5000,
|
|
||||||
|
|
||||||
'tables' => [
|
|
||||||
'selected_products' => 'bp_selected_products',
|
|
||||||
'simulation_results' => 'bp_simulation_results',
|
|
||||||
'activity_logs' => 'bp_activity_logs',
|
|
||||||
'cache' => 'bp_cache',
|
|
||||||
'task_progress' => 'bp_task_progress',
|
|
||||||
'task_steps' => 'bp_task_steps',
|
|
||||||
'search_results' => 'bp_search_results',
|
|
||||||
'settings' => 'bp_settings',
|
|
||||||
],
|
|
||||||
|
|
||||||
'db' => [
|
'db' => [
|
||||||
'host' => 'localhost',
|
'host' => 'localhost',
|
||||||
'database' => 'not_set',
|
'database' => 'not_set',
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use App\Handlers\CartHandler;
|
|||||||
use App\Handlers\OrderHandler;
|
use App\Handlers\OrderHandler;
|
||||||
use App\Handlers\ProductsHandler;
|
use App\Handlers\ProductsHandler;
|
||||||
use App\Handlers\SettingsHandler;
|
use App\Handlers\SettingsHandler;
|
||||||
|
use App\Handlers\TelegramHandler;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'products' => [ProductsHandler::class, 'handle'],
|
'products' => [ProductsHandler::class, 'handle'],
|
||||||
@@ -19,4 +20,6 @@ return [
|
|||||||
'settings' => [SettingsHandler::class, 'index'],
|
'settings' => [SettingsHandler::class, 'index'],
|
||||||
'manifest' => [SettingsHandler::class, 'manifest'],
|
'manifest' => [SettingsHandler::class, 'manifest'],
|
||||||
'testTgMessage' => [SettingsHandler::class, 'testTgMessage'],
|
'testTgMessage' => [SettingsHandler::class, 'testTgMessage'],
|
||||||
|
|
||||||
|
'webhook' => [TelegramHandler::class, 'webhook'],
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user