Skip to content

feat(chat): выбор всех сообщений автора из контекстного меню - #115

Merged
catboysense merged 15 commits into
masterfrom
feat/select-by-author-c9axz4
Oct 23, 2026
Merged

catboysense merged 15 commits into
masterfrom
feat/select-by-author-c9axz4

Conversation

@catboysense

@catboysense catboysense commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Что было

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

Что стало

  • В контекстном меню сообщения (долгое нажатие) появился пункт «Выбрать все сообщения: <имя>». Он загружает с сервера все сообщения этого автора в чате (в ветке ответов или теме форума, только в ней), включает режим выбора и отмечает их все.
  • На своём сообщении пункт называется «Выбрать все мои сообщения».
  • Пока сообщения грузятся, показывается индикатор, загрузку можно отменить. Если какая-то страница не загрузилась, выбираются найденные сообщения и показывается подсказка об этом.
  • В личном чате выбираются все сообщения собеседника: переписка просматривается целиком (до 50 000 сообщений), а не поиском, который находил только часть. В чате с ботом, у которого есть темы, выбираются сообщения только открытой темы.
  • За раз выбирается не больше 10 000 самых новых сообщений. Если у автора их больше, подсказка «Выбрано сообщений: N. Это самые новые, больше за раз выбрать нельзя.» Если в длинной личке сработал предел просмотра 50 000, у подсказки свой текст: «Выбрано сообщений: N. В длинной переписке просматриваются только последние 50 000 сообщений.»
  • Работает и для участников больших групп, которых клиент знает только по сообщениям: такой автор адресуется через нажатое сообщение.
  • Перед пересылкой больше 1000 выбранных сообщений спрашивается подтверждение: сервер принимает пересылку по 100 сообщений, поэтому она займёт время и придёт в чат частями.
  • Пересылка больше 100 сообщений больше не теряет альбомы: раньше альбом, не поместившийся в остаток пачки, оставался с часами и уходил позже не на своём месте.
  • Выбор в супергруппе, мигрированной из обычной группы, пересылается по порядку (сначала старая группа, потом супергруппа) и без ошибки «Invalid forward peer ids» у пачки, где встречались сообщения из обоих чатов.
  • Сохранённые удалённые сообщения автора тоже отмечаются. В секретном чате сообщения берутся с устройства, сервера там нет.
  • Служебные, рекламные, эфемерные и просроченные сообщения не отмечаются, как и в обычном «Выбрать».
  • В ветке ответов корневое сообщение ветки (и весь его альбом) не отмечается: в режиме выбора у него нет галочки, и раньше оно незаметно удалялось или пересылалось вместе с ответами.
  • Пункт не показывается в Избранном и в каналах (там все сообщения от одного автора). В отложенных сообщениях и быстрых ответах выбираются загруженные, и пункт там есть, только если он выберет больше, чем обычное «Выбрать».
  • Английский перевод строк: «Select All Messages from %@», «Select All My Messages» и тексты подсказок и предупреждения.

Как

  • TelegramCore/Sources/Donutgram/DonutgramAuthorMessages.swift: новый engine.messages.donutgramAuthorMessages(peerId:threadId:authorId:sourceMessageId:). Листает messages.search с from_id по 100 сообщений (с top_msg_id в ветке, с saved_peer_id в личке канала, плюс история мигрированной группы). Личный чат листается целиком через messages.getHistory (не больше 50 000 сообщений), а сообщения автора отбираются на устройстве: поиск по from_id в личке на устройстве находил только часть сообщений собеседника. У getHistory нет top_msg_id, поэтому в теме бота отбираются ещё и сообщения этой темы (threadId). Найденные сообщения, которых ещё нет в базе, сохраняются в базу через addMessages(..., location: .Random), как в getMessagesLoadIfNecessary (пересылка и выбор вариантов удаления читают сообщения из базы), к ним добавляются сохранённые удалённые. from_id строится через apiInputPeer(_:sourceMessageId:transaction:), чтобы автор без собственного access hash ушёл как inputPeerUserFromMessage. Листание останавливается на пустой странице, на точном count (приблизительный, с флагом inexact, не учитывается) или на пределе donutgramAuthorMessagesLimit = 10 000. Какой предел сработал, результат сообщает отдельно (reachedLimit / reachedHistoryScanLimit), чтобы подсказка не выдавала предел просмотра за предел выбора. Следующая страница всегда начинается дальше предыдущей, чтобы не зациклиться. Корень ветки и его альбом исключаются уже здесь. Если в личке канала не нашёлся пир темы, поиск не делается вовсе, чтобы не искать по всему монофоруму.
  • TelegramUI/Sources/Chat/ChatControllerDonutgramSelectAuthorMessages.swift: решает, показывать ли пункт, по нажатию запускает загрузку под OverlayStatusController с отменой, фильтрует результат через donutgramSelectableMessageIds, добавляет сообщения из окна истории (например, ещё отправляемые), убирает корень ветки, вызывает beginMessageSelection и показывает подсказку о неполной загрузке или о пределе. Там же donutgramConfirmLargeForwardIfNeeded, который вызывается из forwardSelectedMessages в ChatControllerLoadDisplayNode.swift.
  • ChatInterfaceStateContextMenus.swift: у contextMenuForChatPresentationInterfaceState новый параметр selectAuthorMessages, пункт стоит рядом с «Выбрать».
  • TelegramCore/Sources/State/PendingMessageManager.swift (код upstream): альбом, не поместившийся в пачку, возвращается в очередь перед новой пачкой; чат-источник входит в PendingForwardBatchKey, а пачки уходят в порядке постановки в очередь.
  • ChatControllerForwardMessages.swift: forwardMessages(messageIds:) сортирует по чатам (раньше начавшийся чат первым), внутри чата по id. Раньше сортировка шла по MessageId, где номер сравнивается раньше чата, и новые сообщения супергруппы обгоняли старые сообщения группы.
  • Строки добавлены в DGLocalization.swift. Имя стоит после двоеточия, потому что в «от <имя>» его пришлось бы склонять; число в подсказках по той же причине стоит после «Выбрано сообщений:».

Проверка

  • Сборки в облаке нет (Linux), код проверен чтением и пятью ревью (reviews/pr-115-review.md … reviews/pr-115-review-5.md); сборку IPA делает CI этого PR. Разбивка пересылки на пачки проверена симуляцией той же логики: 95 одиночных + альбом из 10 + 5 одиночных раньше давали пачки [95, 5] (альбом выпадал), теперь [95, 15]; 2000 случайных наборов до 500 сообщений: всё отправлено, порядок сохранён, пачки не больше 100.
  • На устройстве посмотреть:
    • группа с длинной историей: «Выбрать все сообщения: <имя>» → индикатор → счётчик в панели выбора совпадает с числом сообщений автора (можно сверить с поиском по участнику в чате)
    • после выбора пролистать далеко вверх: старые сообщения автора тоже отмечены
    • автор с альбомами, больше 100 выбранных → переслать в Избранное: все альбомы пришли, ни одного сообщения с часами
    • супергруппа, мигрированная из обычной группы, автор писал до и после миграции: переслать все, порядок верный, ошибок отправки нет
    • личный чат с десятками тысяч сообщений: подсказка о пределе (текст поменялся на «Выбрано сообщений: N. …», стоит глянуть ещё раз)
    • бот с темами: в одной теме «Выбрать все мои сообщения», сообщения из других тем не отмечены
    • большая публичная группа, автор не в контактах: нет подсказки «не все удалось загрузить»
    • отмена на индикаторе: выбор не включается
    • группа: долгое нажатие на своё сообщение → «Выбрать все мои сообщения»
    • группа, ветка ответов на своё сообщение: корень ветки не отмечен, счётчик совпадает с галочками
    • тема форума: выбираются сообщения только этой темы
    • личный чат: выбираются все сообщения собеседника и все свои (сверить пролистыванием вверх); секретный чат тоже
    • больше 1000 выбранных → «Переслать» → предупреждение, «Отмена» ничего не пересылает
    • автор с тысячами сообщений: панель выбора не тормозит, «Удалить» и «Переслать» работают
    • Избранное и канал: пункта нет
    • служебные сообщения (вступил в группу и т.п.) не отмечаются

Не сделано

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

@catboysense catboysense self-assigned this Oct 20, 2026
@catboysense
catboysense force-pushed the feat/select-by-author-c9axz4 branch from f831075 to b5a25b7 Compare October 20, 2026 05:23
@catboysense catboysense changed the title feat(chat): выбор всех сообщений автора из контекстного меню feat(chat): выбор сообщений автора из контекстного меню Oct 20, 2026
@catboysense catboysense changed the title feat(chat): выбор сообщений автора из контекстного меню feat(chat): выбор всех сообщений автора из контекстного меню Oct 20, 2026
donutgramAuthorMessages листает messages.search по from_id (с историей
мигрированной группы и в ветке, если она указана), сохраняет найденные
сообщения в базу, как getMessagesLoadIfNecessary, и добавляет сохранённые
удалённые. Автор без собственного access hash адресуется через своё
сообщение. В секретном чате сообщения берутся из локальной базы.
@catboysense
catboysense force-pushed the feat/select-by-author-c9axz4 branch from 6d168e0 to 5596212 Compare October 20, 2026 05:42
Пункт «Выбрать все сообщения: имя» («Выбрать все мои сообщения» на своём
сообщении) загружает все сообщения автора с сервера под индикатором с
отменой и включает выбор со всеми ними. Корень ветки ответов не выбирается:
у него нет галочки, и он ушёл бы в удаление или пересылку незаметно. Если
страница не загрузилась, выбираются найденные и показывается подсказка.
В отложенных и быстрых ответах выбираются загруженные.
Сервер принимает пересылку по 100 сообщений, поэтому выбор, сделанный
«Выбрать все сообщения», может пересылаться долго и приходить частями.
Перед пересылкой такого выбора теперь спрашивается подтверждение.
@catboysense
catboysense force-pushed the feat/select-by-author-c9axz4 branch from 5596212 to 75dd10a Compare October 20, 2026 06:25
Если альбом не помещался в остаток пачки, начиналась новая пачка, а сам
альбом, уже снятый с очереди, в неё не возвращался. Он оставался с часами
до следующей отправки и уходил не на своём месте.
Пачки по 100 собирались только по месту назначения. Пачка, где были
сообщения супергруппы и группы, из которой она мигрировала, падала на
"Invalid forward peer ids" (в debug на assertionFailure), и её сообщения
помечались неотправленными. Теперь источник входит в ключ пачки, а пачки
уходят в порядке постановки в очередь.
MessageId сравнивает сначала номер, а супергруппа нумерует сообщения
заново, тогда как группа до миграции брала номера аккаунта. Поэтому при
пересылке выбора из обеих историй новые сообщения супергруппы уходили
раньше старых. Теперь сообщения упорядочены по чатам (раньше начавшийся
чат первым), внутри чата по id, как и раньше.
Экран выбора ждал только итог, а промежуточные .progress и knownTotals
никто не читал. Загрузка теперь отдаёт один итог.
С флагом inexact сервер даёт оценку, которая может быть меньше настоящего
числа. Загрузка останавливалась по ней раньше времени и не сообщала, что
нашла не всё. Теперь приблизительный count не учитывается, и листание идёт
до пустой страницы.
Если пир темы не нашёлся, запрос уходил без saved_peer_id и искал по всему
монофоруму, а найденное попадало в выбор для удаления. Теперь поиск не
делается, выбираются сообщения из окна истории и показывается подсказка,
что загрузить не удалось. Так же теперь отвечает и случай, когда не
нашёлся сам чат или автор.
В длинной личной переписке у собеседника бывают десятки тысяч сообщений:
сотни запросов подряд, а потом выбор, в котором панель следит за каждым
сообщением отдельно. Теперь загрузка останавливается на 10 000 самых
новых, а подсказка говорит, что выбраны последние 10 000.
Чат исключал корень ветки и его альбом, только пока корень был в
загруженном окне. В длинной ветке элементы альбома-корня, найденные
поиском, попадали в выбор без галочек. Теперь загрузка сама исключает
корень и весь его альбом.
В личном чате поиск messages.search по from_id находил только часть
сообщений собеседника, так что выбиралась часть. Официальные клиенты
так личку не ищут. Теперь личка листается целиком через getHistory, а
сообщения нужного автора отбираются на устройстве. Чтобы в очень длинной
переписке не листать сотни тысяч сообщений, просматривается не больше
50 000, и тогда подсказка называет число выбранных.
Личка листается через getHistory, у которого нет top_msg_id, поэтому в
теме бота с топиками выбирались сообщения автора из всех тем, и удаление
или пересылка забирали невидимые сообщения из других тем. Теперь из
истории отбираются только сообщения открытой темы.
Когда в длинной личке срабатывал предел просмотра 50 000 сообщений,
подсказка говорила «больше за раз выбрать нельзя» с любым числом, как
будто это и есть предел. Теперь у этого случая свой текст, а число
стоит после «Выбрано сообщений:», чтобы не склонять «сообщений» по
числу (раньше выходило «последние 21 сообщений»).
@catboysense
catboysense merged commit 69ba8cb into master Oct 23, 2026
4 checks passed
catboysense added a commit that referenced this pull request Oct 23, 2026
В 13.1 весь TelegramCore подключает TelegramApi через internal import, а
файл из #115 подключал его обычным import. TelegramCore собирается с
-warnings-as-errors, и в режиме Swift 5 такое смешение даёт ошибку
«ambiguous implicit access level for import of 'TelegramApi'».
catboysense added a commit that referenced this pull request Oct 23, 2026
В контекстном меню сообщения появился пункт «Выбрать все сообщения: <имя>»,
на своём сообщении «Выбрать все мои сообщения». Он загружает с сервера все
сообщения автора в чате (в ветке ответов или теме форума только в ней) и
выделяет их: не больше 10 000 самых новых, личка просматривается целиком до
50 000 сообщений. Пока идёт загрузка, виден индикатор с отменой; если
загрузилось не всё или сработал предел, показывается подсказка.

Перед пересылкой больше 1000 сообщений спрашивается подтверждение. Пересылка
больше 100 сообщений больше не теряет альбомы, а выбор из супергруппы и
группы, из которой она мигрирована, уходит по порядку и без ошибки
«Invalid forward peer ids».
catboysense added a commit that referenced this pull request Oct 23, 2026
В 13.1 весь TelegramCore подключает TelegramApi через internal import, а
файл из #115 подключал его обычным import. TelegramCore собирается с
-warnings-as-errors, и в режиме Swift 5 такое смешение даёт ошибку
«ambiguous implicit access level for import of 'TelegramApi'».
@catboysense
catboysense deleted the feat/select-by-author-c9axz4 branch October 23, 2026 22:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant