Управлять сотнями вкладок в Chrome с помощью JXA и Alfred Статьи редакции
Перевод материала Ринана Кейкирерка, бэкенд-разработчика Uber.
Несколько сотен открытых вкладок уже долгое время являются для меня проблемой. Ну, может и не несколько сотен, но точно около сотни. Чем больше у меня открытых вкладок, тем больше среди них повторяющих друг друга. В итоге я не пытаюсь их разобрать, а просто закрываю браузер и начинаю сначала, теряя в результате что-то важное или интересное.
Частично в этом можно обвинить плохую и устаревшую структуру интерфейса современных браузеров. Вообще, всё, чем занимаются их разработчики с позиции структуры и дизайна интерфейса — это делают браузеры чуть привлекательнее внешне.
Например, просто вбивать ключевые слова в адресную строку в поисках ссылки, по которой вы когда-то переходили — очень плохой способ её найти. В Chrome, кажется, есть некий поиск по неточному соответствию, но работает он плохо, в отличие от прекрасного инструмента fzf.
Ещё одним примером может служить работа с несколькими вкладками. В Chrome нет поиска по вкладкам, располагать их вертикально нельзя, а если вкладок слишком много, то в горизонтальном положении видны только иконки. К тому же, нельзя избавиться от повторяющихся вкладок.
Да, для этого можно скачать расширение. Пусть это прозвучит параноидально, но я не хочу пользоваться никакими расширениями, которые запрашивают такой доступ:
Читать и изменять данные на посещаемых сайтах.
Изменять стартовую страницу при открытии новой вкладки.
- Просматривать и изменять историю браузера.
- Отображать оповещения.
Да, это означает, что я не пользуюсь никакими расширениями для Chrome, несмотря на их очевидные преимущества. Я знаю, что некоторые из них созданы на основе открытого программного кода, но открытый код не гарантирует безопасности, особенно если проверить весь код лень.
Итак, я решил сам разобраться с проблемой управления вкладками, потому что для творчества время всегда найдётся.
Выбор подходящих инструментов
Первым пунктом в списке стояло найти способ управлять Chrome. Я рассматривал следующие варианты:
- Написать расширение для Chrome самому: делать этого мне не хотелось, потому что расширения полезны только тогда, когда большая часть работы происходит в браузере. То есть, когда я работаю в другом окне, например, терминале, а потом хочу открыть определённую вкладку, мне придётся открыть Chrome, а потом включить расширение. Слишком много действий.
- Использовать AppleScript: AppleScript — это язык сценариев для контроля приложений с помощью Apple Events. Этот вариант мне понравился, пока я не попробовал разные примеры. Мне было очень неудобно, поскольку я всю жизнь пользовался C, C++, Python, Java, Go и JavaScript. Так что я решил вернуться к этому, только если не найду ничего получше.
- Использовать Python: Я обрадовался, узнав, что есть библиотеки, которые можно использовать для работы с macOS. В итоге оказалось, что те давно устарели и никакой документации по ним не было. От этого варианта тоже пришлось отказаться, потому что мне необходимо было быстрое решение.
Но я не сдавался, продолжал изучать пути решения и наткнулся на JXA.
Что такое JXA
JXA — это «JavaScript для Автоматизации». Он поддерживается Apple, позволяет управлять приложениями с помощью AppleScript, поддерживает синтаксис ES6; в целом всё это звучит слишком хорошо, чтобы быть правдой… Так и оказалось, потому что у него худшая документация, которую я когда-либо видел у Apple.
Я посмотрел забавные заметки о выпуске (документация JXA от Apple…), а затем подумал: «Так, JavaScript, неужели трудно собраться и самому разобраться?»
Я и представить не мог, что меня ждёт.
Несколько часов я потратил на поиски места для написания кода, расширения имени файла, выполнения файла и прикладного программного интерфейса.
Но в конце концов, это того стоило.
Начало работы с JXA
В основном, писать JXA рекомендуют в Script Editor App или Automator App, который идёт в комплекте с macOS. Я попробовал их, чуть не выкинул ноутбук в окно и решил использовать редактор, которым пользуюсь всегда: VSCode.
Вот что стоит знать, прежде чем взяться за работу:
- Создайте файл с обычным расширением js (например, script.js)
- Вставьте эту строку первой в файле: #!/usr/bin/env osascript -l JavaScript
- Сделайте сценарий исполняемым: chmod +x ./script.js
- И запустите его с помощью: ./script.js
Можете воспользоваться этим шаблоном:
Подключение к Chrome
Самой сложной проблемой оказалось придумать, как подключаться к приложениям. Для методов нет автозавершения, а вывод полей и методов объекта не сработал.
Затем я случайно наткнулся на статью, в которой упоминается использование функции редактора сценариев: «Открыть словарь».
И вуаля! Именно то, что я искал: небольшая заметка для каждого приложения, в которой перечислены методы и объекты; и там был Chrome!
Вот как выглядит интерфейс.
Не лучший вариант, но работать с ним можно.
Итак, первое, что необходимо было сделать, это создать копию Chrome:
includeStandardAdditions используется для добавления стандартных методов, например, displayDialog в копию приложения.
Понять, как взаимодействовать с этой копией, оказалось довольно просто. Вот как создать список всех вкладок во всех окнах:
Закрыть вкладки тоже оказалось довольно просто:
Остановиться на определённой вкладке в определённом окне немного сложнее:
Затем я хотел вывести текст в формате JSON, чтобы передать его на спасительную команду jq, но тут понял, что console.log выводит данные в stderr, а функции для вывода в stdout не существует, а я не хотел перенаправлять stderr на stdout.
К счастью, я узнал, что можно импортировать Objective C в JXA. И решил написать свою функцию для вывода:
Хоть понимание того, как это работает, и заняло много времени, оно оказалось очень полезным. К счастью, у меня уже был небольшой опыт работы с Objective C, который помог в проекте «Как можно смотреть трейлеры фильмов на постерах к ним с помощью iPad и дополненной реальности» в 2011–2012 годах, когда AR еще только начинала развиваться на iOS, и я возлагал на неё большие надежды…
После этого, чтобы получить входную информацию от терминала, я должен был написать свою собственную входную функцию:
Наконец, мне надо было найти способ отобразить подсказку Chrome, которая будет спрашивать, действительно ли я хочу закрыть перечисленные вкладки:
Появление нового проекта: Chrome Control
Я собрал всё в единый проект, который назвал Chrome Control. Может быть, кто-то сможет использовать его вместе со своим любимым инструментом — например, vim или fzf. Программный код лежит на GitHub.
Вот как выглядит Chrome Control:
Теперь, когда у меня появилось решение, пришло время использовать Chrome Control в лучшем приложении для продуктивности в нашей мультивселенной: Alfred.
Использование Chrome Control с Alfred
Alfred — одно из лучших приложений, благодаря которому я — 🦄без преувеличения — становлюсь в десять раз продуктивнее. Я создал десятки рабочих процессов, которые ускоряют выполнение повседневных задач.
С этим проектом моей мечтой стало иметь возможность нажать Alt + T в любом месте macOS, начать вводить название вкладки и мгновенно увидеть список вкладок, отфильтрованных с помощью поиска по неточным соответствиям, затем просто выделить нужную вкладку, нажать Enter и перейти к ней.
Для начала мне нужно было создать новый рабочий процесс в Alfred.
Я сделал так, чтобы Chrome Control выводил список вкладок в формате JSON для включения интеграции. Это помогает, потому что, если бы я хотел получить список вкладок в Alfred, мне пришлось бы использовать фильтр сценариев, а фильтр сценариев требует вывода в формате JSON с дополнительными особыми полями.
Вот выходные данные команды list:
Поля arg и subtitle необходимы для фильтра сценариев, ниже мы рассмотрим это подробнее.
Создание фильтра сценариев открывает такое окно:
Я хотел, чтобы ключевым словом было tabs, чтобы каждый раз, когда я набираю tabs, появлялся список всех вкладок во всех окнах.
Стоит обратить внимание на множество других вещей здесь. Во-первых, with space argument optional. Это говорит Alfred ожидать только команды tabs или дополнительного аргумента, например, tabs apple, который отобразит все связанные с Apple вкладки.
У Alfred есть замечательная функция поиска неточных соответствий. Чтобы включить её, я просто установил флажок в Alfred filters results.
В разделе сценариев я сказал Alfred выполнить мою команду list. И перетащил иконку, созданную в Photoshop.
Вот как выглядит конечный результат:
Второй шаг — привязка сочетания Alt + T к этой команде. В Alfred сделать это очень просто при помощи триггера hotkey:
Установить его на Alt + T:
Затем я соединил его с фильтром сценариев с помощью связки:
Теперь, когда у меня появился способ использовать горячую клавишу и отфильтровать вкладку, можно выбрать нужную вкладку нажатием клавиши Enter.
Работа с необходимой вкладкой
С этой задачей пришлось повозиться. Мне надо дать Alfred команду запуска другого сценария по результату фильтра сценариев.
Помните значение arg, которое я выводил в JSON, с результатом 0,1? Первое значение — это Window Index, а второе — Tab Index. Мне нужно было передать это значение arg команде ./chrome.js focus.
К счастью, Alfred использует это значение arg, чтобы передать его как переменную шаблона {query} для дальнейших действий, которые вы подключаете к фильтру сценариев.
Это означает, что я могу подключить выходные данные фильтра сценариев к новому действию Run Script и передать значение arga команде focus.
А затем просто запускать команду ./chrome.js focus {query} всякий раз, когда элемент выбирается из списка.
Получилось!
Я также хотел иметь способ закрыть выделенные вкладки.Для этого я мог бы использовать клавишуAlt, которая является клавишей-модификатором в macOS.
Чтобы сделать это, мне нужно было подключить фильтр сценариев к моей команде close.
Чтобы сообщить Alfred, что этот сценарий должен запускаться только при нажатии клавиши Alt, вам нужно щелкнуть правой кнопкой мыши на соединение и настроить его.
Я хотел, чтобы текст «Закрыть эту вкладку» появлялся при удержании клавиши Alt. Результат выглядит следующим образом:
После этого мне захотелось добавить ещё больше функций, и я решил придумать решение для моей проблемы повторяющихся вкладок.
Удаление повторяющихся вкладок
Дублирующие друг друга вкладки давно были проблемой, для решения которой я решил добавить команду дедупликации в Chrome Control.
Она просто перебирает все открытые вкладки, находит дубликаты и закрывает их.
Но была одна загвоздка: что, если бы я случайно закрыл вкладку с несохраненной работой, частично заполненной формой, несохраненным документом или наполовину написанным письмом?
По этой причине я хотел, чтобы выскакивало окошко со списком вкладок, которые программа хотела закрыть, и спрашивало у меня, действительно ли я хочу их закрыть. Я сделал это параметром по умолчанию и добавил флажок --yes, чтобы иметь возможность принудительно закрыть все вкладки без появления окошка.
Вот пример избавления от одинаковых пяти вкладок Hacker News:
Затем я подсоединил это к Alfred. Но потом я понял, что теперь мне нужно уведомление в браузере. Поэтому я добавил флажок --ui. Когда этот флажок отмечен, Chrome Control будет показывать уведомления в браузере, а не в терминале.
Я просто создал keyword trigger, который соединил с ./chrome.js dedup. Теперь в Chrome появляется диалоговое окно!
Вот почему мы добавили следующую строку в начале нашего сценария. Она позволила этому окошку появиться.
Закрытие вкладок по ключевым словам
Ещё одна функция, которую я хотел добавить — это возможность закрывать вкладки по ключевым словам. Ключевые слова могут быть как в URL, так и в заголовке вкладки.
Например, если бы у меня было открыто несколько миллионов документов в Google Docs, я мог бы просто напечатать ./chrome.js close --url docs.google. И тогда Chrome Control найдет все URL-адреса, содержащие эту строку, и закроет вкладки.
Но я также хотел, чтобы это работало с заголовком вкладки. Например, если бы я изучал информацию о последнем iPhone, возможно, я мог бы закрыть все заголовки, которые включали слово iPhone следующим образом: ./chrome.js close --title iphone.
Итак, я решил создать обе эти команды.
Эти команды могут включать в себя несколько ключевых слов, разделенных пробелом. А если в самой фразе есть пробел, то её можно заключить в двойные кавычки, например, "это фраза с пробелами".
Затем я подсоединил их к Alfred, только в этот раз необходимо было создать аргумент.
А затем добавил действие Run Script и на этот раз использовал with input as argv. Это позволило мне использовать $@, которая отправляет все набранные ключевые слова в Chrome Control в качестве аргументов.
Вот как я закрываю все вкладки, содержащие apple или doc.
Я проделал то же самое для создания команды закрытия по названию на Alfred.
Я также добавил дополнительные горячие клавиши для запуска других команд:
- Alt + T — показать все вкладки.
- Alt + D — удалить повторяющиеся вкладки.
- Alt + C — закрыть вкладки по URL.
- Alt + Shift + C — закрыть вкладки по названию.
И вот как выглядит итоговый рабочий процесс:
Исходный код и рабочий процесс Chrome Control Alfred
Вы можете найти дополнительную документацию о Chrome Control и весь исходный код на GitHub.
Если вам кажется, что вам пригодится рабочий процесс Chrome Control, не стесняйтесь скачивать его с GitHub здесь.
Заключение
JXA в сочетании с Alfred — чрезвычайно мощный инструмент для создания практически неограниченного количества полезных рабочих процессов. Было очень сложно начать, найти хорошую документацию, но в итоге я смог создать то, о чём мечтал, и радости моей нет предела.
Таких костылей, я, однако, никогда не встречал:)
Комментарий недоступен
Интересно, спасибо. :)
Я недавно тоже боролся со временными вкладками.
У меня их не было 100, но это были вкладки, которые не охота добалвять в закладки (т. к. их через пару дней из закладок нужно будет удалять вручную), а когда они "висели" незакрытыми, ожидая своей очереди, мешали концентрироваться на текущих задачах.
В итоге я пошел по пути раширения. Если интересно, вот что вышло:
https://chrome.google.com/webstore/detail/tab-bucket/ojmpjnfpigebajjcoddpjoihgjlahnpa
Эээ, но ведь есть же популярное (и, главное, удобное) расширение OneTab.
https://chrome.google.com/webstore/detail/onetab/chphlpgkkbolifaimnlloiipkdnihall
Да, я пользовался им какое-то время. К сожалению, не подходит под мои конкретные задачи. Мне нужно было что-то очень простое, с быстрым доступом, именно для временного хранения вкладок (не больше двух дней), и с автоматическим удалением из списка чтобы не удалять вручную после просмотра/прочтения. Что я и реализовал.
В любом случае, спасибо за совет. Думаю, кому-нибудь пригодится.
Гиковская херь.