mail · Почта 26.1000.0 Заметное

Mail 26.1000.0: FIELD_BCC без конвертных получателей, один адрес на портал и транзакции в MailboxTable

3 мин чтения Устаревших API: 3
Узнавайте о ломающих изменениях Битрикса раньше, чем они доедут до прода: свежие разборы — в канале @bxmax_updates

Mail 26.1000.0 вышел следом за 26.900.0 и снова большой. Много кода спрятано за выключенными флагами: серверные черновики, метки, новая форма письма, AI-классификация входящих, макросы в подписях. Сразу после обновления заработают запрет повторного подключения адреса, лимит получателей и переделанная синхронизация истории IMAP.

Свой код стоит проверить, если вы достаёте адрес доставки из FIELD_BCC, ищете ящики по адресу, пишете в MailboxTable напрямую, наследуете Helper\Mailbox\Imap или включали массовые действия в гриде ящиков опцией enable_list_improvements.

Что сломается

Ищите адрес доставки в FIELD_RCPT

Раньше в FIELD_BCC входящего письма (classes/general/mail.php) дописывался заголовок X-Original-Rcpt-to, то есть конвертные получатели. Теперь в поле попадает только BCC (Helper\Message::getBccFromParsedHeader()). Если ваш код брал из FIELD_BCC адрес, на который письмо реально доставили, теперь его там нет.

Конвертные получатели теперь идут в правила и в поиск:

  • почтовые правила получают их в FIELD_RCPT;
  • в SEARCH_CONTENT они записываются токенами xrcpt + SHA-256 от адреса (Helper\Message::prepareOriginalRecipientsSearchString());
  • поиск по получателю в admin/mail_message_admin.php и в CMailMessage::GetList ищет и по этим токенам.

Проверьте поиск ящиков по адресу

MailboxTable::getMailboxesWithEmail() раньше искал по =EMAIL. Теперь поиск ведёт AddressBindingResolver: сначала по новой колонке EMAIL_NORMALIZED, а если там ничего нет, по истории адресов ящика. getUserMailboxWithEmail() тоже учитывает нормализацию и историю, а для неавторизованного $USER возвращает null.

EMAIL_NORMALIZED заполняют новые onBeforeAdd() и onBeforeUpdate(). Они берут первый валидный адрес из EMAIL, NAME или LOGIN и приводят его к нижнему регистру (lib/internals/service/mailbox/emailnormalizer.php). У существующих ящиков колонку заполняет Internals\Service\Mailbox\NormalizedEmailBackfill::run(), но в коде модуля его никто не вызывает, и кто запускает его при обновлении, из кода релиза не видно. После обновления убедитесь, что getMailboxesWithEmail() находит ваши старые ящики.

Учтите транзакции в MailboxTable

MailboxTable (lib/mailbox.php) переопределил update(), delete(), deleteByFilter() и updateMulti():

  • update(), который меняет EMAIL, NAME или LOGIN, открывает транзакцию и блокирует строку через SELECT ... FOR UPDATE;
  • delete() в одной транзакции удаляет ящик и его строки истории адресов из b_mail_mailbox_alias;
  • deleteByFilter() больше не берётся из DeleteByFilterTrait. Он блокирует строки через SELECT ... FOR UPDATE, чистит алиасы и выполняет DELETE сырым SQL, а на пустой фильтр бросает ArgumentException;
  • смена USER_ID, ACTIVE или SERVER_TYPE сбрасывает кеш владельцев и весь кеш общих ящиков.

Если вы правите ящики пачкой в цикле или зовёте эти методы внутри своей транзакции, прогоните такой код на копии до выкладки. MailboxTable::rollbackOwnTransaction() при этом глушит TransactionException с текстом «Nested rollbacks are unsupported.». В main вложенный rollbackTransaction() (main/lib/db/mysqlcommonconnection.php) сначала делает ROLLBACK TO SAVEPOINT, а потом всё равно бросает это исключение.

CMailbox::Delete() при ошибке MailboxTable::delete() теперь возвращает MAIL_MAILBOX_DELETE_ERROR, хотя раньше результат удаления игнорировал. Метки ящика он удаляет до удаления самого ящика.

Перенесите опцию массовых действий

Helper\Config\Feature::isMailboxGridBulkActionsAvailable() читает опцию mailbox_grid_bulk_actions_enabled вместо enable_list_improvements. Там, где массовые действия включались старой опцией, после обновления они выключатся. Значение можно перенести, например, в миграции:

        <?php declare(strict_types=1);

use Bitrix\Main\Config\Option;

Option::set(
    'mail',
    'mailbox_grid_bulk_actions_enabled',
    Option::get('mail', 'enable_list_improvements', 'N'),
);

    

Сверьте наследников Imap

В Helper\Mailbox\Imap (lib/helper/mailbox/imap.php) удалён protected excludeRegisteredUids(). Его заменил collectPendingUids(), который учитывает попытки. syncMessage() получил by-ref параметр bool &$isStorageFailure = false, Helper\Mailbox::unregisterMessages() получил bool &$keptAliveRows = false. Наследник, который переопределяет эти методы со старой сигнатурой, станет несовместим.

Заодно поменялось поведение синхронизации, на которое мог опираться ваш код:

  • При смене UIDVALIDITY папки строки старого поколения раньше удалялись с пометкой «the directory has been deleted». Теперь они остаются, папка получает маркер сверки, и новые строки забирают письма по одному. Удаляется только то, что полный обход не нашёл (startGenerationReconcile(), finishGenerationReconcile()).
  • Строки папок, выключенных пользователем, удаляются как раньше. Строки папок, которых нет в БД (например, после неполного ответа LIST), теперь проверяются по расписанию раз в 6 часов (UNKNOWN_DIRS_CHECK_INTERVAL, unregisterMessagesOutsideSyncDirs()).

Пройдитесь по мелким несовместимостям

  • Integration\Intranet\Secretary::onTaskDelete($taskId, array $parameters = []) сразу выходит, если задача ушла в корзину (MOVED_TO_RECYCLEBIN), и токены доступа к письму остаются. При настоящем удалении обработчик дополнительно чистит новую таблицу b_mail_task_source.
  • Из Controller\ActionFilter\SharedSignatureAccess удалена константа ERROR_LICENSE_DENIED, проверка сведена к MailAccess::hasCurrentUserAccessToSharedSignatureManagement(). Код, который сравнивал с ней код ошибки, упадёт на обращении к константе.
  • Helper\Message\Loader\MessageLoader::buildAttachmentStack(int $count, array $icons) помечен @internal, но если вы его вызывали, учтите: раньше он принимал строки вложений, теперь готовые иконки. Строит их новый buildStackIcons().

Deprecated

Helper\Mailbox::findActiveMailbox($userId, $email, $lid) (lib/helper/mailbox.php) помечен @deprecated. Замена — Internal\Service\Mailbox\MailboxEmailOccupancyService: isAddressHeldByUser() отвечает про одного пользователя, checkOccupancy() про весь портал. Поведение старого метода уже поменялось, это написано в docblock: $lid игнорируется, а ящик, который ждёт пароль, больше не считается совпадением.

Новое

Один адрес — один ящик на портале

Модуль завёл таблицу истории адресов b_mail_mailbox_alias и не даёт подключить занятый адрес. Таблицу читают поиск ящиков и проверка занятости, а пишет в неё только AddressHistory::register(), который в этой версии никто не вызывает, так что после обновления она пустая.

  • Public\Service\Mailbox\AddressHistory умеет register(), isOccupied(), inspectOccupation(), ищет и удаляет осиротевшие алиасы. Регистрация идёт под именованной блокировкой Connection::lock() (AddressLock). Рядом лежат Public\Service\Mailbox\AddressResolver и Internals\Repository\MailboxAddressAliasRepository.
  • Internal\Service\Mailbox\MailboxEmailOccupancyService различает три состояния адреса: свободен, занят этим пользователем, занят другим. По этому ответу MailboxConnector отказывает в подключении с кодом EXISTS_ON_PORTAL_ERROR.
  • Если один адрес подключают два раза одновременно, выигрывает запись с меньшим ID (assertNewMailboxWinsEmailRace()). Проигравшую удаляют, а если удалить не вышло, деактивируют и пишут об этом в лог mail.mailbox.connector.
  • Service\SharedSignature\SenderIdentityResolver привязывает отправителя к ящику по текущему адресу, а прошлые ищет в b_mail_mailbox_alias, так что, пока таблица пустая, по прошлому адресу он ящик не найдёт. В описании версии вендор пишет, что подбор личной подписи после смены адреса исправлен.

Задача может помнить письмо, из которого её создали

Integration\Tasks\TaskMailSourceService хранит источник задачи в b_mail_task_source: saveSource(), canReadSource(), canReadSourceByTaskId(), getEmailDataByTaskId(), grantMailMessageAccess(), bindCrmActivityMailMessageToTask(). Источник бывает двух типов, mail_message и crm_activity. Сам mail saveSource() не вызывает: источник должен записывать модуль задач.

Письмо из дела CRM участник задачи откроет и без прав на CRM: ссылку подписывает токен в области задачи (Integration\Crm\Activity::getTaskScopedDetailsUrl()). При сохранении источника права задачи намеренно не учитываются, иначе через свою задачу можно было бы получить доступ к чужому делу (docblock canReadSource()).

Public\Service\Access\MessageAccessService::canRead() получил параметр ?string $entityType = null. Передавайте тип сущности, потому что без него совпадёт привязка любой сущности с тем же ID. Docblock предупреждает об этом прямо.

Лимит получателей зависит от тарифа

Жёсткое > 10 в install/components/bitrix/mail.client/ajax.php заменила проверка по тарифу. Helper\LicenseManager::getMessageRecipientsTotalLimit() берёт значение из Bitrix24\Feature::getVariable('mail_message_recipients_total_limit'). Без модуля bitrix24 лимит по-прежнему 10. Та же проверка работает в Helper\Message\MessageSender и в maxItems схем AI-инструментов send, reply и forward, а текущие лимиты отдаёт Public\Service\RecipientLimitService::getRecipientLimits().

Выбирайте ящик отправки явно

MessageSender::send(), reply() и forward() получили ?int $senderId и ?int $mailboxId. REST-методы mail.message.send, mail.message.reply и mail.message.forward принимают senderId и mailboxId, при этом mailboxId важнее. mail.mailbox.senders теперь отдаёт оба идентификатора (lib/Infrastructure/Rest/Controller/*.php), а в Integration\Main\SenderProvider появился getAvailableTypedSenders().

Helper\Mailbox передаёт ядру Main\Mail\Context::setSenderIdentity(Sender\Identity::fromMailbox()). Sender\Identity и новый выбор SMTP-отправителя мы разбирали в main 26.800.0. Модуль проверяет этот API через class_exists и method_exists, так что на старом main отправитель по-прежнему выбирается по адресу.

Подпишитесь на догрузку тела письма

Тело письма модуль может догрузить отдельным проходом. Для этого появилось событие mail:onMailMessageBodySynced с параметрами mailboxId, messageId и bodyArrived. Модуль шлёт его для каждого письма прохода догрузки тел, даже если тело так и не пришло (Helper\Message::publishBodySynced()). В mail:onMailMessageNew добавили isFreshArrival: он истинен, если INTERNALDATE письма не старше 12 часов (Imap::FRESH_ARRIVAL_WINDOW). Ставит его только IMAP-синхронизация: для POP3 и встроенного SMTP-сервера он всегда false.

На эти два события подписан AI-классификатор модуля, и свою обработку входящих можно построить так же:

        <?php declare(strict_types=1);

namespace Vendor\Inbox\Internals\Integration\Mail;

use Bitrix\Main\Application;
use Bitrix\Main\DI\ServiceLocator;
use Bitrix\Main\Event;
use Vendor\Inbox\Application\Service\IncomingMailService;

final class IncomingMailHandler
{
    public static function onMailMessageNew(Event $event): void
    {
        // false: письмо старше 12 часов или пришло не через IMAP-синхронизацию
        if (!$event->getParameter('isFreshArrival')) {
            return;
        }

        $message = (array)$event->getParameter('message');

        self::guard(static fn (IncomingMailService $service) => $service->accept(
            mailboxId: (int)($message['MAILBOX_ID'] ?? 0),
            messageId: (int)($message['ID'] ?? 0),
        ));
    }

    public static function onMailMessageBodySynced(Event $event): void
    {
        self::guard(static fn (IncomingMailService $service) => $service->completeBody(
            mailboxId: (int)$event->getParameter('mailboxId'),
            messageId: (int)$event->getParameter('messageId'),
            bodyArrived: (bool)$event->getParameter('bodyArrived'),
        ));
    }

    // Event::send() пробрасывает исключение обработчика, и проход синхронизации оборвётся
    private static function guard(\Closure $job): void
    {
        try {
            $job(ServiceLocator::getInstance()->get(IncomingMailService::class));
        } catch (\Throwable $e) {
            Application::getInstance()->getExceptionHandler()->writeToLog($e);
        }
    }
}

    

IncomingMailService здесь ваш, он зарегистрирован в секции services файла .settings.php модуля. В accept() он решает, есть ли у письма тело, а в completeBody() доделывает работу или отмечает, что тело в этом проходе не пришло. Оба метода обработчика подписываются через registerEventHandler() в install/index.php.

Посмотрите фичи за флагами

Остальные фичи включаются флагами, и почти все по умолчанию выключены:

Фича Флаг По умолчанию
Серверные черновики internal_drafts_enabled, для веба ещё нужен main ≥ 26.800.0 N
Пользовательские метки user_labels_enabled, лимит user_labels_limit = 20 N
Новая форма письма compose_redesign_enabled N
AI-классификация входящих auto_classify_incoming плюс свойство ящика AUTO_CLASSIFY_ENABLED N
Макросы в подписях signature_macros_enabled N
Шаблоны в форме письма compose_templates_enabled (только в новой форме) Y

Черновики хранит Internal\Service\Draft\DraftService в таблицах b_mail_draft и b_mail_draft_attachment. DraftRepository держит оптимистичную блокировку по REVISION и идемпотентность по CLIENT_ID, черновик живёт 30 дней, старые раз в час чистит агент DraftCleanupAgent::run(). Контроллер Controller\Draft умеет config, save, get, list, delete и deleteMany, для CRM есть Public\Service\Draft\DraftSendService и DraftFeature. Версию main проверяет Helper\Config\InternalDraftCompatibility.

Метки собраны из Controller\Label, Internal\Service\Label\LabelService и LabelCountersService, фильтра MessageFilter::addLabel(), компонента mail.client.config.labels и JS-расширений mail.label.*.

AI-классификатор Integration\AI\MessageClassifier подписан на onMailMessageNew, onMailMessageBodySynced и ai:onQueueJobExecute (install/migrations/events.php). TritonMessageClassifier отправляет Payload\Classify в движок ai через completionsInQueue(), а метку по ответу ставит MessageClassifier, если вероятность выше 0.6 (CLASSIFICATION_THRESHOLD). Очередь Messenger mail_message_classify работает на своём брокере mail_classify_db со своей таблицей Internal\Async\Model\MessengerTable, в .settings.php у неё limit 10 и total_processing_limit 500. Ресивер ClassifyMailMessageReceiver между письмами сверяется с бюджетом прохода PASS_TIME_BUDGET в 15 секунд.

Новая форма письма — это Controller\ComposeForm (getSignatures, getCalendarSharingLink), Service\Compose\ComposeFormDataProvider, SignatureListProvider, ReplyRecipientResolver, MessageQuoteBuilder, шаблон compose у mail.client.message.new и расширение mail.client.compose-form.

Шаблоны писем включены флагом (compose_templates_enabled = Y), но показывает их только новая форма письма, а она выключена. Controller\MailTemplate с действиями quickList, search, prepare, recordUsage и setRememberLast работает через Internal\Service\MailTemplate\TemplateCatalog поверх интерфейса TemplateProvider, а Integration\Crm\CrmTemplateProvider пускает к шаблонам CRM только тех, у кого есть доступ к CRM.

Сбойные письма уходят в карантин

Helper\Mailbox\HistorySyncAttemptService считает неудачные попытки загрузить письмо. После трёх (MailboxSyncManager::MAX_CONNECTION_ATTEMPTS_BEFORE_UNAVAILABLE) письмо уходит в карантин. Папку, которая отказала целиком, повторно не трогают 40 минут (RETRY_DELAY). Исход по каждому UID сообщают MessageSyncPassResult и Imap::syncMessagesPass().

Прочее

  • Макросы подписей живут в Internal\Service\Signature\Template\* и SignatureMacroCatalog. Для мобильных подписей добавлены Controller\Signature::addOwnerAction(), deleteOwnerAction(), getAvailableForSendersAction(), setChoiceAction(), Service\SharedSignature\SignatureContextService и Dto\MobileSignatureContextDto, включает их Feature::isMobileSignaturesAvailable().
  • Отдельное право на управление общими подписями: PermissionDictionary::MAIL_SHARED_SIGNATURES_MANAGE = '205'. У администратора оно включено, у руководителя и сотрудника выключено. Выдаёт его агент SharedSignaturePermissionInstaller под флагом shared_signature_permission_enabled.
  • OAuth: Helper\OAuth::classifyProviderError(), getErrorScenarioMessage() и renderError() разбирают ошибки провайдера, включая коды AADSTS. Профиль OAuth-аккаунта теперь запрашивает отдельное действие MailboxConnecting::getOauthUserAction(), форма настроек mail.client.config больше не делает этого при рендере.
  • Письмо можно распечатать: bindPrintControl() в скрипте mail.client.message.view и правила @media print в его стилях.

БД

Схему меняет install/migrations/tables.php:

  • b_mail_mailbox: колонка EMAIL_NORMALIZED varchar(255) и индекс IX_B_MAIL_MAILBOX_EMAIL_NORMALIZED;
  • b_mail_mailbox_alias (ID, MAILBOX_ID, EMAIL): история адресов ящика с UNIQUE-индексом по (MAILBOX_ID, EMAIL);
  • b_mail_message_uid: индекс IX_MAIL_MSG_UID_DIR_START_DATE по (MAILBOX_ID, DIR_MD5, DELETE_TIME, IS_OLD, INTERNALDATE, MESSAGE_ID);
  • b_mail_task_source (TASK_ID PK, SOURCE_TYPE varchar(32), SOURCE_ID);
  • b_mail_draft и b_mail_draft_attachment с UNIQUE-индексами UX_B_MAIL_DRAFT_ATTEMPT (USER_ID, CONTEXT_TYPE, CLIENT_ID) и UX_B_MAIL_DRAFT_CRM_CONTEXT;
  • b_mail_user_label (UNIQUE по USER_ID, MAILBOX_ID, NAME) и b_mail_message_label (PK LABEL_ID, MESSAGE_ID);
  • b_mail_messenger_message: колонки повторяют b_main_messenger_message, у индексов префикс модуля.

В install/migrations/agents.php добавлен DraftCleanupAgent::run() с интервалом 3600, в install/migrations/events.php три обработчика классификатора.

Мелочи и находки

У своей таблицы очереди MessengerTable::isCacheable() возвращает false. Комментарий обосновывает это замером. Каждая запись в общую таблицу платит Entity::cleanCache(), это 3,70 мс на сообщение против 0,12 мс самой вставки. BrokerManager ядра принимает брокеры модулей с params.table, так что тяжёлую очередь своего модуля можно увести из b_main_messenger_message тем же способом:

        <?php declare(strict_types=1);

namespace Vendor\Inbox\Internal\Async\Model;

use Bitrix\Main\Messenger\Internals\Storage\Db\Model\MessengerMessageTable;

class MessengerTable extends MessengerMessageTable
{
    public static function getTableName(): string
    {
        return 'b_vendor_inbox_messenger_message';
    }

    public static function isCacheable(): bool
    {
        return false;
    }
}

    

В .settings.php модуля брокер и очередь описываются так:

        <?php declare(strict_types=1);

use Vendor\Inbox\Internal\Async\Model\MessengerTable;
use Vendor\Inbox\Internal\Async\Receiver\ImportReceiver;

return [
    'messenger' => [
        'value' => [
            'brokers' => [
                'vendor_inbox_db' => [
                    'type' => \Bitrix\Main\Messenger\Internals\Broker\DbBroker::TYPE_CODE,
                    'params' => ['table' => MessengerTable::class, 'module' => 'vendor.inbox'],
                ],
            ],
            'queues' => [
                'vendor_inbox_import' => [
                    'broker' => 'vendor_inbox_db',
                    'handler' => ImportReceiver::class,
                    'limit' => 10,
                ],
            ],
        ],
        'readonly' => true,
    ],
];

    

Таблицу с колонками b_main_messenger_message создаёт инсталлятор модуля. Комментарий в tables.php у mail объясняет, зачем индексам префикс модуля: в PostgreSQL имя индекса живёт на уровне схемы, и без префикса оно столкнулось бы с индексами b_main_messenger_message.

Ещё несколько находок:

  • TritonMessageClassifier создаёт контекст ai без пользователя, поэтому запрос тратит общий бюджет Copilot портала. Отказ по квоте ловится через onError, иначе исчерпанная квота выглядела бы как письмо без меток.
  • Комментарий к сверке поколений UIDVALIDITY допускает серверы, которые вопреки RFC 3501 продолжают отвечать по старым UID. Их строки живут, пока не найдут пару.
  • Токены макросов подписи кириллические: {{имя}}, {{фио}}, {{должность}}, {{юр_адрес}}, {{физ_адрес}} (SignatureMacroCatalog).
  • Feature::isMobileSignaturesAvailable() читает опцию чужого модуля (mailmobile, signatures_enabled), чтобы у mail не появилась зависимость от мобильных модулей.
  • ComposeForm::getSignaturesAction() отдаёт карты как (object). Пустой PHP-массив ушёл бы в JSON как [], и тип поля зависел бы от того, есть ли данные.
  • В Helper\OAuth::classifyProviderError() один и тот же абзац комментария про коды Microsoft повторён дважды, до разбора кодов AADSTS и после него, и копии отличаются одним словом (below/above).
  • Из тулбара mail.client.config убрали кнопку «Подписи» (createSignaturesButton()).

Устаревшие и удалённые API в этой версии

Символ Статус Чем заменять
Bitrix\Mail\Helper\Mailbox::findActiveMailbox() Deprecated Bitrix\Mail\Internal\Service\Mailbox\MailboxEmailOccupancyService::isAddressHeldByUser() / Bitrix\Mail\Internal\Service\Mailbox\MailboxEmailOccupancyService::checkOccupancy()
Bitrix\Mail\Controller\ActionFilter\SharedSignatureAccess::ERROR_LICENSE_DENIED Удалено —
Bitrix\Mail\Helper\Mailbox\Imap::excludeRegisteredUids() Удалено Bitrix\Mail\Helper\Mailbox\Imap::collectPendingUids()

Новая версия вышла — разбор уже в Telegram

В канале @bxmax_updates публикуем каждый свежий разбор: что сломается, что устарело и что появилось в API. Узнаете до того, как обновите прод.

Подписаться

Читайте дальше

mail 26.1200.0 Заметное Свежее

Mail 26.1200.0: отложенная отправка, отмена отправки и автоответ за выключенными опциями

Mail 26.1200.0 идёт следом за [26.1100.0](/bitrix-updates/mail/mail-26-1100-0) и по объёму тянет на несколько релизов: отложенная отправка, отмена отправки, автоответ «в отпуске», группировка писем в переписки, картинки в подписях и генерация темы через AI. Всё это выключено опциями модуля, и после…

2 мин
mail 26.1100.0 Заметное Свежее

Mail 26.1100.0: smtpd.php без токена отвечает 404, а ящики удалённых пользователей удаляет агент

Mail 26.1100.0 вышел следом за [26.1000.0](/bitrix-updates/mail/mail-26-1000-0). Вендор пишет только о повышенной стабильности подключённых ящиков. В коде за этим стоят три вещи: инфраструктура «поколений источника» для переезда ящика на другой IMAP-сервер без потери истории (по умолчанию выключена)…

5 мин
mail 26.900.0 Заметное

Mail 26.900.0: общие подписи ломают старый API, а тяжёлые вложения переезжают на Диск

Модуль mail получил один из самых жирных релизов за последнее время: 523 файла, +39 182/−3 131 строк. Внутри — три большие фичи (общие подписи, автоперенос тяжёлых вложений на Диск, избранное с AI-классификацией писем), переработанный IMAP-синк истории и три новые таблицы в БД. Если вы кастомизирова…

4 мин
mail 26.700.0 Заметное

mail 26.700.0: статистика писем для AI-ассистента и предупреждение о провайдерах

Модуль научился считать входящие по ящику или сотруднику за период и отдавать это AI-ассистенту как инструмент. Плюс появился слой ограничений по почтовым провайдерам. Основной риск обновления — не удаления, а вставка аргументов в середину конструкторов двух DTO.

3 мин
Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

AI Домовой История

на связи

пишет…
Нет истории чатов
AI Домовой

Нужна авторизация

Войдите, чтобы задавать вопросы AI Домовому.

Войти