Управлять сотнями вкладок в 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 означает X. Может, macOS X? ¯\_(ツ)_/¯
Я посмотрел забавные заметки о выпуске (документация JXA от Apple…), а затем подумал: «Так, JavaScript, неужели трудно собраться и самому разобраться?»
Я и представить не мог, что меня ждёт.
Несколько часов я потратил на поиски места для написания кода, расширения имени файла, выполнения файла и прикладного программного интерфейса.
Но в конце концов, это того стоило.
Начало работы с JXA
В основном, писать JXA рекомендуют в Script Editor App или Automator App, который идёт в комплекте с macOS. Я попробовал их, чуть не выкинул ноутбук в окно и решил использовать редактор, которым пользуюсь всегда: VSCode.
Я был пользователем Vim в течение примерно четырех лет, в течение года я ипользовал IDE JetBrains, Atom и Sublime, потом перешел на VSCode, и после четырех лет ежедневного использования в основном с Go, а иногда с Python, JavaScript и Markdown я до сих пор думаю, что это лучший редактор; и каждый должен попробовать пользоваться им месяц-другой, прежде чем судить.
Вот что стоит знать, прежде чем взяться за работу:
- Создайте файл с обычным расширением 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.
Я считаю, Apple стоит отказаться от устаревшего Spotlight и задуматься о приобретении 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.
Если вы не знали об этой функции, попробуйте щелкнуть значок Wi-Fi или значок динамика в строке меню, удерживая клавишу Alt, и вам откроются некоторые дополнительные функции.
Чтобы сделать это, мне нужно было подключить фильтр сценариев к моей команде 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 в качестве аргументов.
Соединение ключевого слова close url с Chrome Control
Вот как я закрываю все вкладки, содержащие apple или doc.
Я проделал то же самое для создания команды закрытия по названию на Alfred.
Я также добавил дополнительные горячие клавиши для запуска других команд:
- Alt + T — показать все вкладки.
- Alt + D — удалить повторяющиеся вкладки.
- Alt + C — закрыть вкладки по URL.
- Alt + Shift + C — закрыть вкладки по названию.
И вот как выглядит итоговый рабочий процесс:
Исходный код и рабочий процесс Chrome Control Alfred
Вы можете найти дополнительную документацию о Chrome Control и весь исходный код на GitHub.
Если вам кажется, что вам пригодится рабочий процесс Chrome Control, не стесняйтесь скачивать его с GitHub здесь.
Alfred не позволяет рабочим процессам экспортировать горячие клавиши для защиты пользователей от конфликтующих ярлыков. После импорта вам придется вручную устанавливать горячие клавиши, причем это должно занять не более 60 секунд.
Заключение
JXA в сочетании с Alfred — чрезвычайно мощный инструмент для создания практически неограниченного количества полезных рабочих процессов. Было очень сложно начать, найти хорошую документацию, но в итоге я смог создать то, о чём мечтал, и радости моей нет предела.
Конечно, было бы замечательно, если бы Chrome предоставлял такие функции по умолчанию, но я не очень оптимистично настроен по этому поводу.
Таких костылей, я, однако, никогда не встречал:)
Комментарий недоступен
Интересно, спасибо. :)
Я недавно тоже боролся со временными вкладками.
У меня их не было 100, но это были вкладки, которые не охота добалвять в закладки (т. к. их через пару дней из закладок нужно будет удалять вручную), а когда они "висели" незакрытыми, ожидая своей очереди, мешали концентрироваться на текущих задачах.
В итоге я пошел по пути раширения. Если интересно, вот что вышло:
https://chrome.google.com/webstore/detail/tab-bucket/ojmpjnfpigebajjcoddpjoihgjlahnpa
Эээ, но ведь есть же популярное (и, главное, удобное) расширение OneTab.
https://chrome.google.com/webstore/detail/onetab/chphlpgkkbolifaimnlloiipkdnihall
Гиковская херь.