Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <?php
  2. namespace App\Service;
  3. use PhpImap\IncomingMail;
  4. use PhpImap\IncomingMailAttachment;
  5. use Psr\Log\LoggerInterface;
  6. use SecIT\ImapBundle\Connection\ConnectionInterface;
  7. use Symfony\Component\DependencyInjection\Attribute\Target;
  8. use Symfony\Component\Filesystem\Filesystem;
  9. use Symfony\Component\Filesystem\Path;
  10. use App\Presentation\PortfolioPresenter as PresentationPortfolio;
  11. use Symfony\Component\Notifier\Bridge\Telegram\TelegramOptions;
  12. use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransport;
  13. use Symfony\Component\Notifier\Chatter;
  14. use Symfony\Component\Notifier\Message\ChatMessage;
  15. use Symfony\Component\Notifier\Notifier;
  16. use Symfony\Component\Notifier\NotifierInterface;
  17. use App\Service\TelegramNotifier;
  18. readonly class MailFetcher
  19. {
  20. public function __construct(
  21. #[Target('finfollowConnection')]
  22. private ConnectionInterface $connection,
  23. private XmlParser $xmlParser,
  24. private PortfolioManager $portfolioManager,
  25. private LoggerInterface $logger,
  26. private TelegramNotifier $telegramNotifier,
  27. )
  28. {
  29. }
  30. public function fetchNewEmails(): void
  31. {
  32. $mailbox = $this->connection->getMailbox();
  33. try {
  34. // Get all emails (messages)
  35. // PHP.net imap_search criteria: http://php.net/manual/en/function.imap-search.php
  36. $mailsIds = $mailbox->searchMailbox('ALL');
  37. } catch (PhpImap\Exceptions\ConnectionException $ex) {
  38. $this->logger->error("IMAP connection failed: " . implode(",", $ex->getErrors('all')));
  39. die();
  40. }
  41. if (!$mailsIds) {
  42. $this->logger->info('Mailbox is empty');
  43. $this->logger->warn("Here we should send info about no changes performed..");
  44. return;
  45. }
  46. // If '__DIR__' was defined in the first line, it will automatically
  47. // save all attachments to the specified directory
  48. foreach ($mailsIds as $mailId) {
  49. $mail = $mailbox->getMail($mailId);
  50. $this->logger->debug(print_r([
  51. 'date' => $mail->date,
  52. 'message_id' => $mail->messageId,
  53. 'from' => $mail->fromAddress,
  54. 'subject' => $mail->subject,
  55. 'hasAttachments' => $mail->hasAttachments(),
  56. ], true));
  57. if ($this->canProcessMail($mail)) {
  58. if ($this->processMail($mail)) {
  59. //break;
  60. }
  61. //$mailbox->deleteMail($mailId);
  62. $this->logger->debug("Deleted email with id=" . $mailId);
  63. } else {
  64. $this->logger->debug("Skipped mail with id=" . $mailId);
  65. }
  66. }
  67. $mailbox->disconnect();
  68. }
  69. private function canProcessMail(IncomingMail $mail): bool
  70. {
  71. if (!$mail->hasAttachments()) {
  72. return false;
  73. }
  74. if (mb_stripos($mail->subject, 'Broker report') === false) {
  75. return false;
  76. }
  77. if (mb_stripos($mail->fromAddress, '@bcs.ru') === false) {
  78. return false;
  79. }
  80. return true;
  81. }
  82. private function processMail(IncomingMail $mail): bool
  83. {
  84. $mailProcessed = false;
  85. foreach ($mail->getAttachments() as $attachment) {
  86. if ($attachment->mimeType != 'application/zip') {
  87. continue;
  88. }
  89. $mailProcessed |= $this->processZipArchiveAttachment($attachment);
  90. }
  91. return $mailProcessed;
  92. }
  93. private function processZipArchiveAttachment(IncomingMailAttachment $attachment): bool
  94. {
  95. $zip = new \ZipArchive();
  96. if ($zip->open($attachment->filePath) !== true) {
  97. $this->logger->error('ZIP open error: ' . $zip);
  98. return false;
  99. }
  100. $xmlFound = false;
  101. for ($i = 0; $i < $zip->numFiles; $i++) {
  102. $stat = $zip->statIndex($i);
  103. if (pathinfo($stat['name'])['extension'] !== 'xml') {
  104. continue;
  105. }
  106. $tmpdir = $this->getTmpDir();
  107. $zip->extractTo($tmpdir, [$stat['name']]);
  108. $this->logger->debug("Extracted '$stat[name]' into '$tmpdir'");
  109. $xmlString = file_get_contents($tmpdir . '/' . $stat['name']);
  110. $xml = simplexml_load_string($xmlString);
  111. $parsedPortfolio = $this->xmlParser->processXml($xml);
  112. $message = PresentationPortfolio::toPrint($parsedPortfolio);
  113. if ($this->portfolioManager->updatePortfolio($parsedPortfolio, $xmlString)) {
  114. $this->telegramNotifier->notify($message);
  115. }
  116. $fs = new Filesystem();
  117. $fs->remove($tmpdir);
  118. $xmlFound = true;
  119. }
  120. return $xmlFound;
  121. }
  122. private function getTmpDir(): string
  123. {
  124. $tmpdir = Path::normalize(sys_get_temp_dir() . '/FINFOLLOW-' . random_int(0, 100));
  125. $filesystem = new Filesystem();
  126. $filesystem->mkdir($tmpdir);
  127. return $tmpdir;
  128. }
  129. }