Справочный центр eXpress

Собрали ответы на все самые популярные вопросы, чтобы вам было легко и удобно пользоваться eXpress. Также вам может помочь наша База знаний. Если вы не найдете ответ на свой вопрос, свяжитесь с нашей службой поддержки.

Чаты, каналы и обсуждения

Классификация чатов и их свойства

В приложении есть следующие чаты:
  • персональные — чат с одним пользователем;
  • групповые — для общения, писать могут все, участники видят друг друга;
  • каналы — для новостей, пишут и видят участников только администраторы;
  • комнаты — групповые чаты, которые хранят переписку и материалы групповых звонков и конференций;
  • чат «Сохранённые сообщения» — предназначен для отправки сообщений самому себе. Включается или восстанавливается после удаления в настройках приложения. Любое сообщение можно отправить в этот чат через контекстное меню.
  • обсуждения — это возможность любому участнику прокомментировать то или иное сообщение в групповом чате или канале.
  • глобальные чаты — это специальные чаты, видимые всем пользователям того или иного сервера.
По составу участников чаты бывают:
  • персональные с контактами из телефонной книги;
  • персональные с корпоративными контактами;
  • групповые с контактами из телефонной книги;
  • групповые с корпоративными контактами;
  • смешанные чаты с участниками с серверов разных организаций;
  • персональные чаты с чат-ботами.
Чтобы определить тип чата по составу участников:
  • Посмотрите на значок рядом с названием чата:
    • зеленый глобус — чат внешних пользователей на публичном сервере (RTS);
    • синий глобус — чат пользователей корпоративных серверов (CTS) из разных организаций;
    • сине-зелёный глобус — смешанный чат с корпоративными и внешними пользователями;
    • синий чемоданчик — чат с пользователями с текущего или с трастового корпоративного сервера;
    • синий мегафон — канал с корпоративными пользователями;
    • сине-зелёный мегафон — канал с корпоративными и внешними пользователями;
    • синий бот — персональный чат с корпоративным чат-ботом.
  • Откройте Роутинг в свойствах чата.
  • Откройте и просмотрите список участников чата: участники сгруппированы по принадлежности к серверам.

Сравнение группового чата и канала

  1. В групповом чате могут писать все участники, в канале — только администраторы. Канал удобен для публикации новостей, групповой чат — для общения всех участников.
  2. В групповом чате любой участник может просматривать состав чата и видеть системные сообщения, в канале — только администратор. Обычные подписчики канала (не администраторы) не могут видеть других участников канала.
  3. Для обычных участников отправитель сообщения в канале — сам канал, для администраторов — конкретный пользователь.
  4. Если в чате/канале включены обсуждения:
    • в каналах нет автоматической подписки на обсуждения, пользователь сам присоединяется к интересующему его обсуждению;
    • в чатах есть автоматическая подписка на обсуждения, она зависит от того, включены ли в групповом чате/канале уведомления или нет.

Хранение и удаление чатов

Все чаты хранятся на сервере в зашифрованном виде. Благодаря этому данные не потеряются, даже если вы завершите сессию или переустановите приложение — просто войдите в приложении снова, и чаты загрузятся с сервера на ваше устройство. На устройстве чаты также будут храниться в зашифрованном хранилище.
Чаты хранятся на серверах каждого участника переписки. Например, если в групповой чат добавят участника с нового корпоративного сервера, чат скопируется на этот сервер.
После повторного входа, если вы откроете любой чат, в нём загрузятся последние 50 (веб/десктоп) или 100 сообщений/системных сообщений (мобильное приложение). Чтобы загрузилась более старая история, прокрутите чат вверх.

Доступ к чатам потеряется только в следующих случаях: Чаты можно удалить только локально с устройства (командой Удалить чат в контекстном меню чата), на сервере их удалить нельзя. Для экономии места на сервере системный администратор может настроить автоматическую очистку файлов, которые передавались в чатах.

Ограничения по числу участников в групповом чате или канале

В чатах с числом участников больше 128 в списке участников отображаются лишь администраторы. Чтобы найти остальных участников чата, воспользуйтесь полем поиска по участникам.

Если на корпоративном сервере настроено, что чаты и каналы создаются с включённым сквозным шифрованием по умолчанию, то у созданного чата будет ограничение в 128 участников. Чтобы его снять, администратору чата нужно выключить Сквозное шифрование в свойствах чата, либо нужно обратиться к администратору корпоративного сервера, чтобы он выключил сквозное шифрование (к администратору корпоративного сервера можно обратиться через поддержку своей организации или через поддержку eXpress). Вернуть сквозное шифрование в чате можно только в случае, если количество участников не превысило 128 пользователей.

В чатах с выключенным сквозным шифрованием есть техническое ограничение в 8000 пользователей. Этот показатель будет увеличен в будущем.

Каталог, открытые и закрытые групповые чаты и каналы

В разделе Каталог над списком чатов находятся открытые групповые чаты, каналы и корпоративные боты. К ним имеет доступ любой пользователь корпоративного сервера.

Подробнее об открытых и закрытых групповых чатах и каналах:
  • Открытые чаты и каналы, доступные в корпоративном каталоге чатов. Это корпоративные чаты и каналы, к которым может присоединиться любой желающий из зарегистрированных на сервере пользователей через раздел Каталог. Создать изначально открытый чат или канал, или преобразовать закрытый чат или канал в открытый может только администратор корпоративного сервера.
    Администратору: как сделать чат или канал открытым на сервере В панели администратора корпоративного сервера:
    • Чтобы создать открытый чат или канал, откройте раздел Открытые чаты и нажмите кнопку Создать. Первого участника открытого чата нельзя добавить в панели администратора. Первый участник должен сам присоединиться с клиентского приложения через каталог.
    • Чтобы преобразовать закрытый чат или канал в открытый, найдите чат в разделе Чаты и нажмите кнопку Преобразовать в открытый чат. При преобразовании закрытого чата или канала в открытый в нём автоматически выключается сквозное шифрование. При необходимости сквозное шифрование можно будет включить позже, но при этом не стоит забывать об ограничениях, накладываемых им.
    Чаты загружаются на серверы каждого участника переписки. Поэтому, чтобы сделать чат открытым на нескольких серверах, нужно для начала убедиться, что в чате есть хотя бы один пользователь с нужного сервера. Если его нет, необходимо добавить в чат хотя бы одного такого пользователя.
  • Закрытые чаты и каналы. Такими корпоративные чаты и каналы создаются по умолчанию. Персональные чаты являются только закрытыми. Закрытые групповые чаты и каналы предназначены для ограниченного круга лиц и поэтому недоступны в корпоративном каталоге чатов. Новых пользователей в закрытый групповой чат или канал добавляет администратор данного чата вручную или по пригласительной ссылке. Администратор корпоративного сервера может сделать открытый чат или канал закрытым в панели администратора.
    Администратору: как закрыть чат или канал на сервере Чтобы закрыть открытый чат, в панели администратора откройте раздел Открытые чаты и нажмите кнопку-корзину напротив желаемого открытого чата или в разделе Чаты найдите чат и на его странице нажмите кнопку Удалить из открытых чатов.

Администраторы чатов и каналов

Создатель группового чата или канала может сделать других участников администраторами. Администраторы, как и создатель чата, могут управлять списком участников, менять информацию о чате или канале, а также видеть подписчиков канала. Аватары участников-администраторов в списке для упоминания в чате через @ помечены значком звезды.

Лишить участника прав администратора может создатель или другой администратор чата в контекстном меню участника (веб/десктоп) или по долгому нажатию на него в списке участников (мобильные клиенты).
Администратору: управление правами администратора чата на сервере Можно управлять правами администратора чата в панели администратора: в разделе Чаты найдите нужный чат > откройте Пользователи > переключайте true и false в столбце Администратор у желаемых пользователей. Учтите, что для этого в чате должен присутствовать администратор чата, зарегистрированный на этом корпоративном сервере.

Роутинг — как идёт трафик в чате

Роутинг (схема маршрутизации) — это наглядное схематическое представление маршрутов обмена сообщениями между пользователями в чате любого типа. Чтобы просмотреть схему роутинга, откройте свойства чата и выберите Роутинг.

Схема формируется относительно участника чата, просматривающего её. Линиями на схеме указаны пути от сервера данного пользователя до серверов других участников чата. Трастовые (доверенные) корпоративные серверы соединены линией друг с другом.

Таким образом, роутинг чата позволяет увидеть, через какие серверы маршрутизируется трафик в конкретном чате.

Если чат находится в рамках одного сервера или между трастовыми серверами, трафик будет идти, минуя публичный региональный транспортный сервер (RTS). Если чат создан между нетрастовыми серверами или среди участников есть хотя бы один пользователь с внешнего сервера, трафик будет идти через публичный региональный транспортный сервер.

Глобальные чаты

Существуют следующие разновидности глобальных чатов:
  • Федеративный глобальный чат eXpress. Называется «eXpress». Содержит новости о выпусках новых версий eXpress, в будущем — будет отображать коды подтверждения, позволит обращаться в поддержку eXpress. Находится на RTS и доступен всем пользователям, зарегистрированным на RTS или чей корпоративный сервер (CTS) подключён к RTS. Администратор ETS может выключить федеративный чат eXpress в настройках глобального чата на ETS.
  • Глобальный чат ETS. Называется так, как укажет администратор. Содержит новости о выпусках новых версий специального приложения организации с функциями eXpress, в будущем — будет отображать коды подтверждения, позволит обращаться в поддержку организации. Находится на ETS и доступен всем пользователям, чей корпоративный сервер подключён к ETS.
  • Корпоративный глобальный чат на CTS. Называется так, как укажет администратор. Содержит информацию, предназначенную для всех пользователей корпоративного сервера. Находится на CTS и доступен всем пользователям корпоративного сервера.
Если администратор сервера включит тот или иной глобальный чат, тот станет сразу доступен в общем списке чатов у соответствующих пользователей. Глобальный чат является закрытым чатом (не отображается в корпоративном каталоге чатов). Кому какой глобальный чат может быть виден среди других чатов:
  • Пользователи RTS видят только один глобальный чат — федеративный, находящийся на RTS.
  • Пользователи CTS могут видеть два глобальных чата: один федеративный с RTS, другой корпоративный с CTS (если настроен администратором).
  • Пользователи ECTS могут видеть два глобальных чата (один с ETS, другой с ECTS), только один из них, либо никакой из них (зависит от того, настроены ли они администратором).
Глобальные чаты на RTS/ETS и CTS/ECTS являются разными сущностями и не разделяют общую историю.

Писать в глобальный чат могут только те пользователи, которые были назначен администраторами специального бота для глобального чата в панели администратора корпоративного сервера. См. как создать публикацию в глобальном чате.

История и сквозное шифрование

По типу шифрования чаты бывают двух видов:
  • Чаты, где сквозное шифрование выключено — в них можно добавить больше 128 участников, и история с момента отключения открыта для новых вступивших участников. Переписка шифруется общим серверным ключом, а не ключами каждого отдельного участника чата. Потому и появится возможность добавить в чат больше пользователей (сквозное шифрование ресурсоёмко) и увидеть старую историю. Это менее безопасно, потому что в теории, если кто-либо получит доступ к серверу, он сможет похитить общий ключ с сервера, загрузить зашифрованную переписку к себе и расшифровать её похищенным ключом. Чаты с отключённым сквозным шифрованием на аватаре имеют значок с приоткрытым замочком.

    Внимание! При отключении сквозного шифрования история станет доступной новым участникам только с момента отключения сквозного шифрования! Если нужно, чтобы новые участники видели всю историю чата, создателю или администратору чата необходимо выключить сквозное шифрование на этапе создания чата, а также добавить минимум по одному участнику с каждого корпоративного сервера.

    Выключить сквозное шифрование в каналах можно только из приложения, у администратора корпоративного сервера данный функционал отсутствует.
  • Чаты, где сквозное шифрование включено — в них можно добавить строго до 128 участников, история для новых вступивших участников закрыта. Никакое третье лицо не сможет расшифровать переписку, поскольку не существует общего универсального ключа, и переписка расшифровывается только у тех, кто присутствует в чате, их личными уникальными ключами. Единственный минус сквозного шифрования — новые участники чата не смогут увидеть старую переписку, потому что старые сообщения не шифровались их ключами, так как их ещё не было в чате. Перешифрование старых сообщений для новых участников не выполняется.

    В персональных чатах сквозное шифрование всегда включено, выключить его нельзя.

Открытие истории для новых участников

Управлять сквозным шифрованием может создатель или администратор чата или администратор корпоративного сервера.

Чтобы снять ограничение на количество участников и открыть историю для новых участников, вступающих в чат, выключите сквозное шифрование в чате: в приложении откройте свойства чата или канала и выключите тумблер Сквозное шифрование. Выключить сквозное шифрование можно также при добавлении нового участника. Администратор корпоративного сервера тоже может преобразовать чат или канал в открытый или выключить сквозное шифрование.

Чаты загружаются на серверы каждого участника. Поэтому, если на момент отправки сообщений в чате не было никого с конкретного сервера, это означает, что сообщения на тот сервер не попадали, и первый участник с того сервера старые сообщения не увидит. Следующие пользователи этого сервера увидят историю с момента добавления первого пользователя с их сервера.

Пользователь увидит, но не не сможет открыть закреплённое в чате сообщение, если его добавили в чат с включённым шифрованием позже отправки этого закреплённого сообщения.

Поэтому, если администратору чата или канала нужно, чтобы новые участники видели всю историю, необходимо ещё на этапе создания чата, заранее:
  • добавить хотя бы по одному пользователю со всех нужных серверов;
  • выключить сквозное шифрование сразу.
Администратору: признак открытой истории в «Чат JSON» В панели администратора в разделе Чат JSON по признаку sharedHistory:true можно определить, открыта ли история чата. Он появляется при выключении сквозного шифрования в панели администратора (это возможно только для чатов).

Очистка истории чата, канала или обсуждения

Команда Очистить историю чата/канала/обсуждения в свойствах (карточке) чата, канала или обсуждения удаляет все сообщения внутри них на всех ваших устройствах, но не на сервере — они останутся там в зашифрованном виде. Чат, канал или обсуждение не исчезнут из списка чатов, а переместятся по времени на момент, когда были созданы. Это может произойти не сразу, поскольку на обновление локального кэша требуется некоторое время.

Удалённую историю в чате или канале можно вернуть, для этого:
  1. Покиньте чат или канал, в котором вы удалили историю: удалите его из списка чатов или найдите себя в списке участников, вызовите контекстное меню и выберите команду Выйти.
  2. Вернитесь в чат или канал по пригласительной ссылке или попросите администратора добавить вас обратно.
  3. Ранее удалённая история загрузится с сервера на все ваши устройства.

Работа с чатами и каналами

Как найти нужный чат или канал

Чтобы найти персональный чат, в поле поиска над списком чатов начните набирать имя (ФИО) пользователя. Пользователя можно будет найти, даже если персонального чата с ним ещё нет, но у вас с ним есть общий групповой чат.

Выберите Каталог над списком чатов, чтобы просмотреть открытые корпоративные групповые чаты, каналы и ботов корпоративного сервера, на котором Вы зарегистрированы.

Если искомый чат или канал закрыт, в каталоге вы его не увидите. В закрытые корпоративные чаты и каналы можно попасть только по приглашению создателя или администратора, либо вы можете попросить администратора корпоративного сервера добавить вас в желаемый закрытый чат через панель администратора (для этого обратитесь в поддержку своей организации или в поддержку eXpress).

Создание персонального чата, группового чата или канала

Для создания персонального чата выполните действия, описанные в таблице ниже.
Версия приложения Как написать контакту
Android

Откройте карточку контакта из раздела Контакты > нажмите кнопку сообщения; найдите его в поиске по чатам; нажмите «карандаш» внизу справа на списке чатов > найдите пользователя > откройте его карточку > нажмите кнопку сообщения

iOS

Откройте карточку контакта из раздела Контакты > нажмите кнопку сообщения;

нажмите «карандаш» в правом верхнем углу > найдите пользователя > откройте его карточку > нажмите кнопку сообщения

Веб/десктоп

Откройте карточку контакта из раздела Контакты > нажмите кнопку сообщения;

найдите его в поиске по чатам;

нажмите «плюсик» в правом верхнем углу > Персональный чат > выберите контакт


Для создания группового чата или канала выполните действия, описанные в таблице ниже.
Версия приложения Как создать групповой чат или канал
Android Нажмите «карандаш» внизу справа на списке чатов > выберите Групповой чат или Канал > добавьте пользователей через поиск или создайте пригласительную ссылку > укажите название чата > чат создан
iOS Нажмите «карандаш» в правом верхнем углу > далее аналогично
Веб/десктоп Нажмите кнопку-«плюс» в правом верхнем углу > далее аналогично

Как создать открытый чат, доступный в корпоративном каталоге

Это может сделать администратор корпоративного сервера: инструкция выше.

Добавление участников в чат или канал

Существуют следующие способы добавления участников:
  1. Администратор чата или канала может добавить участников вручную. Для этого ему нужно открыть карточку чата, нажать Добавить участников и воспользоваться поиском.
    Когда поиск с точным совпадением включён в настройках приложения, в результатах поиска будут отображены только те результаты, которые точно соответствуют имени контакта (или названию чата, если искать в разделе Чаты). То есть, при вводе фамилии руководителя найдётся только его контакт. По умолчанию эта функция выключена.
  2. Администратор чата или канала может создать пригласительную ссылку, которую можно распространить. Для этого ему нужно открыть карточку чата, нажать Добавить участников – Ссылка и далее создать ссылку с определёнными параметрами доступа:
    • Доверенных корпоративных серверов. По ссылке смогут подключаться пользователи с вашего или с трастового корпоративного сервера (CTS).
    • Любого корпоративного сервера. По ссылке смогут подключаться пользователи с любого корпоративного сервера, то есть из других организаций.
    • Всем (включая гостей). По ссылке смогут подключаться любые пользователи, в том числе с личных аккаунтов или как гости (только на ПК).
  3. Групповой чат или канал можно сделать открытым, чтобы пользователи корпоративного сервера могли его найти через Каталог и добавлялись самостоятельно. Для этого обратитесь к системному администратору корпоративного сервера. Для связи с администратором обратитесь в поддержку своей организации или в поддержку eXpress.
  4. Можно настроить правила в специальном боте Invite Bot для автоматического добавления пользователей в чаты по критериям. Этот бот доступен в бесплатной коллекции ботов и SmartApp и его нужно установить на корпоративный сервер.
  5. Также администратор корпоративного сервера может вручную или списком добавить пользователей через панель администратора. Чтобы связаться с ним, обратитесь в поддержку своей организации или в поддержку eXpress.
Администратору: массовое добавление пользователей в чат списком Существует способ через панель администратора быстро добавить в чат/канал множество пользователей. Например, чтобы добавить всех пользователей корпоративного сервера:
    1. Выгрузите всех зарегистрированных корпоративных пользователей: Пользователи > Cкачать как csv > отметьте только cts_user > сохраните файл.
    2. Далее в панели администратора откройте чат > Пользователи > Импорт пользователей и загрузите ранее сохранённый файл.

Режим конфиденциальности в чате

В режиме конфиденциальности можно настроить удаление сообщений по таймеру после прочтения или после написания, как на клиентах, так и на сервере. Это единственный способ удалить сообщения на сервере. Кроме того, в режиме конфиденциальности включается дополнительная защита от снятия скриншотов, нельзя скачать файлы (их можно просмотреть в чате, но поддерживаются не все форматы). Также возможно настроить режим так, чтобы доступ к сообщениям был возможен только с мобильных устройств.

Режим конфиденциальности может включить и настроить любой корпоративный участник группового и персонального чата, если в нём нет участника с публичного сервера. В каналах включить режим конфиденциальности нельзя.

На устройствах Android для того, чтобы можно было пользоваться режимом конфиденциальности в чате, приложению должен быть предоставлен доступ к хранилищу (к файлам). Это необходимо для того, чтобы нельзя было обойти систему контроля скриншотов. Поэтому при первом открытии чата в режиме конфиденциальности в диалоговом окне доступа к файлам выберите Разрешить.

Создание опроса в групповом чате или канале

Для создания опроса в чате используется специальный бот Vote Bot. Его можно найти в корпоративном каталоге чатов.

Этот бот доступен в бесплатной коллекции ботов и SmartApp и его нужно установить на корпоративный сервер. Если в чате участвует несколько серверов, бот должен быть опубликован на всех серверах, пользователи которых прошли опрос, иначе бот не получит информацию об этих пользователях и выдаст ошибку при попытке скачать результаты опроса. Чтобы заказать установку бота, обратитесь к администратору сервера через поддержку своей организации.

Как создать опрос с помощью Vote Bot:
  1. Напишите этому боту в персональном чате, создайте опрос.
  2. Добавьте бота в групповой чат или канал, где нужно провести опрос. В канале обязательно нужно сделать бота администратором.
  3. Завершите создание опроса в чате с ботом, следуя инструкциям.

Скачивать файлы, присланные ботом, смогут только пользователи того сервера, на котором развёрнут основной бот.

Добавление стикеров

Вот какими способами можно добавить стикеры:
  1. Через стикер, отправленный кем-либо в чат: нажмите любой стикер, чтобы добавить этот набор стикеров себе.
  2. Через специального бота, в котором также есть команда для переноса стикеров из Telegram (анимированные стикеры не поддерживаются). Если этот бот отсутствует в корпоративном каталоге чатов, обратитесь к администратору сервера через поддержку своей организации и попросите установить этого бота.
  3. Можно создать собственный набор стикеров. Это делается администратором в панели администратора сервера в разделе Стикеры. Для этого обратитесь к администратору своего корпоративного сервера через поддержку своей организации.

Отправка сообщения или файла самому себе

Чтобы отправить сообщение или файл самому себе, воспользуйтесь чатом Сохранённые сообщения. Любое сообщение в него можно отправить и через контекстное меню: в веб- или десктоп-версии приложения щелкните правой кнопкой мыши на желаемом сообщении, а в мобильном приложении — нажмите и удерживайте палец на сообщении.

Как вернуться в чат, если удалил(а) его или был логаут аккаунта

Если вы удалили персональный чат, его можно просто заново создать из списка контактов.

Если вы удалили у себя групповой чат или канал, вернуть вас в него сможет его администратор или создатель (если вы и были администратором, новый администратор назначается сервером автоматически), также администратор может направить вам пригласительную ссылку для присоединения в чат. Либо администратор корпоративного сервера может добавить вас вручную через панель администратора — свяжитесь с администратором сервера через поддержку своей организации или через поддержку eXpress.

Если вы выйдете из корпоративного сервера (запросите логаут и администратор сервера подтвердит его), вы потеряете доступ:
  • ко всем корпоративным чатам вне зависимости от того, в какой момент вы были в них добавлены (Вы исключитесь из состава участников в связи с выходом с корпоративного сервера);
  • к переписке в персональных чатах, которая велась, пока вы были на корпоративном сервере (переписка, когда вы общались с личного аккаунта, останется).
Но если позднее вы вернётесь на тот же корпоративный сервер, персональная корпоративная переписка вернётся, а в групповых чатах и каналах переписка вернётся после того, как вы через администратора группового чата или канала или с помощью администратора корпоративного сервера вернётесь в них.

Как создать публикацию в глобальном чате

Если глобальный чат включён и вы были назначены администратором бота-оповещателя на корпоративном сервере, то вы сможете отправлять сообщения в глобальный чат, которые будут видны всем пользователям сервера:
  1. Начните чат с ботом Notifications bot одним из следующих способов:
    • Откройте глобальный чат в приложении. Нажмите название чата, чтобы открыть его свойства. В списке участников чата найдите Notifications bot > в контекстном меню бота в списке выберите Отправить сообщение или нажмите на бота и в его свойствах нажмите кнопку Написать.
    • Либо найдите бота Notifications bot в корпоративном каталоге > откроется чат с ботом.
  2. Если необходимо, в чате с ботом нажмите внизу кнопку Начать.
  3. В чате с ботом отправьте команду /start_post.
  4. Далее отправьте в чат с ботом любое количество сообщений, которые нужно будет направить в глобальный чат. Можете свободно редактировать и удалять их в течение 48 часов, пока боту не отправлена команда завершения создания сообщений. Поддерживаются любые сообщения, кроме геолокации. Контакты и голосовые сообщения будут выглядеть как прикреплённые файлы, а ссылки будут без предпросмотра.
  5. Когда сообщения будут готовы, отправьте боту команду завершения создания сообщений для глобального чата /finish_post, затем нажмите Confirm (Подтвердить).
  6. В глобальном чате появятся ваши сообщения.
  7. Сообщения, отправленные в глобальный чат напрямую, не через бота, видны только их отправителю. Эта возможность оставлена для всех на будущее: разрабатывается бот для поддержки пользователей, пишущих в глобальный чат.

Работа с обсуждениями

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

Версии клиентов и сервера с поддержкой обсуждений:
  • Веб/десктоп 2.5
  • iOS 2.20
  • Android 2.20
  • Бэкенд 2.6
Внимание! Если в чате или в канале есть хотя бы один участник, на сервере которого версия бэкенда ниже 2.6, при попытке включить функцию обсуждений появится «Ошибка включения обсуждений» или «Функция обсуждений не поддерживается».

Как выглядят и работают обсуждения

Обсуждение по сути представляет собой чат, связанный с исходным обсуждаемым сообщением. В обсуждении нельзя звонить, просматривать роутинг и закреплять сообщения. В самом обсуждении первым идёт исходное сообщение с именем его автора, далее находится горизонтальная полоса, обозначающая начало обсуждения, после которой от старых к новым идут комментарии других пользователей.

В контекстном меню исходного сообщения можно перейти в чат или канал, в котором оно находится.

Обсуждение не может быть начато без исходного сообщения. Создать обсуждение внутри обсуждения тоже нельзя.

Поиск текста внутри исходного чата не ищет сообщения внутри его обсуждений. Чтобы найти текст в обсуждении, нужно сначала открыть это обсуждение.

Через контекстное меню сообщений, пересланных из обсуждения, можно будет перейти в исходное обсуждение.

При удалении исходного сообщения, с которого было начато обсуждение, обсуждение не удаляется. Переход в обсуждение возможен через местозаполнитель удалённого сообщения Данное сообщение удалено. Если удалить этот местозаполнитель, соответствующее обсуждение также будет удалено у пользователя. При удалении чата или канала происходит автоматическая отписка от всех обсуждений в нём, эти обсуждения пропадают у пользователя. Если пользователь снова вернётся в чат или канал, присоединяться обратно во все обсуждения он должен самостоятельно.

Любое сообщение из любого чата или обсуждения можно переслать в обсуждение, если Вы подписаны на него.

По умолчанию уведомления для обсуждений в каналах выключены, а для обсуждений в групповых чатах — наследуют настройки уведомлений группового чата.

Где найти и как подписаться на обсуждения

Вы становитесь участником обсуждения (подписываетесь на него) автоматически в следующих случаях:
  1. Вы — участник исходного группового чата (не канала), в котором включены уведомления;
  2. Вас упомянули в стартовом сообщении или в обсуждении через символ @;
  3. Вы сами написали в обсуждении;
  4. Вы сами присоединились в карточке обсуждения. В каналах стать участником обсуждения можно только вручную.

На обсуждение не может подписаться пользователь, не являющийся участником родительского чата.

Вы прекратите быть участником обсуждения (отпишетесь) в следующих случаях:
  1. Вы удалили исходный чат/канал или само обсуждение в списке чатов;
  2. Вас исключили из чата или канала;
  3. Вы покинули обсуждение через карточку обсуждения.

Все обсуждения, в которых вы участвуете и подписаны, находятся в разделе Обсуждения и в списке чатов (есть настройка по отображению обсуждений в общем списке чатов).

Обсуждения, в которых Вы не участвуете, можно опознать по счётчику комментариев под сообщением.

Чтобы подписаться на обсуждение, к нему нужно присоединиться: отыщите в чате или канале сообщение, в котором идёт обсуждение, откройте карточку обсуждения и нажмите Подписаться на обсуждение.

Как самому начать обсуждение?

Перед тем, как начать обсуждение, сначала необходимо убедиться, что обсуждения включены в групповом чате или канале, иначе эта функция будет недоступна. Включить обсуждения в групповом чате или канале может создатель или администратор: ему нужно открыть карточку чата, выбрать пункт Настройки и включить тумблер Обсуждения.

Чтобы начать обсуждение любого сообщения в чате или в канале, выберите пункт Начать обсуждение (Комментировать) в его контекстном меню (долгое нажатие на сообщении на мобильных). Оставьте свой комментарий, и у сообщения в чате или канале появится счётчик комментариев.

Нельзя начинать обсуждения для системных сообщений, сообщений бота, сообщений, состоящих из смайлика (эмодзи) и стикера, и сообщений, отправленных в режиме конфиденциальности.

После добавления вашего комментария Вы автоматически присоединитесь к обсуждению, и оно появится у вас в списке чатов и в разделе Обсуждения.

Если обсуждение находится в канале, присоединяться к обсуждению надо самостоятельно: открыть карточку обсуждения и выбрать пункт Подписаться на обсуждение, и обсуждение из канала появится у вас в списке чатов и в разделе Обсуждения.

Свойства обсуждения

Заголовок Обсуждение в… наверху, если открыть обсуждение, открывает карточку (свойства) обсуждения.

В карточке обсуждения есть следующее:
  1. Общие файлы — раздел для просмотра всех вложений, отправленных в комментариях обсуждения;
  2. Поиск по обсуждению — тестовый поиск внутри обсуждения;
  3. Загрузить все сообщения — принудительная загрузка всех комментариев обсуждения на устройство;
  4. Очистить историю обсуждения — очистка комментариев обсуждения;
  5. Присоединиться к обсуждению/Покинуть обсуждение — подписка или отписка на обсуждение; ниже находится список участников обсуждения (если обсуждение начато в групповом чате, а не в канале).

В контекстном меню обсуждения (щелчок правой кнопкой мыши в веб- и десктоп-приложении или смахивание или долгое нажатие на мобильных) в списке чатов или в разделе Обсуждения можно пометить обсуждение непрочитанным, переключить уведомления о новых сообщениях, закрепить обсуждение в списке, покинуть обсуждение или очистить историю сообщений обсуждения.

Работа с сообщениями

Форматирование текста сообщений

Форматировать текст сообщений в eXpress можно в контекстном меню выделенного текста, нажатием сочетаний клавиш (Ctrl+B, Ctrl+U и т.д.), а также с использованием специальных символов разметки (язык Markdown («маркдаун») поддерживается частично).

Максимальное количество символов в одном сообщении: 4096. Если символов будет больше, сообщение разобьётся на несколько с соблюдением форматирования.

Стиль
Обозначение
Полужирный текст
**полужирный текст**
Курсивный текст
*курсивный текст*
Полужирный курсив
***полужирный курсив***
Маркированный список
* 1-й элемент * 2-й элемент * 3-й элемент
Зачёркнутый текст
~~зачёркнутый~~
Блок кода без указания языка
``` код ```
Блок кода с указанием языка1
```язык код ```
Код в строке
`код в строке`
Ссылки
[текст ссылки](www.example.com)
Теги
#тег
Упоминания («меншены»)2
@участник чата
@All (уведомление всех участников)
@@не участник чата

1 Языки, для которых доступно выделение синтаксиса: sh, zsh, bash, basic, clojure, cmake.in, cmake, coffeescript, coffee, cson, iced, patch, cpp, cs, css, diff, jinja, django, docker, dockerfile, delphi, elixir, elm, erlang, go, haskell, ini, java, js, jsx, javascript, json, kotlin, less, lisp, llvm, lua, makefile, matlab, nginx, ocaml, perl, php, ps, powershell, prolog, python, r, rb, gemspec, podspec, thor, irb, ruby, rs, rust, scala, scss, shell, sql, swift, tex, twig, ts, typescript, vbs, vbscript, xml, yml, yaml.

2 Подробнее о работе упоминаний и их ограничения:
  • Упоминание через один символ @ уведомит пользователя о том, что его упомянули, даже если у него отключены уведомления в чате или обсуждении. Найти и упомянуть через @ можно только тех, кто присутствует в чате. Работает как непосредственное обращение к боту или пользователю в чате.
  • Упоминание через два символа @@ используются для отправки контакта кому-либо или упоминания пользователя в контексте беседы. Упоминаемому уведомление в таком случае не придет, но тот, кому отправлено такое упоминание, сможет открыть карточку упомянутого контакта и написать ему. Найти и упомянуть через @@ можно даже тех, кого в чате нет. Здесь поиск контакта аналогичен поиску в разделе Контакты, благодаря чему можно упомянуть пользователя с трастового сервера или пользователя, с которым есть общий чат. Администратор чата или канала через контекстное меню такого упоминания сможет добавить упомянутого в список участников.
  • В каналах нет одиночных упоминаний (в том числе @All). В канале через @ можно упоминать только бота, и только если бот назначен администратором канала. Двойные упоминания в каналах есть.
  • Начиная с версии приложения 3.6 не работает @All в чатах и обсуждениях, в которых более 256 участников. Если участников меньше 256, упоминание всех сработает.

Редактирование, удаление и пересылка сообщений

Чтобы изменить/удалить (у себя или у всех участников) или переслать сообщение:
  • в веб- или десктоп-версии приложения щёлкните сообщение правой кнопкой мыши, чтобы открыть контекстное меню с соответствующими командами;
  • в мобильных версиях приложения нажмите и удерживайте палец на сообщении, чтобы открыть контекстное меню.
Чтобы удалить или переслать несколько сообщений, для одного из них выберите соответствующую команду в контекстном меню и далее нажимайте на другие сообщения, чтобы выбрать их — вокруг них появится рамка.

Чтобы переслать сообщение в обсуждение, в поле поиска чата для пересылки введите название или часть названия родительского чата или канала, где было создано обсуждение.

В десктоп- или веб-приложении по желанию сверните список чатов, нажав на стрелку в левом нижнем углу (над номером версии) или разверните окно приложения на весь экран или просто чуть пошире – в окне чата станут видны флажки выбора сообщений. Отметьте с их помощью сообщения, которые хотите переслать.

Администратор группового чата, как и все другие участники, может редактировать и удалять для всех только свои сообщения. Администратор канала может удалять свои сообщения и сообщения других администраторов, за исключением пересланных в канал сообщений от других пользователей — их редактировать нельзя ни при пересылке, ни после отправки.

Внимание! Отредактировать и удалить своё сообщение в групповом чате для всех можно в течение 48 часов с момента отправки, в канале — без ограничений. Удалять и редактировать сообщения в обсуждениях пока что может только их отправитель в течение 48 часов — в новых версиях приложения эти возможности будут расширены.

Закрепление сообщения

Любой участник чата или администратор канала может закрепить одно сообщение наверху. Открепить сообщение или закрепить другое сообщение может участник-администратор или тот участник, который ранее закреплял сообщение.

Ошибки чатов, каналов и обсуждений

Ошибки «Невозможно расшифровать» или «Подпись не подтверждена» на сообщениях

Чаще всего эти ошибки вызываются временными сетевыми проблемами и устраняются повторным входом в приложение в приложении на проблемном устройстве.

Если ошибка возникла у одного из участников группового чата, для начала проверьте, как другие участники чата видят это проблемное сообщение:
  • Если у других участников сообщение с ошибкой, попросите отправителя завершить сессию в настройках и войти в приложение заново на том устройстве, с которого тот отправил сообщение;
  • Если остальные участники видят сообщение корректно, и только у одного из них сообщение с ошибкой, попросите этого участника завершить сессию в настройках и войти в приложение заново.
Если ошибка возникла у одного из участников персонального чата, напишите что-нибудь в групповой чат, где есть этот пользователь, и:
  • Если участники чата видят ошибку в вашем сообщении в групповом чате, вам нужно завершить сессию в настройках и войти в приложение заново;
  • Если ошибка есть только у одного из получателей в чате, завершить сессию в настройках и войти в приложение заново нужно этому получателю.
Если действия выше не помогли, и ошибка повторяется у Вас на всех устройствах, значит, возникла проблема с вашими cts-ключами шифрования. Обратитесь за помощью в поддержку eXpress.
Администратору: проверка ключей шифрования Если пользователь с любого устройства отправляет сообщение, и на всех устройствах появляется ошибка шифрования, возможно, перепутаны версии ключей cts пользователя. Такое могло произойти, если пользователь CTS входил на сервер через устаревшую версию приложения, имея несколько ключей cts. Исправление делается обращением в поддержку eXpress:
  • Смотрим ключи cts пользователя на корпоративном сервере в панели администратора: раздел Пользователи > ищем профиль > Ключи. Искать ключи шифрования можно в браузере по нажатию клавиш Ctrl (Cmd) + F и ключевому слову cts. Сохраняем ключи cts вместе со всей сопроводительной информацией о них в текстовый файл.
  • Передаём файл с ключами в поддержку eXpress. Она сверит CTS-ключи на CTS с ключами на RTS и в случае расхождений по версиям или дате ключей вышлет рекомендации, какие версии нужно будет поменять местами. CTS-ключами с правильными версиями считаются ключи на RTS или ETS. Начиная с версии контейнера messaging 3.9.1 правильными считаются ключи на CTS.

  • Если версии ключей перепутаны
    Например, последний ключ на CTS является предпоследним на RTS/ETS, а и там, и там последний по версии ключ должен совпадать.
    Пример исправления перепутавшихся версий ключей cts на корпоративном сервере через консоль:
  • Через консоль подключаемся к базе kdc_prod. Пароль для postgres записан в файле settings, он потребуется при входе в контейнер.
    docker exec -ti cts_postgres_1 psql -U postgres \c kdc_prod
  • Далее в базе kdc_prod нужно выполнить последовательно команды:
    UPDATE public_keys SET version = 0 WHERE id = '1-я_версия_ключа_cts_с_RTS'; UPDATE public_keys SET version = 2, inserted_at = current_timestamp, updated_at = current_timestamp WHERE id = '2-я_версия_ключа_cts_с_RTS'; UPDATE public_keys SET version = 1 WHERE id = '1-я_версия_ключа_cts_с_RTS';
  • Заходим в консоль messaging:
    dpl --dc exec messaging ./bin/messaging remote_console или:
    docker exec -it {messaging_container} ./bin/messaging remote_console
  • Копируем код, приведённый ниже, и нажимаем Enter:
    defmodule Messaging.Troubleshooting do defmodule Chats do alias CcsSdk.API.Messaging.V1, as: MessagingV1 alias Messaging.Chats.{ChatsContext, GroupChat} alias Messaging.Chats.RoutingContext alias Messaging.Chats.GroupChat.Changeset.ChatServersChangeset alias Messaging.Chats.GroupChat.Commands.ChatSyncPullCommand alias Messaging.Chats.GroupChat.Queries.GetChatsByHuidInMembersQuery alias Messaging.Events.Kafka.EventHandler.Decoders.ChatParamsDecoder alias Messaging.Events.{SystemEventsContext, ChatEventsContext} alias Messaging.Event alias Messaging.Repo require Logger def resync_chat_servers(chat_id) do {:ok, base} = ChatsContext.get_chat_base_from_cache(chat_id) RoutingContext.sync(base.members) {:ok, chat} = ChatsContext.get_chat(chat_id, preload: true) ChatsContext.activate_chat(chat) chat |> ChatServersChangeset.apply() |> Repo.update!(force: true) ChatsContext.clear_chat_cache(chat.id, "chat_sync") end def update_chat_servers(chat_id) do with {:ok, chat} <- ChatsContext.get_chat(chat_id, preload: true) do chat |> ChatServersChangeset.apply() |> Repo.update!(force: true) ChatsContext.clear_chat_cache(chat_id, "chat_sync") end end def refresh_chat(chat_id) do with {:ok, chat_data} <- MessagingV1.sync_data(chat_id), decoded_data = ChatParamsDecoder.decode(chat_data), {:ok, result} <- ChatsContext.sync_chat(decoded_data, []) do produce_chat_updated(result.group_chat) end end def sync_chat(chat_id, server_id) do ChatSyncPullCommand.execute(chat_id, server_id) end def repair(chat_id) do with :ok <- refetch_routing_info_for_chat(chat_id) do produce_chat_updated(chat_id) end end def repair_user(user_huid) do with {:ok, _} <- RoutingContext.sync([user_huid]), {:ok, chats} <- GetChatsByHuidInMembersQuery.execute(user_huid) do Enum.each(chats, fn(chat) -> clear_cache(chat.id) produce_chat_updated(chat) produce_routing_changed(chat, user_huid) end) end end def repair_user_with_chats(user_huid) do with {:ok, _} <- RoutingContext.sync([user_huid]), {:ok, chats} <- GetChatsByHuidInMembersQuery.execute(user_huid) do Enum.each(chats, fn(chat) -> refresh_chat(chat.id) clear_cache(chat.id) end) end end def produce_chat_updated(%GroupChat{} = chat) do with {:ok, chat_updated} <- SystemEventsContext.chat_updated(chat) do Event.emit(chat_updated, chat: chat) {:ok, [id: chat.id]} else error -> Logger.error("Troubleshooting.Chats produce chat updated error: #{chat.id} #{error}") {:error, [id: chat.id, reason: error]} end end def produce_chat_updated(chat_id) do with {:ok, chat} <- ChatsContext.get_chat(chat_id, preload: true) do produce_chat_updated(chat) end end def produce_routing_changed(%GroupChat{} = chat, user_huid) do backward = %GroupChat{chat | chat_member_routing_infos: []} routing_info = Enum.find(chat.chat_member_routing_infos, &(&1.user_huid == user_huid)) forward = %GroupChat{chat | chat_member_routing_infos: [routing_info]} with {:ok, routing_changed} <- ChatEventsContext.routing_changed(backward, forward) do Event.emit(routing_changed, chat: chat) {:ok, [id: chat.id]} else error -> Logger.error("Troubleshooting.Chats produce chat routing changed error: #{chat.id} #{error}") {:error, [id: chat.id, reason: error]} end end def activate_chat(chat_id) do with {:ok, chat} <- ChatsContext.get_chat(chat_id), {:ok, updated_chat} <- ChatsContext.activate_chat(chat) do clear_cache(chat_id) produce_chat_updated(updated_chat) end end def refetch_routing_info_for_chat(chat_id) do {:ok, chat} = ChatsContext.get_chat(chat_id) with {:ok, _} <- RoutingContext.sync(chat.members) do ChatsContext.clear_chat_cache(chat_id, "chat_sync") end end def refetch_routing_info(user_huid) do with {:ok, _} <- RoutingContext.sync([user_huid]), {:ok, chats} <- GetChatsByHuidInMembersQuery.execute(user_huid) do Enum.each(chats, &(ChatsContext.clear_chat_cache(&1.id, "chat_sync"))) end end def clear_cache(chat_id) do ChatsContext.clear_chat_cache(chat_id, "chat_sync") end def validate_chat(chat_id) do {:ok, local_chat} = ChatsContext.get_chat(chat_id, preload: true) {:ok, local_chat_from_cache} = ChatsContext.get_chat_from_cache(chat_id) check1 = validate_chat_member_routing_info( {:local, local_chat.chat_member_routing_infos}, {:local_cache, local_chat_from_cache.chat_member_routing_infos} ) check2 = validate_keys( {:local, ChatsContext.keys(local_chat)}, {:local_cache, local_chat_from_cache.keys} ) [check1, check2] end defp validate_chat_member_routing_info({tag1, cmr_info1}, {tag2, cmr_info2}) do i1 = cmr_info1 |> Enum.map(&map_routing_info/1) |> Enum.sort_by(&(&1.user_huid)) i2 = cmr_info2 |> Enum.map(&map_routing_info/1) |> Enum.sort_by(&(&1.user_huid)) validate_diff(:routing_info, {tag1, i1}, {tag2, i2}) end defp map_routing_info(routing_info) do routing_info |> Map.from_struct() |> Map.take([ :active, :cts_id, :cts_key_id, :ets_id, :rts_id, :rts_key_id, :user_huid, :user_kind ]) end defp validate_keys({tag1, keys1}, {tag2, keys2}) do validate_diff(:keys, {tag1, keys1}, {tag2, keys2}) end defp validate_diff(set_name, {tag1, set1}, {tag2, set2}) do missing1 = case set1 -- set2 do [] -> [] diff -> [{tag2, diff}] end missing2 = case set2 -- set1 do [] -> [] diff -> [{tag1, diff}] end case missing1 ++ missing2 do [] -> {set_name, :ok} missing -> {set_name, {:missing, missing}} end end end end
  • В конце тоже в консоли messaging выполняем команду:
    Messaging.Troubleshooting.Chats.repair_user("HUID_пользователя_с_проблемой") Эту последнюю команду можно вызывать несколько раз для разных HUID пользователей, если необходимо.

  • Если версии ключей не перепутаны, но дата/время последнего ключа отстаёт от предпоследнего
    Если ключ по версии актуален, но по дате отстаёт, то в чаты будет подтягиваться ключ с предыдущей версией даже после репейра из-за того, что его дата позже даты ключа последней версии.
    Исправление даты:
  • Через консоль подключаемся к базе kdc_prod. Пароль для postgres записан в файле settings, он потребуется при входе в контейнер. docker exec -ti cts_postgres_1 psql -U postgres \c kdc_prod
  • В базе kdc_prod выполняем команду: UPDATE public_keys SET inserted_at = now(), updated_at = now() WHERE id = 'ID_верного_ключа_с_неверной_датой';
  • Далее заходим в консоль messaging: dpl --dc exec messaging ./bin/messaging remote_console или: docker exec -it {messaging_container} ./bin/messaging remote_console
  • Копируем код, приведённый ниже, и нажимаем Enter: defmodule Messaging.Troubleshooting do defmodule Chats do alias CcsSdk.API.Messaging.V1, as: MessagingV1 alias Messaging.Chats.{ChatsContext, GroupChat} alias Messaging.Chats.RoutingContext alias Messaging.Chats.GroupChat.Changeset.ChatServersChangeset alias Messaging.Chats.GroupChat.Commands.ChatSyncPullCommand alias Messaging.Chats.GroupChat.Queries.GetChatsByHuidInMembersQuery alias Messaging.Events.Kafka.EventHandler.Decoders.ChatParamsDecoder alias Messaging.Events.{SystemEventsContext, ChatEventsContext} alias Messaging.Event alias Messaging.Repo require Logger def resync_chat_servers(chat_id) do {:ok, base} = ChatsContext.get_chat_base_from_cache(chat_id) RoutingContext.sync(base.members) {:ok, chat} = ChatsContext.get_chat(chat_id, preload: true) ChatsContext.activate_chat(chat) chat |> ChatServersChangeset.apply() |> Repo.update!(force: true) ChatsContext.clear_chat_cache(chat.id, "chat_sync") end def update_chat_servers(chat_id) do with {:ok, chat} <- ChatsContext.get_chat(chat_id, preload: true) do chat |> ChatServersChangeset.apply() |> Repo.update!(force: true) ChatsContext.clear_chat_cache(chat_id, "chat_sync") end end def refresh_chat(chat_id) do with {:ok, chat_data} <- MessagingV1.sync_data(chat_id), decoded_data = ChatParamsDecoder.decode(chat_data), {:ok, result} <- ChatsContext.sync_chat(decoded_data, []) do produce_chat_updated(result.group_chat) end end def sync_chat(chat_id, server_id) do ChatSyncPullCommand.execute(chat_id, server_id) end def repair(chat_id) do with :ok <- refetch_routing_info_for_chat(chat_id) do produce_chat_updated(chat_id) end end def repair_user(user_huid) do with {:ok, _} <- RoutingContext.sync([user_huid]), {:ok, chats} <- GetChatsByHuidInMembersQuery.execute(user_huid) do Enum.each(chats, fn(chat) -> clear_cache(chat.id) produce_chat_updated(chat) produce_routing_changed(chat, user_huid) end) end end def repair_user_with_chats(user_huid) do with {:ok, _} <- RoutingContext.sync([user_huid]), {:ok, chats} <- GetChatsByHuidInMembersQuery.execute(user_huid) do Enum.each(chats, fn(chat) -> refresh_chat(chat.id) clear_cache(chat.id) end) end end def produce_chat_updated(%GroupChat{} = chat) do with {:ok, chat_updated} <- SystemEventsContext.chat_updated(chat) do Event.emit(chat_updated, chat: chat) {:ok, [id: chat.id]} else error -> Logger.error("Troubleshooting.Chats produce chat updated error: #{chat.id} #{error}") {:error, [id: chat.id, reason: error]} end end def produce_chat_updated(chat_id) do with {:ok, chat} <- ChatsContext.get_chat(chat_id, preload: true) do produce_chat_updated(chat) end end def produce_routing_changed(%GroupChat{} = chat, user_huid) do backward = %GroupChat{chat | chat_member_routing_infos: []} routing_info = Enum.find(chat.chat_member_routing_infos, &(&1.user_huid == user_huid)) forward = %GroupChat{chat | chat_member_routing_infos: [routing_info]} with {:ok, routing_changed} <- ChatEventsContext.routing_changed(backward, forward) do Event.emit(routing_changed, chat: chat) {:ok, [id: chat.id]} else error -> Logger.error("Troubleshooting.Chats produce chat routing changed error: #{chat.id} #{error}") {:error, [id: chat.id, reason: error]} end end def activate_chat(chat_id) do with {:ok, chat} <- ChatsContext.get_chat(chat_id), {:ok, updated_chat} <- ChatsContext.activate_chat(chat) do clear_cache(chat_id) produce_chat_updated(updated_chat) end end def refetch_routing_info_for_chat(chat_id) do {:ok, chat} = ChatsContext.get_chat(chat_id) with {:ok, _} <- RoutingContext.sync(chat.members) do ChatsContext.clear_chat_cache(chat_id, "chat_sync") end end def refetch_routing_info(user_huid) do with {:ok, _} <- RoutingContext.sync([user_huid]), {:ok, chats} <- GetChatsByHuidInMembersQuery.execute(user_huid) do Enum.each(chats, &(ChatsContext.clear_chat_cache(&1.id, "chat_sync"))) end end def clear_cache(chat_id) do ChatsContext.clear_chat_cache(chat_id, "chat_sync") end def validate_chat(chat_id) do {:ok, local_chat} = ChatsContext.get_chat(chat_id, preload: true) {:ok, local_chat_from_cache} = ChatsContext.get_chat_from_cache(chat_id) check1 = validate_chat_member_routing_info( {:local, local_chat.chat_member_routing_infos}, {:local_cache, local_chat_from_cache.chat_member_routing_infos} ) check2 = validate_keys( {:local, ChatsContext.keys(local_chat)}, {:local_cache, local_chat_from_cache.keys} ) [check1, check2] end defp validate_chat_member_routing_info({tag1, cmr_info1}, {tag2, cmr_info2}) do i1 = cmr_info1 |> Enum.map(&map_routing_info/1) |> Enum.sort_by(&(&1.user_huid)) i2 = cmr_info2 |> Enum.map(&map_routing_info/1) |> Enum.sort_by(&(&1.user_huid)) validate_diff(:routing_info, {tag1, i1}, {tag2, i2}) end defp map_routing_info(routing_info) do routing_info |> Map.from_struct() |> Map.take([ :active, :cts_id, :cts_key_id, :ets_id, :rts_id, :rts_key_id, :user_huid, :user_kind ]) end defp validate_keys({tag1, keys1}, {tag2, keys2}) do validate_diff(:keys, {tag1, keys1}, {tag2, keys2}) end defp validate_diff(set_name, {tag1, set1}, {tag2, set2}) do missing1 = case set1 -- set2 do [] -> [] diff -> [{tag2, diff}] end missing2 = case set2 -- set1 do [] -> [] diff -> [{tag1, diff}] end case missing1 ++ missing2 do [] -> {set_name, :ok} missing -> {set_name, {:missing, missing}} end end end end
  • В конце тоже в консоли messaging выполняем команду: Messaging.Troubleshooting.Chats.repair_user("HUID_пользователя_с_проблемой") Эту последнюю команду можно вызывать несколько раз для разных HUID пользователей, если необходимо.

Зависла синхронизация, сообщения не отправляются или не видны в одном чате

Могут отображаться сообщения об ошибках: «Дождитесь окончания синхронизации чата» или «Вы не можете отправлять сообщения в чат, поскольку больше не являетесь его участником».

Попробуйте сделать следующее:
  • Если чат групповой, попробуйте передобавить в чат любого пользователя или сделайте любого участника чата администратором. Это событие вызовет обновление чата, и проблема с зависанием чата может быть решена.
  • Если чат персональный, создайте групповой чат с теми же двумя участниками и проверьте, видите ли вы сообщения друг друга. Если сообщения видно, создайте персональный чат заново с двух сторон: нужно удалить чат у каждого участника и написать заново из контактов.
  • Сообщения могут не отображаться с трастовых серверов при наличии проблем с трастами — тогда проблема проявляется во всех чатах между пользователями конкретных серверов. Обратитесь к администратору корпоративного сервера через поддержку своей организации или поддержку eXpress.
Если действия выше не помогают, обратитесь за помощью в поддержку своей организации или в поддержку eXpress.
Администратору: помощь пользователю с одним проблемным чатом
Как узнать ID чата в веб-/десктоп-приложении:
1. Откройте Настройки > О программе > много раз нажмите на логотип, пока не откроется инженерное меню.
2. Включите тумблер Инфо для разработчика в меню сообщения. Закройте инженерное меню.
3. В нужном чате щёлкните правой кнопкой мыши любое сообщение и выберите Инфо для разработчика.
4. В панели справа посмотрите значение groupChatId — это и есть идентификатор чата.

Если чат пустой у всех участников чата, тогда не получится вызвать меню для сообщений в нём. Можно попробовать выполнить звонок в этом чате, скачать стейт со стороны любого участника чата и, ориентируясь на HUID участников, найти нужный groupChatId в этом стейте.

Сначала проверяем, не стал ли этот чат ошибочно неактивен на сервере. Неактивные чаты в списке чатов в панели администратора не отображаются.
Если чат в панели администратора найти не удалось, значит, выполняем последовательно команды в консоли сервиса messaging:
defmodule Messaging.Troubleshooting do defmodule Chats do alias CcsSdk.API.Messaging.V1, as: MessagingV1 alias Messaging.Chats.{ChatsContext, GroupChat} alias Messaging.Chats.RoutingContext alias Messaging.Chats.GroupChat.Changeset.ChatServersChangeset alias Messaging.Chats.GroupChat.Commands.ChatSyncPullCommand alias Messaging.Chats.GroupChat.Queries.GetChatsByHuidInMembersQuery alias Messaging.Events.Kafka.EventHandler.Decoders.ChatParamsDecoder alias Messaging.Events.{SystemEventsContext, ChatEventsContext} alias Messaging.Event alias Messaging.Repo require Logger def resync_chat_servers(chat_id) do {:ok, base} = ChatsContext.get_chat_base_from_cache(chat_id) RoutingContext.sync(base.members) {:ok, chat} = ChatsContext.get_chat(chat_id, preload: true) ChatsContext.activate_chat(chat) chat |> ChatServersChangeset.apply() |> Repo.update!(force: true) ChatsContext.clear_chat_cache(chat.id, "chat_sync") end def update_chat_servers(chat_id) do with {:ok, chat} <- ChatsContext.get_chat(chat_id, preload: true) do chat |> ChatServersChangeset.apply() |> Repo.update!(force: true) ChatsContext.clear_chat_cache(chat_id, "chat_sync") end end def refresh_chat(chat_id) do with {:ok, chat_data} <- MessagingV1.sync_data(chat_id), decoded_data = ChatParamsDecoder.decode(chat_data), {:ok, result} <- ChatsContext.sync_chat(decoded_data, []) do produce_chat_updated(result.group_chat) end end def sync_chat(chat_id, server_id) do ChatSyncPullCommand.execute(chat_id, server_id) end def repair(chat_id) do with :ok <- refetch_routing_info_for_chat(chat_id) do produce_chat_updated(chat_id) end end def repair_user(user_huid) do with {:ok, _} <- RoutingContext.sync([user_huid]), {:ok, chats} <- GetChatsByHuidInMembersQuery.execute(user_huid) do Enum.each(chats, fn(chat) -> clear_cache(chat.id) produce_chat_updated(chat) produce_routing_changed(chat, user_huid) end) end end def repair_user_with_chats(user_huid) do with {:ok, _} <- RoutingContext.sync([user_huid]), {:ok, chats} <- GetChatsByHuidInMembersQuery.execute(user_huid) do Enum.each(chats, fn(chat) -> refresh_chat(chat.id) clear_cache(chat.id) end) end end def produce_chat_updated(%GroupChat{} = chat) do with {:ok, chat_updated} <- SystemEventsContext.chat_updated(chat) do Event.emit(chat_updated, chat: chat) {:ok, [id: chat.id]} else error -> Logger.error("Troubleshooting.Chats produce chat updated error: #{chat.id} #{error}") {:error, [id: chat.id, reason: error]} end end def produce_chat_updated(chat_id) do with {:ok, chat} <- ChatsContext.get_chat(chat_id, preload: true) do produce_chat_updated(chat) end end def produce_routing_changed(%GroupChat{} = chat, user_huid) do backward = %GroupChat{chat | chat_member_routing_infos: []} routing_info = Enum.find(chat.chat_member_routing_infos, &(&1.user_huid == user_huid)) forward = %GroupChat{chat | chat_member_routing_infos: [routing_info]} with {:ok, routing_changed} <- ChatEventsContext.routing_changed(backward, forward) do Event.emit(routing_changed, chat: chat) {:ok, [id: chat.id]} else error -> Logger.error("Troubleshooting.Chats produce chat routing changed error: #{chat.id} #{error}") {:error, [id: chat.id, reason: error]} end end def activate_chat(chat_id) do with {:ok, chat} <- ChatsContext.get_chat(chat_id), {:ok, updated_chat} <- ChatsContext.activate_chat(chat) do clear_cache(chat_id) produce_chat_updated(updated_chat) end end def refetch_routing_info_for_chat(chat_id) do {:ok, chat} = ChatsContext.get_chat(chat_id) with {:ok, _} <- RoutingContext.sync(chat.members) do ChatsContext.clear_chat_cache(chat_id, "chat_sync") end end def refetch_routing_info(user_huid) do with {:ok, _} <- RoutingContext.sync([user_huid]), {:ok, chats} <- GetChatsByHuidInMembersQuery.execute(user_huid) do Enum.each(chats, &(ChatsContext.clear_chat_cache(&1.id, "chat_sync"))) end end def clear_cache(chat_id) do ChatsContext.clear_chat_cache(chat_id, "chat_sync") end def validate_chat(chat_id) do {:ok, local_chat} = ChatsContext.get_chat(chat_id, preload: true) {:ok, local_chat_from_cache} = ChatsContext.get_chat_from_cache(chat_id) check1 = validate_chat_member_routing_info( {:local, local_chat.chat_member_routing_infos}, {:local_cache, local_chat_from_cache.chat_member_routing_infos} ) check2 = validate_keys( {:local, ChatsContext.keys(local_chat)}, {:local_cache, local_chat_from_cache.keys} ) [check1, check2] end defp validate_chat_member_routing_info({tag1, cmr_info1}, {tag2, cmr_info2}) do i1 = cmr_info1 |> Enum.map(&map_routing_info/1) |> Enum.sort_by(&(&1.user_huid)) i2 = cmr_info2 |> Enum.map(&map_routing_info/1) |> Enum.sort_by(&(&1.user_huid)) validate_diff(:routing_info, {tag1, i1}, {tag2, i2}) end defp map_routing_info(routing_info) do routing_info |> Map.from_struct() |> Map.take([ :active, :cts_id, :cts_key_id, :ets_id, :rts_id, :rts_key_id, :user_huid, :user_kind ]) end defp validate_keys({tag1, keys1}, {tag2, keys2}) do validate_diff(:keys, {tag1, keys1}, {tag2, keys2}) end defp validate_diff(set_name, {tag1, set1}, {tag2, set2}) do missing1 = case set1 -- set2 do [] -> [] diff -> [{tag2, diff}] end missing2 = case set2 -- set1 do [] -> [] diff -> [{tag1, diff}] end case missing1 ++ missing2 do [] -> {set_name, :ok} missing -> {set_name, {:missing, missing}} end end end end Затем команду:
Messaging.Troubleshooting.Chats.resync_chat_servers("ID_чата") Эту команду можно вызывать несколько раз с разными ID чатов, если нужно.

Если решение выше не подошло, может пригодиться следующая информация:
  • HUID проблемного пользователя — ищем пользователя в разделе Пользователи в панели администратора корпоративного сервера или передаём поддержке eXpress номер телефона пользователя, она сообщит HUID. Через HUID можно будет найти пользователя на серверах и в чатах.
  • ID чата — ищем чат в разделе Чаты в панели администратора. С его помощью можно будет найти чат на том или ином сервере.
  • Список CTS-ключей пользователя с их версиями — кнопка Ключи в профиле пользователя в панели администратора. Для удобства можно воспользоваться поиском по странице (сочетание клавиш (Cmd) Ctrl + F, ключевое слово cts). Имея на руках ключи пользователя, можно будет сверить их с ключами в чатах. Как правило, если последняя версия ключа не совпадает с текущим ключом пользователя в чате, у пользователя будут проблемы с чатом.
  • Чат JSON — ищем чат в разделе Чаты в панели администратора, нажимаем Чат JSON. Если в чате участвуют разные серверы, нужно собрать JSON со всех серверов-участников — при необходимости через поддержку eXpress. Чат JSON — это сводная информация об участниках чата и об их ключах. Здесь у пользователя должен быть ключ последней версии.
  • Можно собрать информацию о проблемном сообщении — ищем чат в разделе Чаты панели администратора, нажимаем События таблица, там по времени ищем нужное сообщение. Здесь тоже будет информация о CTS-ключе пользователя в поле sender_key_id.
  • Собираем стейт или логи клиентского приложения, чтобы просмотреть, какой CTS-ключ пользователя прописался в клиентском приложении.
В идеальном состоянии ключ в профиле пользователя (Ключи), ключ в чате (Чат JSON), ключ на клиенте (стейт или логи мобильного приложения) и ключ в событии (События таблица > sender_key_id) должны совпадать. Если где-то ключи не совпадают, это вызовет проблемы с отображением сообщений.

Далее проверяем эти признаки:
  • В панели администратора ищем проблемный чат, в списке пользователей у пользователя видим неверное значение в столбце Conn Type (например, rts вместо cts);
  • В роутинге чата у других участников отображается неверная принадлежность этого пользователя к серверу (трафик от него отображается через RTS, а не через CTS). То есть пользователь корпоративного сервера (синий) в роутинге участников проблемного чата может ошибочно отображаться как зарегистрированный на региональном транспортном сервере (зелёный).
  • Открываем Chat JSON чата. Параллельно открываем ключи пользователя, ищем ключ cts самой последней версии и сверяем с ключом пользователя в Chat JSON. В чате будет неверный ключ cts пользователя, скорее всего, предыдущей версии.
Если присутствует хотя бы один из вышеперечисленных признаков:
  1. 1. Открываем консоль сервиса messaging:
    cd /opt/express dpl --dc exec messaging ./bin/messaging remote_console если deployka отдельно, то:
    • До бэкенда 3.13: docker exec -ti cts_messaging_1 bin/messaging remote_console
    • Бэкенд 3.13 и выше: docker exec -ti cts-messaging-1 bin/messaging remote_console
  2. 2. Обновляем роутинг у проблемного пользователя и затем очищаем кэш его проблемного чата следующей командой:
    Messaging.Chats.Routing.ChatMemberRoutingInfoFetcher.get_and_save!(["HUID ПОЛЬЗОВАТЕЛЯ"]) Messaging.Chats.GroupChatCache.clear("ID ЧАТА")

Зависла синхронизация, сообщения не отправляются или не видны во всех чатах

Во всех чатах могут отображаться сообщения об ошибках: «Дождитесь окончания синхронизации чата» или «Вы не можете отправлять сообщения в чат, поскольку больше не являетесь его участником».
Также в этом случае пользователь корпоративного сервера (синий) в роутинге участников всех его чатов может ошибочно отображаться как зарегистрированный на региональном транспортном сервере (зелёный). Ещё один признак: статус последнего «онлайн» проблемного пользователя может быть далеко в прошлом, хотя тот был недавно активен.

Здесь необходимо исправлять кэш чатов на корпоративном сервере, на котором возникла проблема. Это делается через поддержку организации, если сервер находится у Вашей организации, или через поддержку eXpress.
Администратору: исправление кэша чатов Дополнительные признаки этой проблемы:
  • В панели администратора ищем любой чат с пользователем, в списке пользователей у пользователя видим неверное значение в столбце Conn Type (например, rts вместо cts);
  • В роутинге чата у других участников отображается неверная принадлежность этого пользователя к серверу (трафик от него отображается через RTS, а не через CTS). То есть пользователь корпоративного сервера (синий) в роутинге участников проблемного чата может ошибочно отображаться как зарегистрированный на региональном транспортном сервере (зелёный).
  • Открываем Chat JSON чата. Параллельно открываем ключи пользователя, ищем ключ cts самой последней версии и сверяем с ключом пользователя в Chat JSON. В чате будет неверный ключ cts пользователя, скорее всего, предыдущей версии.
  • Сообщения от проблемного пользователя не видны другим участникам как в групповом, так и в персональном чатах.
  1. 1. Открываем консоль сервиса messaging:
    cd /opt/express dpl --dc exec messaging ./bin/messaging remote_console если deployka отдельно, то:
    • До бэкенда 3.13: docker exec -ti cts_messaging_1 bin/messaging remote_console
    • Бэкенд 3.13 и выше: docker exec -ti cts-messaging-1 bin/messaging remote_console
  2. 2. Копируем код, приведённый ниже, нажимаем клавишу Enter:
    defmodule Messaging.Troubleshooting do defmodule Chats do alias CcsSdk.API.Messaging.V1, as: MessagingV1 alias Messaging.Chats.{ChatsContext, GroupChat} alias Messaging.Chats.RoutingContext alias Messaging.Chats.GroupChat.Changeset.ChatServersChangeset alias Messaging.Chats.GroupChat.Commands.ChatSyncPullCommand alias Messaging.Chats.GroupChat.Queries.GetChatsByHuidInMembersQuery alias Messaging.Events.Kafka.EventHandler.Decoders.ChatParamsDecoder alias Messaging.Events.{SystemEventsContext, ChatEventsContext} alias Messaging.Event alias Messaging.Repo require Logger def resync_chat_servers(chat_id) do {:ok, base} = ChatsContext.get_chat_base_from_cache(chat_id) RoutingContext.sync(base.members) {:ok, chat} = ChatsContext.get_chat(chat_id, preload: true) ChatsContext.activate_chat(chat) chat |> ChatServersChangeset.apply() |> Repo.update!(force: true) ChatsContext.clear_chat_cache(chat.id, "chat_sync") end def update_chat_servers(chat_id) do with {:ok, chat} <- ChatsContext.get_chat(chat_id, preload: true) do chat |> ChatServersChangeset.apply() |> Repo.update!(force: true) ChatsContext.clear_chat_cache(chat_id, "chat_sync") end end def refresh_chat(chat_id) do with {:ok, chat_data} <- MessagingV1.sync_data(chat_id), decoded_data = ChatParamsDecoder.decode(chat_data), {:ok, result} <- ChatsContext.sync_chat(decoded_data, []) do produce_chat_updated(result.group_chat) end end def sync_chat(chat_id, server_id) do ChatSyncPullCommand.execute(chat_id, server_id) end def repair(chat_id) do with :ok <- refetch_routing_info_for_chat(chat_id) do produce_chat_updated(chat_id) end end def repair_user(user_huid) do with {:ok, _} <- RoutingContext.sync([user_huid]), {:ok, chats} <- GetChatsByHuidInMembersQuery.execute(user_huid) do Enum.each(chats, fn(chat) -> clear_cache(chat.id) produce_chat_updated(chat) produce_routing_changed(chat, user_huid) end) end end def repair_user_with_chats(user_huid) do with {:ok, _} <- RoutingContext.sync([user_huid]), {:ok, chats} <- GetChatsByHuidInMembersQuery.execute(user_huid) do Enum.each(chats, fn(chat) -> refresh_chat(chat.id) clear_cache(chat.id) end) end end def produce_chat_updated(%GroupChat{} = chat) do with {:ok, chat_updated} <- SystemEventsContext.chat_updated(chat) do Event.emit(chat_updated, chat: chat) {:ok, [id: chat.id]} else error -> Logger.error("Troubleshooting.Chats produce chat updated error: #{chat.id} #{error}") {:error, [id: chat.id, reason: error]} end end def produce_chat_updated(chat_id) do with {:ok, chat} <- ChatsContext.get_chat(chat_id, preload: true) do produce_chat_updated(chat) end end def produce_routing_changed(%GroupChat{} = chat, user_huid) do backward = %GroupChat{chat | chat_member_routing_infos: []} routing_info = Enum.find(chat.chat_member_routing_infos, &(&1.user_huid == user_huid)) forward = %GroupChat{chat | chat_member_routing_infos: [routing_info]} with {:ok, routing_changed} <- ChatEventsContext.routing_changed(backward, forward) do Event.emit(routing_changed, chat: chat) {:ok, [id: chat.id]} else error -> Logger.error("Troubleshooting.Chats produce chat routing changed error: #{chat.id} #{error}") {:error, [id: chat.id, reason: error]} end end def activate_chat(chat_id) do with {:ok, chat} <- ChatsContext.get_chat(chat_id), {:ok, updated_chat} <- ChatsContext.activate_chat(chat) do clear_cache(chat_id) produce_chat_updated(updated_chat) end end def refetch_routing_info_for_chat(chat_id) do {:ok, chat} = ChatsContext.get_chat(chat_id) with {:ok, _} <- RoutingContext.sync(chat.members) do ChatsContext.clear_chat_cache(chat_id, "chat_sync") end end def refetch_routing_info(user_huid) do with {:ok, _} <- RoutingContext.sync([user_huid]), {:ok, chats} <- GetChatsByHuidInMembersQuery.execute(user_huid) do Enum.each(chats, &(ChatsContext.clear_chat_cache(&1.id, "chat_sync"))) end end def clear_cache(chat_id) do ChatsContext.clear_chat_cache(chat_id, "chat_sync") end def validate_chat(chat_id) do {:ok, local_chat} = ChatsContext.get_chat(chat_id, preload: true) {:ok, local_chat_from_cache} = ChatsContext.get_chat_from_cache(chat_id) check1 = validate_chat_member_routing_info( {:local, local_chat.chat_member_routing_infos}, {:local_cache, local_chat_from_cache.chat_member_routing_infos} ) check2 = validate_keys( {:local, ChatsContext.keys(local_chat)}, {:local_cache, local_chat_from_cache.keys} ) [check1, check2] end defp validate_chat_member_routing_info({tag1, cmr_info1}, {tag2, cmr_info2}) do i1 = cmr_info1 |> Enum.map(&map_routing_info/1) |> Enum.sort_by(&(&1.user_huid)) i2 = cmr_info2 |> Enum.map(&map_routing_info/1) |> Enum.sort_by(&(&1.user_huid)) validate_diff(:routing_info, {tag1, i1}, {tag2, i2}) end defp map_routing_info(routing_info) do routing_info |> Map.from_struct() |> Map.take([ :active, :cts_id, :cts_key_id, :ets_id, :rts_id, :rts_key_id, :user_huid, :user_kind ]) end defp validate_keys({tag1, keys1}, {tag2, keys2}) do validate_diff(:keys, {tag1, keys1}, {tag2, keys2}) end defp validate_diff(set_name, {tag1, set1}, {tag2, set2}) do missing1 = case set1 -- set2 do [] -> [] diff -> [{tag2, diff}] end missing2 = case set2 -- set1 do [] -> [] diff -> [{tag1, diff}] end case missing1 ++ missing2 do [] -> {set_name, :ok} missing -> {set_name, {:missing, missing}} end end end end
  3. 3. Затем там же в консоли messaging выполняем команду:
    Messaging.Troubleshooting.Chats.repair_user("HUID_пользователя_с_проблемой")

Нет истории переписки в одном или нескольких конкретных чатах на всех устройствах

Если нет истории переписки в одном или нескольких определённых групповых чатах, из таких чатов могут приходить уведомления и это проявляется на всех устройствах, причём у других участников чаты отображаются нормально, обратитесь в поддержку своей организации для исправления ваших чатов.
Администратору: исправление эпохи в чате В версии бэкенда 3.10 была изменена структура хранения чатов: теперь их история делится на т.н. эпохи. Данная проблема исправляется очисткой значения end_at у эпохи.
  • Войдите в базу messaging:
    • Если база данных реализована в контейнере на сервере CTS(ETS): cd /opt/express && DPL_PULL_POLICY=never dpl --dc exec postgres psql -U postgres -d messaging_prod
    • Если база данных внешняя (не в кластере): su postgres psql -d messaging_prod
    • Если база данных внешняя, в кластере(на базе patroni): psql -h ip_master -p 5432 -U express -d messaging_prod
    • Пароль можно взять на сервере CTS из файла /opt/experss/settings.yaml.
  • В базе messaging_prod выполните такой запрос — поиск end_at у конкретной эпохи в конкретном чате: SELECT id, start_at, end_at FROM chat_member_epochs WHERE user_huid = 'HUID_пользователя' AND group_chat_id = 'ID_проблемного_чата'; Получаем такой вывод: id | start_at | end_at --------+----------------------------+---------------------------- 123456 | 2017-08-30 14:10:15.041548 | 2017-08-30 14:48:59.926536 (1 row) Если нашлась эпоха, у которой в end_at есть дата, то смотрим id этой записи (число в столбце id). Если записей несколько, то смотрим последнюю по времени. Если нашлась эпоха, у которой пустое значение end_at, значит, дело в другом — обратитесь в поддержку eXpress.
  • Обновите запись об эпохе, у которой есть значение end_at, сбросом этого значения: UPDATE chat_member_epochs SET end_at = NULL WHERE id = 123456;
  • Чтобы убедиться, что значение end_at исчезло, выполняем запрос: SELECT id, start_at, end_at FROM chat_member_epochs WHERE user_huid = 'HUID_пользователя' AND group_chat_id = 'ID_проблемного_чата'; В выводе видим, что значение исчезло: id | start_at | end_at --------+----------------------------+-------- 123456 | 2017-08-30 14:10:15.041548 | (1 row)

Нет истории переписки в чате группового звонка или конференции (комнате) на всех устройствах

Если нет истории переписки в чате звонка или конференции (комнате) на всех устройствах, причём у других участников сообщения отображаются нормально, обратитесь в поддержку своей организации для исправления этого.
Администратору: исправление эпохи в комнате группового звонка или конференции В версии бэкенда 3.10 была изменена структура хранения чатов, в том числе и комнат: теперь их история делится на т.н. эпохи. Данная проблема исправляется созданием новой эпохи.
  • Войдите в базу messaging:
    • Если база данных реализована в контейнере на сервере CTS(ETS): cd /opt/express && DPL_PULL_POLICY=never dpl --dc exec postgres psql -U postgres -d messaging_prod
    • Если база данных внешняя (не в кластере): su postgres psql -d messaging_prod
    • Если база данных внешняя, в кластере(на базе patroni): psql -h ip_master -p 5432 -U express -d messaging_prod
    • Пароль можно взять на сервере CTS из файла /opt/experss/settings.yaml.
  • В базе messaging_prod выполните такой запрос, чтобы проверить эпоху: SELECT id, start_at, end_at FROM chat_member_epochs WHERE user_huid = 'huid-проблемного-пользователя' AND group_chat_id = 'id-комнаты'; Здесь id-комнаты — это ID чата группового звонка или конференции: в панели администратора находим чат группового звонка или конференции → смотрим ID; либо смотрим ID в логах звонка/конференции с клиента. Получаем такой вывод: id | start_at | end_at --------+----------------------------+---------------------------- (0 rows) Это означает, что эпохи нет.
  • Создаём эпоху: INSERT INTO chat_member_epochs (user_huid, group_chat_id, start_at, end_at, inserted_at, updated_at) VALUES ('huid-проблемного-пользователя', 'id-комнаты', 'время-создания-комнаты', null, now(), now()); Здесь id-комнаты — это ID чата группового звонка или конференции: в панели администратора находим чат группового звонка или конференции → смотрим ID; либо смотрим ID в логах звонка/конференции с клиента. время-создания-комнаты — можно взять из значения inserted_at в Чат JSON, который можно получить так: в панели администратора находим чат группового звонка или конференции → Чат JSON.

В чате, созданном ботом, отправляемые сообщения зависают с ошибкой

Дело может быть в том, что рассинхронизировались ключи на сервере и в приложениях-клиентах (ключ DLPS). Обратитесь в поддержку своей организации или в поддержку eXpress, чтобы исправить синхронизацию ключа DLPS.
Администратору: исправление синхронизации ключа DLPS Перезапуск контейнера messaging сбросит кэш, и ключи обновятся.
  1. 1. В панели администратора корпоративного сервера, на котором находится бот, берём логи контейнера messaging.
  2. 2. Воспроизводим проблему в клиентском приложении. Если видим в логах контейнера ошибку keys_error, поможет перезапуск контейнера messaging в консоли командой:
    cd /opt/express && dpl --dc restart messaging

Сообщения отображаются недоставленными, хотя получателю на iOS они приходят

У отправителя сообщения отображаются как недоставленные (с одной галочкой), хотя на самом деле получатель с iOS прочитал их.

Получателю необходимо на устройстве iOS открыть Настройки > Уведомления > найти приложение и включить хотя бы один из вариантов отображения уведомлений: Заблокированный экран, Центр уведомлений, Баннеры. В противном случае сообщения, отправленные ему, будут отображаться у отправителя недоставленными.

В сообщениях неправильное время, статус «онлайн» отображается неверно

На устройстве нужно выключить ручную установку часового пояса и поставить автоматическую синхронизацию времени с сотовой сетью или с NTP-сервером.

Пустой список чатов на iOS после перезапуска приложения

Если после обновления приложения на iOS у вас несколько раз в день после запуска отображается пустой список чатов, затем чаты медленно, но подгружаются, скорее всего, возникла проблема с хранилищем данных приложения на устройстве. Переустановите приложение, чтобы исправить хранилище.
Опытным пользователям: сбор логов для диагностики Соберите общие логи в мобильном приложении на iOS, предварительно их очистив. В логах находим ошибку вида:
Type: REALM ERROR Message: can't instantiate migrated encrypted Realm, will create in-memory Realm instead Payload: exception: Migration is required due to the following errors:- Property 'Attachment.deepLinkData' has been added.- Property 'Chat.positionEventInsertedAt' has been added.- Property 'Message.buttonsAutoAdjust' has been added.- Property 'Message.sendStateRaw' has been added.- Property 'Message.needResend' has been removed.
Проблема заключается в базе данных приложения (Realm), с этим поможет переустановка приложения.

Не открывается чат с включённым режимом конфиденциальности на Android

В версии приложения для Android для того, чтобы пользоваться режимом конфиденциальности в чате, приложению должен быть предоставлен доступ к хранилищу (к файлам). Это необходимо для того, чтобы нельзя было обойти систему контроля скриншотов.

Поэтому при первом открытии чата в режиме конфиденциальности выберите Разрешить, не нажимайте Больше не спрашивать и Отклонить.

Ошибка «Чат недоступен в версии для ПК. Откройте его в мобильном клиенте»

Такое уведомление возникает, когда один из участников чата включил режим конфиденциальности с мобильного устройства с настройкой Только мобильный доступ. В карточке чата откройте Режим конфиденциальности и выключите Только мобильный доступ.