UserScripts - Пользовательские скрипты

Зачем нужны, чем могут помочь и где они обитают

@flyink (Котляров Евгений)

Котляров Евгений

Что такое UserScript?

Код, который хранится у пользователя и выполняет заданные действия.

Зачем нужны?

Автоматизация шаблонных действий

Парсинг и улучшение просмотра

Упрощение и ускорение работы

Сложный для реализации функционал

Что умеют?

Где работают пользовательские скрипты?

Greasemonkey

  1. greasespot.net
  2. Дата выхода: 28 марта 2005 года (до Появления Chrome)
Проект Greasemonkey написан Aaron Boodman в 28 ноябре 2004 года, который был вдохновлен Firefox расширением, которое очищало интерфейс сайта AllMusic. К маю 2005 года для Greasemonkey было создано около 60 общих и 115 специфичных для сайта пользовательских скриптов.

Google Chrome

С февраля 2009 года в Google Chrome появилась встроенная поддержка пользовательских скриптов в формате Greasemonkey, но позже установка пользовательских скриптов с посторонних доменов была отключена.

chome block userscript

Tampermonkey

  1. tampermonkey.net
  2. Доступен на Chrome, Microsoft Edge, Safari, Opera и Firefox.
Tampermonkey был написан Jan Biniok в мае 2010 года из Greasemonkey, который уже был встроен в Chrome, но не имел полной совместимости.

Violentmonkey

  1. violentmonkey.github.io
  2. Менеджер пользовательских скриптов с открытым исходным кодом

UserScripts для Safari

  1. github.com/quoid/userscripts
  2. Доступно для Safari на iOS (+ipadOS) и macOS.
  3. An open-source userscript manager for Safari

Поддержка

       Android  iOS  Linux  macOS  Windows
Chrome
Edge
Firefox
Opera
Safari
Yandex Browser

openuserjs.org/about/Userscript-Beginners-HOWTO

Интерфейс

Создадим новый скрипт?

Интерфейс редактора Tampermonkey

Метаданные

// ==UserScript== // @name New Userscript // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author You // @match https://vk.com/ // @grant none // ==/UserScript==

Подробнее о Метаданных пользовательских скриптов: tampermonkey.net/documentation.php

Задаем описание скрипта

// @name Название скрипта // @namespace http://domain.com/ // name + namespace - формируют id скрипта // @version 0.1 // @description Описание // @author Автор

Пишем код?

Пробуем... Обновляем страницу...

(function() { 'use strict'; console.log('hello', location.pathname); })();

... и видим несколько логов

Все дело в iframe

PS: Тут в хроме можно переключать контекст

Определяем набор страниц

// Добавить // @include https://domain.com/* // Исключить // @exclude https://domain.com/image/ // Отключить в фреймах // @noframes

Добавляем @noframes и проверяем

Пробуем вызвать функцию сайта...

(function() { 'use strict'; const onDone = () => { document.body.style = 'green'; }; ajax.post('https://vk.com/foaf.php?id=1', {}, { onDone }); })();

А Greasemonkey ее не видит...

Content Scripts работают в своем контексте

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

Aleks Zinevych. Google Chrome Extensions — Часть 1. Архитектура

Встраиваем код в страницу

В Greasemonkey можно использовать @grant GM.unsafeWindow. Tampermonkey сам пробрасывает переменные.

(function injectScript(fn) { var script = document.createElement('script'); script.textContent = '(' + fn + ')();'; script.id = fn.name; document.head.appendChild(script); })(function TryAjax() { const onDone = () => { document.body.style = 'green'; }; ajax.post('https://vk.com/foaf.php?id=1', {}, { onDone }); });

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

MutationObserver

Помогает отслеживать изменения в дереве элементов

searchGreenElements(document.body); new MutationObserver(function onMutations(mutations) { mutations.forEach(function onMutation(mutation) { searchGreenElements(mutation.target); }); }).observe(document.body, { subtree: true, childList: true, });

window.addEventListener + event.target

Реагируем нужные нам действия

window.addEventListener('keydown', function onKeyDown(event) { if (event.target.classList.contains('support_input')) { showAndFilterHelpList(event.target); } });

Proxy, getter и setter

Ловим чтение и запись в обьектах

Object.defineProperty(store, "adsLists", { set: function() { // хе-хе return []; }, get: function() { return []; } });

Оборачиваем функций

Заменяем аргументы и результаты функций

ajax.post = (function replaceFunction(ajaxPost) { return function ajaxPostFake() { var query = arguments[1]; if (/^хз/i.test(query.msg)) { query.msg = 'Не знаю'; } return ajaxPost.apply(this, arguments); } })(ajax.post);

А можно вызывать API расширений?

Расширения предоставляют свое API

С помощью @grant вы перечисляете все функции, которые хотите вызвать

// Получение метаданных скрипта: // @grant GM.info // Аналог localStorage в контексте расширения: // @grant GM.deleteValue/getValue/setValue/listValues // Вывод уведомлений: // @grant GM.notification // Открытие новой вкладки: // @grant GM.openInTab // Копирование в буффер обмена // @grant GM.setClipboard

@grant GM.getResourceUrl

Запрос ресурсов с другого сайта. Возвращает Data URL.

// ... // @resource logo ../icons/laptop.png // @grant GM.getResourceUrl (async () => { let img = document.createElement("img"); img.src = await GM.getResourceUrl("logo"); document.body.appendChild(img); })();

@grant GM.xmlHttpRequest

Позволяет делать запросы к другом сайтам

// ... // @connect api.vk.com // @grant GM.xmlHttpRequest GM.xmlHttpRequest({ method: "GET", url: "http://api.vk.com/method/users.get", onload: ({ response }) => { console.log(response); } });

Tampermonkey запрещает лишние запросы

Могу ли я добавить элементы в меню расширения?

@grant GM_registerMenuCommand

// ==UserScript== // @name MenuExample // @include https://vk.com/* // @grant GM_registerMenuCommand // ==/UserScript== GM_registerMenuCommand('doSomeMagic', function() { alert('doSomeMagic'); }, 'r');

А работать с контекстным меню?

Можно в контекстное меню *

* - но только в Google Chrome

@run-at context-menu

// ==UserScript== // @name onContextMenu > doSomeMagic // @author FlyInk13 // @include https://vk.com/* // @run-at context-menu // ==/UserScript== (function onContextMenu() { alert('doSomeMagic'); })();

@run-at

Куда можно сохранить скрипт?

Места обитания

  1. OpenUserJS
  2. gist.github.com
  3. Альтернативы UserScripts.org
  4. На своем сайте с «*.user.js» в конце ссылки

А обновлять?

Для этого можно указать @updateURL

// @updateURL https://openuserjs.org/....js

А есть ли жизнь без расширений?

Букмарклеты

Закладки, в адресе которых прописан JavaScript код, выполняемый при их открытии

И не только в браузерах...

Shortcut для iOS

Automator в Mac OS

AppleScript, JavaScript & Shell

Кнопки в Touch Bar

JetBrains LivePlugin

Когда компилировать и публиковать официальные плагины сложно

LivePlugin Kotlin & Groovy

Подведем итоги

На этом все

Материалы

  1. Учимся писать userscript'ы
  2. Автоматизация через Userscript
  3. Укрощаем GreaseMonkey
  4. Букмарклет - wiki