WIP: Symfony 6 project remake #2

Draft
skobkin wants to merge 103 commits from symfony6_remake into master
30 changed files with 398 additions and 976 deletions
Showing only changes of commit 1aff2ac244 - Show all commits

1
.env
View file

@ -38,3 +38,4 @@ APP_CRAWLER_SECRET=some_secret_for_crawler_change_it_right_away
# Telegram Bot settings # Telegram Bot settings
TELEGRAM_BOT_TOKEN=123:abc TELEGRAM_BOT_TOKEN=123:abc
TELEGRAM_WEBHOOK_MAX_CONNECTIONS=2

View file

@ -37,9 +37,9 @@
"symfony/twig-bundle": "6.2.*", "symfony/twig-bundle": "6.2.*",
"symfony/validator": "6.2.*", "symfony/validator": "6.2.*",
"symfony/yaml": "6.2.*", "symfony/yaml": "6.2.*",
"telegram-bot/api": "^2.3",
"twig/extra-bundle": "^2.12|^3.0", "twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0", "twig/twig": "^2.12|^3.0"
"unreal4u/telegram-api": "*"
}, },
"config": { "config": {
"allow-plugins": { "allow-plugins": {

607
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "8d586ece7c8c5b193ba055d36ad7f285", "content-hash": "fa19d360fa749ba51772278ee535bbb3",
"packages": [ "packages": [
{ {
"name": "doctrine/annotations", "name": "doctrine/annotations",
@ -1555,315 +1555,6 @@
}, },
"time": "2022-05-23T21:33:49+00:00" "time": "2022-05-23T21:33:49+00:00"
}, },
{
"name": "guzzlehttp/guzzle",
"version": "6.5.8",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "a52f0440530b54fa079ce76e8c5d196a42cad981"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/a52f0440530b54fa079ce76e8c5d196a42cad981",
"reference": "a52f0440530b54fa079ce76e8c5d196a42cad981",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.9",
"php": ">=5.5",
"symfony/polyfill-intl-idn": "^1.17"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"psr/log": "^1.1"
},
"suggest": {
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.5-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Jeremy Lindblom",
"email": "jeremeamia@gmail.com",
"homepage": "https://github.com/jeremeamia"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle is a PHP HTTP client library",
"homepage": "http://guzzlephp.org/",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"rest",
"web service"
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/6.5.8"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
"type": "tidelift"
}
],
"time": "2022-06-20T22:16:07+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "1.5.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "b94b2807d85443f9719887892882d0329d1e2598"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598",
"reference": "b94b2807d85443f9719887892882d0329d1e2598",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"symfony/phpunit-bridge": "^4.4 || ^5.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.5-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle promises library",
"keywords": [
"promise"
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/1.5.2"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
"type": "tidelift"
}
],
"time": "2022-08-28T14:55:35+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "1.9.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/e98e3e6d4f86621a9b75f623996e6bbdeb4b9318",
"reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
"psr/http-message": "~1.0",
"ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"ext-zlib": "*",
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/1.9.0"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
"type": "tidelift"
}
],
"time": "2022-06-20T21:43:03+00:00"
},
{ {
"name": "jms/metadata", "name": "jms/metadata",
"version": "2.8.0", "version": "2.8.0",
@ -2734,59 +2425,6 @@
}, },
"time": "2019-01-08T18:20:26+00:00" "time": "2019-01-08T18:20:26+00:00"
}, },
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/master"
},
"time": "2016-08-06T14:39:51+00:00"
},
{ {
"name": "psr/log", "name": "psr/log",
"version": "3.0.0", "version": "3.0.0",
@ -2837,50 +2475,6 @@
}, },
"time": "2021-07-14T16:46:02+00:00" "time": "2021-07-14T16:46:02+00:00"
}, },
{
"name": "ralouphie/getallheaders",
"version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/ralouphie/getallheaders.git",
"reference": "120b605dfeb996808c31b6477290a714d356e822"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
"reference": "120b605dfeb996808c31b6477290a714d356e822",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^5 || ^6.5"
},
"type": "library",
"autoload": {
"files": [
"src/getallheaders.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"description": "A polyfill for getallheaders.",
"support": {
"issues": "https://github.com/ralouphie/getallheaders/issues",
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
},
"time": "2019-03-08T08:55:37+00:00"
},
{ {
"name": "sensio/framework-extra-bundle", "name": "sensio/framework-extra-bundle",
"version": "v6.2.10", "version": "v6.2.10",
@ -5372,93 +4966,6 @@
], ],
"time": "2022-11-03T14:55:06+00:00" "time": "2022-11-03T14:55:06+00:00"
}, },
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "639084e360537a19f9ee352433b84ce831f3d2da"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da",
"reference": "639084e360537a19f9ee352433b84ce831f3d2da",
"shasum": ""
},
"require": {
"php": ">=7.1",
"symfony/polyfill-intl-normalizer": "^1.10",
"symfony/polyfill-php72": "^1.10"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Idn\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Laurent Bassin",
"email": "laurent@bassin.info"
},
{
"name": "Trevor Rowbotham",
"email": "trevor.rowbotham@pm.me"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"idn",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
},
{ {
"name": "symfony/polyfill-intl-normalizer", "name": "symfony/polyfill-intl-normalizer",
"version": "v1.27.0", "version": "v1.27.0",
@ -7274,6 +6781,66 @@
], ],
"time": "2023-02-16T09:57:23+00:00" "time": "2023-02-16T09:57:23+00:00"
}, },
{
"name": "telegram-bot/api",
"version": "v2.3.25",
"source": {
"type": "git",
"url": "https://github.com/TelegramBot/Api.git",
"reference": "7a534219842ad59835eb89909ca58f8b8e3223a0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TelegramBot/Api/zipball/7a534219842ad59835eb89909ca58f8b8e3223a0",
"reference": "7a534219842ad59835eb89909ca58f8b8e3223a0",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=5.5.0"
},
"require-dev": {
"codeception/codeception": "*",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "2.*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"TelegramBot\\Api\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ilya Gusev",
"email": "mail@igusev.ru",
"homepage": "https://php-cat.com",
"role": "Developer"
}
],
"description": "PHP Wrapper for Telegram Bot API",
"homepage": "https://github.com/TelegramBot/Api",
"keywords": [
"bot",
"bot api",
"php",
"telegram"
],
"support": {
"issues": "https://github.com/TelegramBot/Api/issues",
"source": "https://github.com/TelegramBot/Api/tree/v2.3.25"
},
"time": "2023-03-06T10:06:04+00:00"
},
{ {
"name": "twig/extra-bundle", "name": "twig/extra-bundle",
"version": "v3.5.1", "version": "v3.5.1",
@ -7429,58 +6996,6 @@
], ],
"time": "2023-02-08T07:49:20+00:00" "time": "2023-02-08T07:49:20+00:00"
}, },
{
"name": "unreal4u/telegram-api",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/unreal4u/telegram-api.git",
"reference": "501a02062942fb533bbcdf6f9f538368208db65a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/unreal4u/telegram-api/zipball/501a02062942fb533bbcdf6f9f538368208db65a",
"reference": "501a02062942fb533bbcdf6f9f538368208db65a",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "~6.0",
"php": ">=7.0.0"
},
"require-dev": {
"phpmd/phpmd": "@stable",
"phpunit/phpunit": "@stable",
"squizlabs/php_codesniffer": "@stable"
},
"type": "library",
"autoload": {
"psr-4": {
"unreal4u\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Camilo Sperberg",
"email": "me@unreal4u.com",
"homepage": "https://github.com/unreal4u/telegram-api/graphs/contributors"
}
],
"description": "Complete implementation used to communicate with the open-source Telegram API",
"keywords": [
"api",
"telegram",
"telegram bot"
],
"support": {
"issues": "https://github.com/unreal4u/telegram-api/issues",
"source": "https://github.com/unreal4u/telegram-api/tree/master"
},
"time": "2016-01-26T21:46:33+00:00"
},
{ {
"name": "webmozart/assert", "name": "webmozart/assert",
"version": "1.11.0", "version": "1.11.0",

View file

@ -4,6 +4,14 @@
# Put parameters here that don't need to change on each machine where the app is deployed # Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters: parameters:
env(APP_POINT_SCHEME): 'https'
env(APP_POINT_DOMAIN): 'point.im'
env(APP_POINT_BASE_URL): 'https://point.im'
env(APP_POINT_API_DELAY): 5000
env(APP_USER_ID): '435'
env(APP_USER_LOGIN): 'point-tools'
env(TELEGRAM_BOT_TOKEN): 'abc:123'
env(APP_CRAWLER_SECRET): 'some_secret_for_crawler_change_it_right_away'
services: services:
# default configuration for services in *this* file # default configuration for services in *this* file
@ -11,19 +19,19 @@ services:
autowire: true # Automatically injects dependencies in your services. autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
bind: bind:
# Point Tools
$appUserId: '%env(int:APP_USER_ID)%'
$appUserLogin: 'env(string:APP_USER_LOGIN)'
# Telegram Bot API # Telegram Bot API
$telegramToken: 'env(string:TELEGRAM_BOT_TOKEN)' $telegramToken: '%env(string:TELEGRAM_BOT_TOKEN)%'
$telegramWebhookMaxConnections: 'env(int:TELEGRAM_WEBHOOK_MAX_CONNECTIONS)'
$debugEnabled: '%kernel.debug%' $debugEnabled: '%kernel.debug%'
# Point API # Point API
$pointDomain: 'env(string:APP_POINT_DOMAIN)' $pointDomain: '%env(string:APP_POINT_DOMAIN)%'
$pointScheme: 'env(string:APP_POINT_SCHEME)' $pointScheme: '%env(string:APP_POINT_SCHEME)%'
$pointApiDelay: '%env(int:APP_POINT_API_DELAY)%' $pointApiDelay: '%env(int:APP_POINT_API_DELAY)%'
$pointApiClient: '@app.point.http_client' $pointApiClient: '@app.point.http_client'
$pointAppUserId: '%env(int:APP_USER_ID)%'
$pointAppUserLogin: '%env(string:APP_USER_LOGIN)%'
# Crawler API # Crawler API
$crawlerSecret: 'env(string:APP_CRAWLER_SECRET)' $crawlerSecret: '%env(string:APP_CRAWLER_SECRET)%'
# makes classes in src/ available to be used as services # makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name # this creates a service per class whose id is the fully-qualified class name
@ -39,5 +47,10 @@ services:
class: Symfony\Component\HttpClient\HttpClient class: Symfony\Component\HttpClient\HttpClient
factory: [null, 'create'] factory: [null, 'create']
arguments: arguments:
base_uri: '%point_base_url%' -
timeout: 5.0 base_uri: '%env(string:APP_POINT_SCHEME)%://%env(string:APP_POINT_DOMAIN)%'
timeout: 5.0
TelegramBot\Api\BotApi:
arguments:
$token: '%env(string:TELEGRAM_BOT_TOKEN)%'

View file

@ -1,49 +0,0 @@
<?php
namespace src\PointToolsBundle\Service\Telegram;
use unreal4u\TelegramAPI\Telegram\Types\{Inline\Query, Message, Update};
use src\PointToolsBundle\Service\Telegram\InlineQueryProcessor;
use src\PointToolsBundle\Service\Telegram\PrivateMessageProcessor;
/**
* Dispatches incoming messages processing to corresponding services
*/
class IncomingUpdateDispatcher
{
const CHAT_TYPE_PRIVATE = 'private';
const CHAT_TYPE_GROUP = 'group';
/** @var InlineQueryProcessor */
private $inlineQueryProcessor;
/** @var PrivateMessageProcessor */
private $privateMessageProcessor;
public function __construct(PrivateMessageProcessor $privateMessageProcessor, InlineQueryProcessor $inlineQueryProcessor)
{
$this->privateMessageProcessor = $privateMessageProcessor;
$this->inlineQueryProcessor = $inlineQueryProcessor;
}
/**
* Processes update and delegates it to corresponding service
*
* @param Update $update
*/
public function process(Update $update): void
{
if ($update->message && $update->message instanceof Message) {
$chatType = $update->message->chat->type;
if (self::CHAT_TYPE_PRIVATE === $chatType) {
$this->privateMessageProcessor->process($update->message);
} elseif (self::CHAT_TYPE_GROUP === $chatType) {
// @todo implement
}
} elseif ($update->inline_query && $update->inline_query instanceof Query) {
$this->inlineQueryProcessor->process($update->inline_query);
}
}
}

View file

@ -1,56 +0,0 @@
<?php
namespace src\PointToolsBundle\Service\Telegram;
use src\PointToolsBundle\Repository\UserRepository;
use unreal4u\TelegramAPI\Telegram\Methods\AnswerInlineQuery;
use unreal4u\TelegramAPI\Telegram\Types\Inline\Query;
use unreal4u\TelegramAPI\Telegram\Types\InputMessageContent\Text;
use unreal4u\TelegramAPI\TgLog;
use function Skobkin\Bundle\PointToolsBundle\Service\Telegram\mb_strlen;
class InlineQueryProcessor
{
/** @var UserRepository */
private $userRepo;
/** @var TgLog */
private $client;
public function __construct(UserRepository $userRepository, TgLog $client)
{
$this->userRepo = $userRepository;
$this->client = $client;
}
public function process(Query $inlineQuery): void
{
if (mb_strlen($inlineQuery->query) < 2) {
return;
}
$answerInlineQuery = new AnswerInlineQuery();
$answerInlineQuery->inline_query_id = $inlineQuery->id;
foreach ($this->userRepo->findUsersLikeLogin($inlineQuery->query) as $user) {
$article = new Query\Result\Article();
$article->title = $user->getLogin();
$contentText = new Text();
$contentText->message_text = sprintf(
"@%s:\nName: %s\nSubscribers: %d",
$user->getLogin(),
$user->getName(),
$user->getSubscribers()->count()
);
$article->input_message_content = $contentText;
$article->id = md5($user->getId());
$answerInlineQuery->addResult($article);
}
$this->client->performApiRequest($answerInlineQuery);
}
}

View file

@ -1,70 +0,0 @@
<?php
namespace src\PointToolsBundle\Service\Telegram;
use src\PointToolsBundle\Entity\User;
use src\PointToolsBundle\Entity\UserRenameEvent;
use src\PointToolsBundle\Service\Telegram\MessageSender;
use src\PointToolsBundle\Entity\{Telegram\Account};
use src\PointToolsBundle\Repository\Telegram\AccountRepository;
/**
* Notifies Telegram users about some events
*/
class Notifier
{
/** @var AccountRepository */
private $accountsRepo;
/** @var MessageSender */
private $messenger;
public function __construct(AccountRepository $accountRepository, MessageSender $messenger)
{
$this->accountsRepo = $accountRepository;
$this->messenger = $messenger;
}
/**
* @param UserRenameEvent[] $userRenameEvents
*/
public function sendUsersRenamedNotification(array $userRenameEvents): void
{
$accounts = $this->accountsRepo->findBy(['renameNotification' => true]);
$this->messenger->sendMassTemplatedMessage($accounts, '@SkobkinPointTools/Telegram/users_renamed_notification.md.twig', ['events' => $userRenameEvents]);
}
/**
* Send notification about changes in user's subscribers list
*
* @param User $user
* @param User[] $subscribed
* @param User[] $unsubscribed
*/
public function sendUserSubscribersUpdatedNotification(User $user, array $subscribed, array $unsubscribed): bool
{
/** @var Account $account */
$account = $this->accountsRepo->findOneBy(
[
'user' => $user,
'subscriberNotification' => true,
]
);
if (null === $account) {
return false;
}
return $this->messenger->sendTemplatedMessage(
$account,
'@SkobkinPointTools/Telegram/user_subscribers_updated_notification.md.twig',
[
'user' => $user,
'subscribed' => $subscribed,
'unsubscribed' => $unsubscribed,
]
);
}
}

View file

@ -6,9 +6,7 @@ namespace App\Command;
use App\Service\Telegram\MessageSender; use App\Service\Telegram\MessageSender;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\{InputArgument, InputInterface, InputOption};
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;

View file

@ -5,13 +5,11 @@ namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\{InputArgument, InputInterface};
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use unreal4u\Telegram\Methods\SetWebhook; use TelegramBot\Api\BotApi;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use unreal4u\TgLog;
#[AsCommand(name: 'app:telegram:webhook', description: 'Set webhook')] #[AsCommand(name: 'app:telegram:webhook', description: 'Set webhook')]
class TelegramWebhookCommand extends Command class TelegramWebhookCommand extends Command
@ -20,7 +18,7 @@ class TelegramWebhookCommand extends Command
private const MODE_DELETE = 'delete'; private const MODE_DELETE = 'delete';
public function __construct( public function __construct(
private readonly TgLog $client, private readonly BotApi $client,
private readonly UrlGeneratorInterface $router, private readonly UrlGeneratorInterface $router,
private readonly string $telegramToken, private readonly string $telegramToken,
private readonly int $telegramWebhookMaxConnections, private readonly int $telegramWebhookMaxConnections,
@ -49,15 +47,21 @@ class TelegramWebhookCommand extends Command
$io->info('Setting webhook: ' . $url); $io->info('Setting webhook: ' . $url);
$setWebHook = new SetWebhook(); try {
$setWebHook->url = $url; $this->client->setWebhook(
$setWebHook->max_connections = $this->telegramWebhookMaxConnections; url: $url,
max_connections: $this->telegramWebhookMaxConnections,
$this->client->performApiRequest($setWebHook); );
} catch (\Exception $e) {
$io->error(\sprintf(
'setWebhook error: %s',
$e->getMessage(),
));
}
$output->writeln('Done'); $output->writeln('Done');
} elseif (self::MODE_DELETE === strtolower($input->getArgument('mode'))) { } elseif (self::MODE_DELETE === strtolower($input->getArgument('mode'))) {
$io->warning('Unsupported until moving to another library.'); $this->client->deleteWebhook();
} else { } else {
throw new \InvalidArgumentException(sprintf('Mode must be exactly one of: %s', implode(', ', [self::MODE_SET, self::MODE_DELETE]))); throw new \InvalidArgumentException(sprintf('Mode must be exactly one of: %s', implode(', ', [self::MODE_SET, self::MODE_DELETE])));
} }

View file

@ -27,7 +27,7 @@ class UpdateSubscriptionsCommand extends Command
private readonly UserApi $api, private readonly UserApi $api,
private readonly SubscriptionsManager $subscriptionManager, private readonly SubscriptionsManager $subscriptionManager,
private readonly int $pointApiDelay, private readonly int $pointApiDelay,
private readonly int $appUserId, private readonly int $pointAppUserId,
) { ) {
parent::__construct(); parent::__construct();
} }
@ -155,9 +155,9 @@ class UpdateSubscriptionsCommand extends Command
} else { } else {
/** @var User $serviceUser */ /** @var User $serviceUser */
try { try {
$serviceUser = $this->userRepo->findActiveUserWithSubscribers($this->appUserId); $serviceUser = $this->userRepo->findActiveUserWithSubscribers($this->pointAppUserId);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->logger->error('Error while getting active user with subscribers', ['app_user_id' => $this->appUserId]); $this->logger->error('Error while getting active user with subscribers', ['app_user_id' => $this->pointAppUserId]);
throw $e; throw $e;
} }
@ -166,7 +166,7 @@ class UpdateSubscriptionsCommand extends Command
$this->logger->warning('Service user not found or marked as removed. Falling back to API.'); $this->logger->warning('Service user not found or marked as removed. Falling back to API.');
try { try {
$serviceUser = $this->api->getUserById($this->appUserId); $serviceUser = $this->api->getUserById($this->pointAppUserId);
} catch (UserNotFoundException $e) { } catch (UserNotFoundException $e) {
throw new \RuntimeException('Service user not found in the database and could not be retrieved from API.'); throw new \RuntimeException('Service user not found in the database and could not be retrieved from API.');
} }
@ -175,7 +175,7 @@ class UpdateSubscriptionsCommand extends Command
$this->logger->info('Getting service subscribers'); $this->logger->info('Getting service subscribers');
try { try {
$usersForUpdate = $this->api->getUserSubscribersById($this->appUserId); $usersForUpdate = $this->api->getUserSubscribersById($this->pointAppUserId);
} catch (UserNotFoundException $e) { } catch (UserNotFoundException $e) {
$this->logger->critical('Service user deleted or API response is invalid'); $this->logger->critical('Service user deleted or API response is invalid');

View file

@ -4,8 +4,7 @@ declare(strict_types=1);
namespace App\Command; namespace App\Command;
use App\Entity\User; use App\Entity\User;
use App\Exception\Api\ForbiddenException; use App\Exception\Api\{ForbiddenException, UserNotFoundException};
use App\Exception\Api\UserNotFoundException;
use App\Repository\UserRepository; use App\Repository\UserRepository;
use App\Service\Api\UserApi; use App\Service\Api\UserApi;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -13,8 +12,7 @@ use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\{InputInterface, InputOption};
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
@ -23,11 +21,11 @@ class UpdateUsersPrivacyCommand extends Command
{ {
public function __construct( public function __construct(
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
private readonly LoggerInterface $logger, private readonly LoggerInterface $logger,
private readonly UserRepository $userRepo, private readonly UserRepository $userRepo,
private readonly UserApi $api, private readonly UserApi $api,
private readonly int $pointApiDelay, private readonly int $pointApiDelay,
private readonly int $pointAppUserId, private readonly int $pointAppUserId,
) { ) {
parent::__construct(); parent::__construct();
} }

View file

@ -14,8 +14,8 @@ class MainController extends AbstractController
private const AJAX_AUTOCOMPLETE_SIZE = 10; private const AJAX_AUTOCOMPLETE_SIZE = 10;
public function __construct( public function __construct(
private readonly int $appUserId, private readonly int $pointAppUserId,
private readonly string $appUserLogin, private readonly string $pointAppUserLogin,
) { ) {
} }
@ -49,9 +49,9 @@ class MainController extends AbstractController
'form' => $form->createView(), 'form' => $form->createView(),
'autocomplete_size' => self::AJAX_AUTOCOMPLETE_SIZE, 'autocomplete_size' => self::AJAX_AUTOCOMPLETE_SIZE,
'users_count' => $userRepository->getUsersCount(), 'users_count' => $userRepository->getUsersCount(),
'subscribers_count' => $subscriptionRepository->getUserSubscribersCountById($this->appUserId), 'subscribers_count' => $subscriptionRepository->getUserSubscribersCountById($this->pointAppUserId),
'events_count' => $subscriptionEventRepository->getLastDayEventsCount(), 'events_count' => $subscriptionEventRepository->getLastDayEventsCount(),
'service_login' => $this->appUserLogin, 'service_login' => $this->pointAppUserLogin,
]); ]);
} }

View file

@ -3,11 +3,11 @@ declare(strict_types=1);
namespace App\Controller\Telegram; namespace App\Controller\Telegram;
use App\Service\Telegram\IncomingUpdateDispatcher;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use src\PointToolsBundle\Service\Telegram\IncomingUpdateDispatcher;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\{JsonResponse, Request, Response}; use Symfony\Component\HttpFoundation\{JsonResponse, Request, Response};
use unreal4u\Telegram\Types\Update; use TelegramBot\Api\Types\Update;
class WebHookController extends AbstractController class WebHookController extends AbstractController
{ {
@ -23,11 +23,13 @@ class WebHookController extends AbstractController
throw $this->createNotFoundException(); throw $this->createNotFoundException();
} }
$content = \json_decode($request->getContent(), flags: JSON_THROW_ON_ERROR); try {
$content = \json_decode($request->getContent(), flags: JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
return new JsonResponse('bad json', JsonResponse::HTTP_UNPROCESSABLE_ENTITY);
}
$update = new Update( $update = Update::fromResponse($content);
$content,
);
try { try {
$updateDispatcher->process($update); $updateDispatcher->process($update);

View file

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace App\Enum\Telegram;
enum ChatTypeEnum: string
{
case Private = 'private';
case Group = 'group';
case SuperGroup = 'supergroup';
case Channel = 'channel';
}

View file

@ -7,7 +7,7 @@ use App\Factory\AbstractFactory;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use App\Entity\Telegram\Account; use App\Entity\Telegram\Account;
use App\Repository\Telegram\AccountRepository; use App\Repository\Telegram\AccountRepository;
use unreal4u\Telegram\Types\Message; use TelegramBot\Api\Types\Message;
class AccountFactory extends AbstractFactory class AccountFactory extends AbstractFactory
{ {
@ -20,17 +20,16 @@ class AccountFactory extends AbstractFactory
public function findOrCreateFromMessage(Message $message): Account public function findOrCreateFromMessage(Message $message): Account
{ {
if (null === $account = $this->accountRepository->findOneBy(['id' => $message->from->id])) { if (null === $account = $this->accountRepository->findOneBy(['id' => $message->getFrom()->getId()])) {
$account = new Account($message->from->id); $account = new Account($message->getFrom()->getId());
$this->accountRepository->save($account); $this->accountRepository->save($account);
} }
// Setting/updating account data
$account->updateFromMessageData( $account->updateFromMessageData(
$message->from->first_name, $message->getFrom()->getFirstName(),
$message->from->last_name, $message->getFrom()->getLastName(),
$message->from->username, $message->getFrom()->getUsername(),
$message->chat->id $message->getFrom()->getId(),
); );
return $account; return $account;

View file

@ -1,5 +1,4 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace App\Repository\Telegram; namespace App\Repository\Telegram;

View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Service\Telegram;
use App\Enum\Telegram\ChatTypeEnum;
use Psr\Log\LoggerInterface;
use TelegramBot\Api\Types\Update;
/** Dispatches incoming messages processing to corresponding services */
class IncomingUpdateDispatcher
{
public function __construct(
private readonly PrivateMessageProcessor $privateMessageProcessor,
private readonly InlineQueryProcessor $inlineQueryProcessor,
private readonly LoggerInterface $log,
) {
}
public function process(Update $update): void
{
if ($update->getMessage() && $update->getMessage()->getText()) {
$chatType = $update->getMessage()->getChat()->getType();
match (ChatTypeEnum::tryFrom($chatType)) {
ChatTypeEnum::Private => $this->privateMessageProcessor->process($update),
default => $this->log->info(\sprintf(
'Unsupported message type \'%s\' received.\n %s\n %s',
$chatType,
$update->getMessage()->getChat()->getId(),
$update->getMessage()->getText(),
))
};
} elseif ($update->getInlineQuery()) {
$this->inlineQueryProcessor->process($update->getInlineQuery());
}
}
}

View file

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace App\Service\Telegram;
use App\Repository\UserRepository;
use TelegramBot\Api\BotApi;
use TelegramBot\Api\Types\Inline\InlineQuery;
use TelegramBot\Api\Types\Inline\InputMessageContent\Text;
use TelegramBot\Api\Types\Inline\QueryResult\Article;
class InlineQueryProcessor
{
public function __construct(
private readonly UserRepository $userRepo,
private readonly BotApi $client,
) {
}
public function process(InlineQuery $inlineQuery): void
{
$text = $inlineQuery->getQuery();
if (\mb_strlen($text) < 2) {
return;
}
$results = [];
foreach ($this->userRepo->findUsersLikeLogin($text) as $key => $user) {
$results[] = new Article(
id: \hash('md5', (string) $user->getId()),
title: $user->getLogin(),
inputMessageContent: new Text(
messageText: \sprintf(
"@%s:\nName: %s\nSubscribers: %d",
$user->getLogin(),
$user->getName(),
$user->getSubscribers()->count()
),
parseMode: MessageSender::PARSE_PLAIN,
disableWebPagePreview: true,
),
);
}
$this->client->answerInlineQuery(
$inlineQuery->getId(),
$results,
);
}
}

View file

@ -1,54 +1,37 @@
<?php <?php
declare(strict_types=1);
namespace src\PointToolsBundle\Service\Telegram; namespace App\Service\Telegram;
use GuzzleHttp\Exception\ClientException; use App\Entity\Telegram\Account;
use src\PointToolsBundle\Entity\Telegram\Account; use Psr\Log\LoggerInterface;
use unreal4u\TelegramAPI\Abstracts\KeyboardMethods; use TelegramBot\Api\BotApi;
use unreal4u\TelegramAPI\Telegram\Methods\SendMessage; use TelegramBot\Api\Types\ReplyKeyboardMarkup;
use unreal4u\TelegramAPI\TgLog; use Twig\Environment;
/**
* Service which sends simple messages to Telegram users
*/
class MessageSender class MessageSender
{ {
public const PARSE_PLAIN = ''; public const PARSE_PLAIN = '';
public const PARSE_MARKDOWN = 'Markdown'; public const PARSE_MARKDOWN = 'Markdown';
public const PARSE_HTML5 = 'HTML'; public const PARSE_MARKDOWN_V2 = 'MarkdownV2';
public const PARSE_HTML = 'HTML';
/** @var TgLog */ public function __construct(
private $client; private readonly BotApi $client,
private readonly Environment $twig,
/** @var \Twig_Environment */ private readonly LoggerInterface $log,
private $twig; ) {
/**
* @param TgLog $client
*/
public function __construct(TgLog $client, \Twig_Environment $twig)
{
$this->client = $client;
$this->twig = $twig;
} }
/** /** @param Account[] $accounts */
* @param Account[] $accounts
* @param string $template
* @param array $templateData
* @param KeyboardMethods|null $keyboardMarkup
* @param bool $disableWebPreview
* @param bool $disableNotifications
* @param string $parseMode
*/
public function sendMassTemplatedMessage( public function sendMassTemplatedMessage(
array $accounts, array $accounts,
string $template, string $template,
array $templateData = [], array $templateData = [],
KeyboardMethods $keyboardMarkup = null, ReplyKeyboardMarkup $keyboardMarkup = null,
bool $disableWebPreview = true, bool $disableWebPreview = true,
bool $disableNotifications = false, bool $disableNotifications = false,
string $parseMode = self::PARSE_MARKDOWN string $parseMode = self::PARSE_MARKDOWN_V2
): void ): void
{ {
$text = $this->twig->render($template, $templateData); $text = $this->twig->render($template, $templateData);
@ -62,10 +45,10 @@ class MessageSender
Account $account, Account $account,
string $template, string $template,
array $templateData = [], array $templateData = [],
KeyboardMethods $keyboardMarkup = null, ReplyKeyboardMarkup $keyboardMarkup = null,
bool $disableWebPreview = true, bool $disableWebPreview = true,
bool $disableNotifications = false, bool $disableNotifications = false,
string $parseMode = self::PARSE_MARKDOWN string $parseMode = self::PARSE_MARKDOWN_V2
): bool ): bool
{ {
$text = $this->twig->render($template, $templateData); $text = $this->twig->render($template, $templateData);
@ -77,7 +60,7 @@ class MessageSender
Account $account, Account $account,
string $text, string $text,
string $parseMode = self::PARSE_PLAIN, string $parseMode = self::PARSE_PLAIN,
KeyboardMethods $keyboardMarkup = null, ReplyKeyboardMarkup $keyboardMarkup = null,
bool $disableWebPreview = false, bool $disableWebPreview = false,
bool $disableNotifications = false bool $disableNotifications = false
): bool ): bool
@ -89,24 +72,28 @@ class MessageSender
int $chatId, int $chatId,
string $text, string $text,
string $parseMode = self::PARSE_PLAIN, string $parseMode = self::PARSE_PLAIN,
KeyboardMethods $keyboardMarkup = null, ReplyKeyboardMarkup $keyboardMarkup = null,
bool $disableWebPreview = false, bool $disableWebPreview = false,
bool $disableNotifications = false bool $disableNotifications = false
): bool ): bool
{ {
$sendMessage = new SendMessage();
$sendMessage->chat_id = (string)$chatId;
$sendMessage->text = $text;
$sendMessage->parse_mode = $parseMode;
$sendMessage->disable_web_page_preview = $disableWebPreview;
$sendMessage->disable_notification = $disableNotifications;
$sendMessage->reply_markup = $keyboardMarkup;
try { try {
$this->client->performApiRequest($sendMessage); $this->client->sendMessage(
chatId: (string) $chatId,
text: $text,
parseMode: $parseMode,
disablePreview: $disableWebPreview,
replyMarkup: $keyboardMarkup,
disableNotification: $disableNotifications,
);
return true; return true;
} catch (ClientException $e) { } catch (\Exception $e) {
$this->log->error('sendMessageToChat', [
'error' => $e->getMessage(),
'file' => $e->getFile() . ':' . $e->getLine(),
]);
return false; return false;
} }
} }

View file

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace App\Service\Telegram;
use App\Entity\{User, UserRenameEvent};
use App\Repository\Telegram\AccountRepository;
/**
* Notifies Telegram users about some events
*/
class Notifier
{
public function __construct(
private readonly AccountRepository $accountsRepo,
private readonly MessageSender $messenger,
) {
}
/** @param UserRenameEvent[] $userRenameEvents */
public function sendUsersRenamedNotification(array $userRenameEvents): void
{
$accounts = $this->accountsRepo->findBy(['renameNotification' => true]);
$this->messenger->sendMassTemplatedMessage($accounts, 'Telegram/users_renamed_notification.md.twig', ['events' => $userRenameEvents]);
}
/**
* @param User[] $subscribed
* @param User[] $unsubscribed
*/
public function sendUserSubscribersUpdatedNotification(User $user, array $subscribed, array $unsubscribed): bool
{
$account = $this->accountsRepo->findOneBy(
[
'user' => $user,
'subscriberNotification' => true,
]
);
if (null === $account) {
return false;
}
return $this->messenger->sendTemplatedMessage(
$account,
'Telegram/user_subscribers_updated_notification.md.twig',
[
'user' => $user,
'subscribed' => $subscribed,
'unsubscribed' => $unsubscribed,
]
);
}
}

View file

@ -1,139 +1,70 @@
<?php <?php
declare(strict_types=1);
namespace src\PointToolsBundle\Service\Telegram; namespace App\Service\Telegram;
use App\Enum\Telegram\ChatTypeEnum;
use App\Exception\Telegram\CommandProcessingException;
use App\Factory\Telegram\AccountFactory;
use App\Service\Api\UserApi;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use src\PointToolsBundle\Entity\User; use App\Entity\Telegram\Account;
use src\PointToolsBundle\Repository\SubscriptionEventRepository; use App\Entity\User;
use src\PointToolsBundle\Repository\SubscriptionRepository; use App\Repository\Telegram\AccountRepository;
use src\PointToolsBundle\Repository\UserRepository; use App\Repository\{SubscriptionEventRepository, SubscriptionRepository, UserRepository};
use src\PointToolsBundle\Service\Telegram\IncomingUpdateDispatcher; use TelegramBot\Api\Types\{ReplyKeyboardMarkup, ReplyKeyboardRemove, Update};
use src\PointToolsBundle\Service\Telegram\MessageSender;
use src\PointToolsBundle\Entity\{Telegram\Account};
use src\PointToolsBundle\Exception\Telegram\CommandProcessingException;
use src\PointToolsBundle\Repository\{Telegram\AccountRepository};
use src\PointToolsBundle\Service\Factory\Telegram\AccountFactory;
use src\PointToolsBundle\Service\Api\UserApi;
use unreal4u\TelegramAPI\Telegram\Types\{Message, ReplyKeyboardMarkup, ReplyKeyboardRemove};
/** Processes all private messages */ /** Processes all private messages */
class PrivateMessageProcessor class PrivateMessageProcessor
{ {
/** @var MessageSender */
private $messenger;
/** @var UserApi */
private $userApi;
/** @var AccountFactory */
private $accountFactory;
/** @var EntityManagerInterface */
private $em;
/** @var UserRepository */
private $userRepo;
/** @var AccountRepository */
private $accountRepo;
/** @var SubscriptionRepository */
private $subscriptionRepo;
/** @var SubscriptionEventRepository */
private $subscriptionEventRepo;
/** @var int */
private $pointUserId;
public function __construct( public function __construct(
EntityManagerInterface $em, private readonly EntityManagerInterface $em,
UserRepository $userRepository, private readonly UserRepository $userRepo,
AccountRepository $accountRepository, private readonly AccountRepository $accountRepo,
SubscriptionRepository $subscriptionRepository, private readonly SubscriptionRepository $subscriptionRepo,
SubscriptionEventRepository $subscriptionRecordRepository, private readonly SubscriptionEventRepository $subscriptionEventRepo,
MessageSender $messageSender, private readonly MessageSender $messenger,
UserApi $userApi, private readonly UserApi $userApi,
AccountFactory $accountFactory, private readonly AccountFactory $accountFactory,
int $appUserId private readonly int $pointAppUserId,
) { ) {
$this->em = $em;
$this->userRepo = $userRepository;
$this->accountRepo = $accountRepository;
$this->subscriptionRepo = $subscriptionRepository;
$this->subscriptionEventRepo = $subscriptionRecordRepository;
$this->messenger = $messageSender;
$this->userApi = $userApi;
$this->accountFactory = $accountFactory;
$this->pointUserId = $appUserId;
} }
public function process(Message $message): void public function process(Update $update): void
{ {
if (!IncomingUpdateDispatcher::CHAT_TYPE_PRIVATE === $message->chat->type) { if (!ChatTypeEnum::Private->value === $update->getMessage()->getChat()->getType()) {
throw new \LogicException('This service can process only private chat messages'); throw new \LogicException('This service can process only private chat messages');
} }
try { try {
// Registering Telegram user $account = $this->accountFactory->findOrCreateFromMessage($update->getMessage());
/** @var Account $account */
$account = $this->accountFactory->findOrCreateFromMessage($message);
$this->em->flush(); $this->em->flush();
} catch (\Exception $e) { } catch (\Exception $e) {
// Low-level message in case of incorrect $account $this->messenger->sendMessageToChat(
$this->messenger->sendMessageToChat($message->chat->id, 'There was an error during your Telegram account registration. Try again or report the bug.'); $update->getMessage()->getChat()->getId(),
'There was an error during your Telegram account registration. Try again or report the bug.'
);
return;
} }
try { try {
$words = explode(' ', $message->text, 10); $words = explode(' ', $update->getMessage()?->getText() ?? '', 10);
if (0 === count($words)) { if (0 === count($words)) {
return; return;
} }
switch ($words[0]) { match ($words[0]) {
case '/link': '/link', 'link' => $this->processLink($account, $words),
case 'link': '/me', 'me' => $this->processMe($account),
$this->processLink($account, $words); '/last', 'last' => $this->processLast($account, $words),
break; '/sub', 'sub' => $this->processSub($account, $words),
'/stats', 'stats' => $this->processStats($account),
case '/me': '/set' => $this->processSet($account, $words),
case 'me': '/exit', 'exit' => $this->processExit($account),
$this->processMe($account); default => $this->processHelp($account),
break; };
case '/last':
case 'last':
$this->processLast($account, $words);
break;
case '/sub':
case 'sub':
$this->processSub($account, $words);
break;
case '/stats':
case 'stats':
$this->processStats($account);
break;
// Settings menu
case '/set':
$this->processSet($account, $words);
break;
// Exit from any menu and remove keyboard
case '/exit':
case 'exit':
$this->processExit($account);
break;
case '/help':
default:
$this->processHelp($account);
break;
}
} catch (CommandProcessingException $e) { } catch (CommandProcessingException $e) {
$this->sendError($account, 'Processing error', $e->getMessage()); $this->sendError($account, 'Processing error', $e->getMessage());
@ -149,7 +80,6 @@ class PrivateMessageProcessor
private function linkAccount(Account $account, string $login, string $password): bool private function linkAccount(Account $account, string $login, string $password): bool
{ {
/** @var User $user */
if (null === $user = $this->userRepo->findUserByLogin($login)) { if (null === $user = $this->userRepo->findUserByLogin($login)) {
throw new CommandProcessingException('User not found in Point Tools database. Please try again later.'); throw new CommandProcessingException('User not found in Point Tools database. Please try again later.');
} }
@ -165,9 +95,8 @@ class PrivateMessageProcessor
private function processLink(Account $account, array $words): void private function processLink(Account $account, array $words): void
{ {
if (array_key_exists(2, $words)) { if (\array_key_exists(2, $words)) {
if ($this->linkAccount($account, $words[1], $words[2])) { if ($this->linkAccount($account, $words[1], $words[2])) {
// Saving linking status
$this->em->flush(); $this->em->flush();
$this->sendAccountLinked($account); $this->sendAccountLinked($account);
} else { } else {
@ -189,7 +118,7 @@ class PrivateMessageProcessor
private function processLast(Account $account, array $words): void private function processLast(Account $account, array $words): void
{ {
if (array_key_exists(1, $words)) { if (\array_key_exists(1, $words)) {
if (null !== $user = $this->userRepo->findUserByLogin($words[1])) { if (null !== $user = $this->userRepo->findUserByLogin($words[1])) {
$this->sendUserEvents($account, $user); $this->sendUserEvents($account, $user);
} else { } else {
@ -202,18 +131,16 @@ class PrivateMessageProcessor
private function processSub(Account $account, array $words): void private function processSub(Account $account, array $words): void
{ {
if (array_key_exists(1, $words)) { if (\array_key_exists(1, $words)) {
if (null !== $user = $this->userRepo->findUserByLogin($words[1])) { if (null !== $user = $this->userRepo->findUserByLogin($words[1])) {
$this->sendUserSubscribers($account, $user); $this->sendUserSubscribers($account, $user);
} else { } else {
$this->sendError($account, 'User not found'); $this->sendError($account, 'User not found');
} }
} elseif ($user = $account->getUser()) {
$this->sendUserSubscribers($account, $user);
} else { } else {
if ($user = $account->getUser()) { $this->sendError($account, 'Account not linked', 'You must /link your account first to be able to use this command.');
$this->sendUserSubscribers($account, $user);
} else {
$this->sendError($account, 'Account not linked', 'You must /link your account first to be able to use this command.');
}
} }
} }
@ -224,10 +151,10 @@ class PrivateMessageProcessor
private function processSet(Account $account, array $words): void private function processSet(Account $account, array $words): void
{ {
$keyboard = new ReplyKeyboardMarkup(); $keyboard = new ReplyKeyboardMarkup([], true);
if (array_key_exists(1, $words)) { if (\array_key_exists(1, $words)) {
if (array_key_exists(2, $words)) { if (\array_key_exists(2, $words)) {
if ('renames' === $words[2]) { if ('renames' === $words[2]) {
$account->toggleRenameNotification(); $account->toggleRenameNotification();
$this->em->flush(); $this->em->flush();
@ -246,22 +173,22 @@ class PrivateMessageProcessor
$this->sendError($account, 'Notification type does not exist.'); $this->sendError($account, 'Notification type does not exist.');
} }
} else { } else {
$keyboard->keyboard = [ $keyboard->setKeyboard([
['/set notifications renames'], ['/set notifications renames'],
['/set notifications subscribers'], ['/set notifications subscribers'],
['exit'], ['exit'],
]; ]);
$this->messenger->sendMessage($account, 'Choose which notification type to toggle', MessageSender::PARSE_PLAIN, $keyboard); $this->messenger->sendMessage($account, 'Choose which notification type to toggle', MessageSender::PARSE_PLAIN, $keyboard);
} }
} else { } else {
$keyboard->keyboard = [ $keyboard->setKeyboard([
['/set notifications'], ['/set notifications'],
['exit'], ['exit'],
]; ]);
$this->messenger->sendTemplatedMessage($account, '@SkobkinPointTools/Telegram/settings.md.twig', ['account' => $account], $keyboard); $this->messenger->sendTemplatedMessage($account, 'Telegram/settings.md.twig', ['account' => $account], $keyboard);
} }
} }
@ -270,7 +197,7 @@ class PrivateMessageProcessor
*/ */
private function processExit(Account $account): void private function processExit(Account $account): void
{ {
$keyboardRemove = new ReplyKeyboardRemove(); $keyboardRemove = new ReplyKeyboardRemove(true);
$this->messenger->sendMessage($account, 'Done', MessageSender::PARSE_PLAIN, $keyboardRemove); $this->messenger->sendMessage($account, 'Done', MessageSender::PARSE_PLAIN, $keyboardRemove);
} }
@ -294,7 +221,7 @@ class PrivateMessageProcessor
$this->messenger->sendTemplatedMessage( $this->messenger->sendTemplatedMessage(
$account, $account,
'@SkobkinPointTools/Telegram/user_subscribers.md.twig', 'Telegram/user_subscribers.md.twig',
[ [
'user' => $user, 'user' => $user,
'subscribers' => $subscribers, 'subscribers' => $subscribers,
@ -308,7 +235,7 @@ class PrivateMessageProcessor
$this->messenger->sendTemplatedMessage( $this->messenger->sendTemplatedMessage(
$account, $account,
'@SkobkinPointTools/Telegram/last_user_subscriptions.md.twig', 'Telegram/last_user_subscriptions.md.twig',
[ [
'user' => $user, 'user' => $user,
'events' => $events, 'events' => $events,
@ -320,17 +247,17 @@ class PrivateMessageProcessor
{ {
$events = $this->subscriptionEventRepo->getLastSubscriptionEvents(10); $events = $this->subscriptionEventRepo->getLastSubscriptionEvents(10);
$this->messenger->sendTemplatedMessage($account, '@SkobkinPointTools/Telegram/last_global_subscriptions.md.twig', ['events' => $events]); $this->messenger->sendTemplatedMessage($account, 'Telegram/last_global_subscriptions.md.twig', ['events' => $events]);
} }
private function sendStats(Account $account): void private function sendStats(Account $account): void
{ {
$this->messenger->sendTemplatedMessage( $this->messenger->sendTemplatedMessage(
$account, $account,
'@SkobkinPointTools/Telegram/stats.md.twig', 'Telegram/stats.md.twig',
[ [
'total_users' => $this->userRepo->getUsersCount(), 'total_users' => $this->userRepo->getUsersCount(),
'active_users' => $this->subscriptionRepo->getUserSubscribersCountById($this->pointUserId), 'active_users' => $this->subscriptionRepo->getUserSubscribersCountById($this->pointAppUserId),
'telegram_accounts' => $this->accountRepo->getAccountsCount(), 'telegram_accounts' => $this->accountRepo->getAccountsCount(),
'telegram_linked_accounts' => $this->accountRepo->getLinkedAccountsCount(), 'telegram_linked_accounts' => $this->accountRepo->getLinkedAccountsCount(),
'today_events' => $this->subscriptionEventRepo->getLastDayEventsCount(), 'today_events' => $this->subscriptionEventRepo->getLastDayEventsCount(),
@ -340,20 +267,18 @@ class PrivateMessageProcessor
private function sendHelp(Account $account): void private function sendHelp(Account $account): void
{ {
$this->messenger->sendTemplatedMessage($account, '@SkobkinPointTools/Telegram/help.md.twig'); $this->messenger->sendTemplatedMessage($account, 'Telegram/help.md.twig');
} }
private function sendError(Account $account, string $title, string $text = ''): void private function sendError(Account $account, string $title, string $text = ''): void
{ {
$this->messenger->sendTemplatedMessage( $this->messenger->sendTemplatedMessage(
$account, $account,
'@SkobkinPointTools/Telegram/error.md.twig', 'Telegram/error.md.twig',
[ [
'title' => $title, 'title' => $title,
'text' => $text, 'text' => $text,
] ]
); );
} }
} }

View file

@ -1,7 +1,7 @@
*Last global subscription events:* *Last global subscription events:*
{% set subscription = constant('\\Skobkin\\Bundle\\PointToolsBundle\\Entity\\SubscriptionEvent::ACTION_SUBSCRIBE') %} {% set subscription = constant('\\App\\Entity\\SubscriptionEvent::ACTION_SUBSCRIBE') %}
{# @var event \Skobkin\Bundle\PointToolsBundle\Entity\SubscriptionEvent #} {# @var event \App\Entity\SubscriptionEvent #}
{% for event in events %} {% for event in events %}
{% set sub_login = event.subscriber.login %} {% set sub_login = event.subscriber.login %}
{% set author_login = event.author.login %} {% set author_login = event.author.login %}

View file

@ -1,8 +1,8 @@
{# @var user \Skobkin\Bundle\PointToolsBundle\Entity\User #} {# @var user \App\Entity\User #}
*Last for @{{ user.login }}:* *Last for @{{ user.login }}:*
{% set subscription = constant('\\Skobkin\\Bundle\\PointToolsBundle\\Entity\\SubscriptionEvent::ACTION_SUBSCRIBE') %} {% set subscription = constant('\\App\\Entity\\SubscriptionEvent::ACTION_SUBSCRIBE') %}
{# @var event \Skobkin\Bundle\PointToolsBundle\Entity\SubscriptionEvent #} {# @var event \App\Entity\SubscriptionEvent #}
{% for event in events %} {% for event in events %}
{% set sub_login = event.subscriber.login %} {% set sub_login = event.subscriber.login %}
{{ event.date|date('d M y H:i') }} {% if subscription == event.action %} + {% else %} - {% endif %} [@{{ sub_login }}]({{ sub_login|point_user_url }}) {{ event.date|date('d M y H:i') }} {% if subscription == event.action %} + {% else %} - {% endif %} [@{{ sub_login }}]({{ sub_login|point_user_url }})

View file

@ -1,4 +1,4 @@
{# @var account \Skobkin\Bundle\PointToolsBundle\Entity\Telegram\Account #} {# @var account \App\Entity\Telegram\Account #}
*Your current settings:* *Your current settings:*
Account /link status: {% if account.user %}linked to [@{{ account.user.login }}]({{ account.user.login|point_user_url }}){% else %}Not linked{% endif %} Account /link status: {% if account.user %}linked to [@{{ account.user.login }}]({{ account.user.login|point_user_url }}){% else %}Not linked{% endif %}

View file

@ -1,4 +1,4 @@
{# @var user \Skobkin\Bundle\PointToolsBundle\Entity\User #} {# @var user \App\Entity\User #}
*@{{ user.login }} subscribers:* *@{{ user.login }} subscribers:*
{% if subscribers|length > 0 %} {% if subscribers|length > 0 %}

View file

@ -1,5 +1,5 @@
{# @var subscribed \Skobkin\Bundle\PointToolsBundle\Entity\User[] #} {# @var subscribed \App\Entity\User[] #}
{# @var unsubscribed \Skobkin\Bundle\PointToolsBundle\Entity\User[] #} {# @var unsubscribed \App\Entity\User[] #}
*Subscribers list changed:* *Subscribers list changed:*
{% if subscribed|length > 0 %} {% if subscribed|length > 0 %}

View file

@ -1,6 +1,6 @@
*Following users recently renamed themselves:* *Following users recently renamed themselves:*
{# @var event \Skobkin\Bundle\PointToolsBundle\Entity\UserRenameEvent #} {# @var event \App\Entity\UserRenameEvent #}
{% for event in events %} {% for event in events %}
{% set login_old = event.oldLogin %} {% set login_old = event.oldLogin %}
{% set login_new = event.user.login %} {% set login_new = event.user.login %}