Полтора месяца назад я рассказывал, зачем сделал Conversation to Markdown: я разговариваю с ChatGPT во время тренировок, а потом передаю весь разговор Codex, чтобы тот превратил его в задачи. Расширение прокручивало диалог, собирало сообщения по ролям и копировало результат в буфер.
На моих собственных диалогах оно работало. На по-настоящему длинных — нет. Версия 1.1.6 закрывает три вещи, которые мешали пользоваться им всерьёз.
Прокрутка, которая не доезжала
Симптом был странный: на коротком разговоре всё хорошо, на длинном — ошибка «Conversation scroll did not settle before timeout».
Причина оказалась в критерии ожидания. Функция ждала, пока страница «успокоится», и считала стабильными кадры без изменений — сбрасывая счётчик при любом росте высоты документа. Но именно этим виртуализированная страница и занимается непрерывно: подгружает контент, высота растёт, счётчик обнуляется. Стабильность не наступала никогда, и чем длиннее диалог, тем вернее был таймаут.
Правильный критерий — не «ничего не менялось», а «прокрутка перестала двигаться». Теперь смотрим только на позицию: рост высоты документа больше не мешает признать её достигнутой. А если позиция всё-таки не взята за отведённое время, вместо ошибки происходит мгновенный переход к цели — сбор не начинается с середины разговора.
Картинки, которые скачивались битыми
Расширение умеет складывать диалог в папку загрузок вместе с картинками, заменяя ссылки на локальные пути. Только скачивались не картинки, а файлы с расширением .json и подписью «Сайт был недоступен».
Код передавал удалённый URL напрямую в загрузчик Chrome. Для картинок ChatGPT это не работает: они лежат на отдельном хосте, ссылки временные и подписанные, а запрос из контекста загрузчика приходит без нужных параметров.
Промежуточная попытка чинить это через canvas — взять уже отрисованный элемент, нарисовать в холст и вытащить пиксели — провалилась ещё нагляднее. Cross-origin картинка без CORS-заголовков «портит» холст, и чтение пикселей бросает исключение. Красивая идея, нулевой результат.
Работающий вариант скучнее. Контент-скрипт запрашивает картинку сам — у него объявлены права на нужный домен, — превращает её в data URL и уже его отдаёт в загрузку. Data URL скачивается всегда, потому что не ходит в сеть. Заодно выяснилось, что у ссылок на картинки нельзя срезать query-параметры, как срезаются они у обычных ссылок ради приватности: сервер отдаёт файл только при наличии подписи и метки времени. Для ссылок в тексте обрезка осталась, для картинок — нет.
Название диалога вместо conversation.md
Третья вещь — не баг, а то, чего не хватало. Файл всегда назывался conversation.md, и после третьего экспорта в загрузках лежали conversation (2).md и conversation (3).md без единого намёка на содержимое.
Теперь расширение читает название диалога — то самое, что ChatGPT показывает в боковой панели, — и использует его дважды: как заголовок в начале документа и как имя файла. Картинки складываются рядом и наследуют то же имя с порядковым номером:
Downloads/chatgpt-export/Агент-Аркана/
├── Агент-Аркана.md
├── Агент-Аркана-image_001.png
└── Агент-Аркана-image_002.jpg
Имя в картинках нужно не для красоты. Пока файлы звались image_001.png, экспорт двух разных диалогов давал одинаковые имена, и Chrome дописывал к ним (1), (2) — понять, из какого разговора картинка, становилось невозможно.
Кириллица в именах сохраняется: слаг вычищает только то, что ломает файловую систему. Если диалог ещё не назван, поведение прежнее — conversation.md в корне папки экспорта.
Что честно назвать ограничениями
Ссылки на картинки живут ограниченное время. Скачивать их надо, пока диалог открыт; те, что остались в Markdown ссылками, со временем перестанут открываться.
Chrome не перезаписывает файлы: повторный экспорт того же диалога создаёт копию с суффиксом, а не обновляет существующий файл.
И главное — расширение зависит от вёрстки ChatGPT. Меняется разметка страницы — нужно обновление. Это цена работы с чужим интерфейсом, и её стоит держать в уме.
Установка
Расширение распространяется как unpacked-сборка, не через Chrome Web Store: скачать репозиторий, открыть chrome://extensions, включить режим разработчика и указать папку с манифестом.
Всё выполняется локально в браузере: ни телеметрии, ни сервера, ни хранилища. Код открыт под лицензией MIT.