Перенести скрипт Tampermonkey на JavaScript
Есть рабочий скрипт в Tampermonkey:
// ==UserScript==
// @name New Userscript
// @namespace http://tampermonkey.net/
// @version 2025-11-06
// @description try to take over the world!
// @author You
// @match file:///C:/Users/soroc/Documents/vs/NewTab/index.html
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant GM.xmlHttpRequest
// ==/UserScript==
(function() {
'use strict';
//Переменные для настройки скрипта (остальные задаются в стилях HTML-страницы)
var setLenVal = 3 //Минимальное кол-во символов для запроса подсказок
var setColorSel = "#ccc7" //Цвет выделенного пункта
//Ниже - собственно рабочий код
var ro = new Object();
ro.method = "GET";
ro.onload = receiveYaSuggest;
var srch = document.getElementById('srch');
var rys = document.getElementById('rys');
var frmsrch = document.getElementById('frmsrch');
rys.style.left = srch.getBoundingClientRect().left + window.scrollX;
rys.style.top = srch.getBoundingClientRect().bottom + window.scrollY;
rys.style.width = srch.getBoundingClientRect().width;
srch.addEventListener('keyup', getYaSuggest, false);
srch.addEventListener('focus', getYaSuggest, false);
srch.addEventListener('mousedown', getYaSuggest, false);
srch.addEventListener('blur', function() {rys.style.display = "none"}, false);
srch.addEventListener('keydown', srchKeydown, false);
var i = 0;
var arrli = new Array();
function getYaSuggest(event) {
if ((event.key == 'ArrowDown') ||
(event.key == 'ArrowUp') ||
(event.key == 'ArrowRight') ||
(event.key == 'ArrowLeft') ||
(event.key == 'Escape') ||
(event.key == 'Enter')) {return};
if (srch.value.length >= setLenVal) {
ro.url = "https://suggest.yandex.ru/suggest-ya.cgi?part=" + srch.value;
GM.xmlHttpRequest(ro)
} else {rys.style.display = "none"};
}
function receiveYaSuggest(r)
{
var arr = r.responseText.substring( r.responseText.indexOf("[",16)+1, r.responseText.indexOf("]") ).replace(new RegExp("\"","g"),"").split(",");
if ((arr.length != 0) && (arr[0] != "")) {
rys.style.display = "block";
var arrs = "<ul class=\"rysul\" id=\"idrysul\">";
arr.forEach(function(item) {arrs = arrs + "<li class=\"rysli\">"+item+"</li>"});
arrs+="</ul>";
document.getElementById('rys').innerHTML = arrs;
arrli = Array.from( document.getElementById('idrysul').getElementsByTagName("li") );
arrli.forEach( function(item) {item.addEventListener('mousedown', function() {srch.value = this.innerText; frmsrch.submit()}, false)} );
arrli.forEach( function(item) {item.addEventListener('mouseenter', function() {i = arrli.indexOf(this); HlSel()}, false)} );
i=0;
} else {rys.style.display = "none"}
};
function srchKeydown(event) {
if (arrli.length == 0) {return};
if (event.key == 'ArrowDown') { i++; if (i>(arrli.length-1)) {i=0}; HlSel(); srch.value = arrli[i].innerText; };
if (event.key == 'ArrowUp') { i--; if (i<0) {i=arrli.length-1}; HlSel(); srch.value = arrli[i].innerText; };
if (event.key == 'Enter') { rys.style.display = "none" };
if (event.key == 'Escape') { rys.style.display = "none" };
}
function HlSel() {
if (arrli.length == 0) {return};
arrli.forEach( function(item) {item.style.backgroundColor = "transparent"} );
arrli[i].style.backgroundColor = setColorSel;
}
})();
Но при попытке перенести его в обычный файл js он перестаёт работать. Я знаю что в нём используется GM.xmlHttpRequest и при копировании заменил его на XMLHttpRequest. Но ничего не происходит.
Вот HTML-код для которого я использую скрипт:
<head>
<!-- Для выпадающих подсказок -->
<style type="text/css">
.rysul {list-style-type:none; margin:0px}
.rysli {margin-left:-40px; padding-left:10px; padding-top:5px; padding-bottom:6px; cursor:pointer; font:16px sans-serif}
</style>
<!-- /Для выпадающих подсказок -->
...
</head>
...
<form action="https://yandex.ru/search" id="frmsrch" style="font-size:16; border-radius:10px; padding:10; background:yellow">
<b>Поиск в <font color="red">Я</font>ндексе:</b>
<input id="srch" name="text" size="140" style="font-size:16"/>
</form>
<div id="rys" style="position:absolute; display:none; border:1px solid black; background-color:#FFFBF0"></div>
Я не могу использовать это расширение потому что планирую сделать из этого полноценную страницу новой вкладки для браузера. Может ли кто-нибудь мне помочь?
Ответы (2 шт):
Я могу ошибаться, но мне кажется, что это невозможно, по крайней мере без использования дополнительных средств. Браузер не даст вам выполнить cross-origin AJAX запрос, если сервер, к которому вы обращаетесь, не будет включать в ответ CORS-заголовки (а он не будет этого делать, потому что ему это не надо, чтобы к нему обращались всякие сторонние скрипты).
В отличие от XMLHttpRequest, запросы, выполняемые с помощью GM.xmlHttpRequest, не ограничены рамками т.н. same-origin политики, о чём в явном виде сказано в первом же абзаце документации:
This method performs a similar function to the standard
XMLHttpRequestobject, but allows these requests to cross the same origin policy boundaries.
Можно где-то поднять свой собственный сервер, который будет проксировать запросы к яндексу и самостоятельно добавлять к его ответу CORS-заголовки перед тем, как отдавать ответ запрашивающей стороне (это один из примеров того, что я назвал "дополнительными средствами"). Другие способы лично мне неизвестны, но может быть кто-нибудь ещё подскажет какое-то альтернативное решение.
Как подсказали в комментариях, специально для таких случаев существуют специальные CORS прокси сервисы. Вот небольшой перечень тех, которые удалось найти в первых результатах выдачи поисковиков (в порядке убывания "выносливости" по результатам короткого тестирования):
Вот что у меня получилось сделать с вашим скриптом:
document.addEventListener('DOMContentLoaded', function() {
'use strict';
// Переменные для настройки скрипта (остальные задаются в стилях HTML-страницы)
var setLenVal = 3 // Минимальное кол-во символов для запроса подсказок
var setColorSel = "#ccc7" // Цвет выделенного пункта
var debounceDelay = 500; // 1/2 секунды задержки между окончанием ввода и запросом
// CORS прокси с поддержкой fallback
var corsProxies = [
"https://corsproxy.io/?",
"https://cors-anywhere.com/",
"https://api.allorigins.win/raw?url=",
];
var currentProxyIndex = 0;
var lastSuccessfulProxy = 0;
var attemptCount = 0;
var debounceTimeout = null;
var ro = null;
var currentQuery = "";
var currentSrch = "";
var srch = document.getElementById('srch');
var rys = document.getElementById('rys');
var frmsrch = document.getElementById('frmsrch');
rys.style.left = srch.getBoundingClientRect().left + window.scrollX;
rys.style.top = srch.getBoundingClientRect().bottom + window.scrollY;
rys.style.width = srch.getBoundingClientRect().width;
srch.addEventListener('keyup', getYaSuggest, false);
srch.addEventListener('focus', showYaSuggest, false);
srch.addEventListener('mousedown', getYaSuggest, false);
srch.addEventListener('blur', function() {rys.style.display = "none"}, false);
srch.addEventListener('keydown', srchKeydown, false);
var i;
var arrli = new Array();
// Для вывода proxy hostname в консольных логах
function getProxyHostname(proxyIndex) {
return new URL(corsProxies[proxyIndex]).hostname;
}
function getYaSuggest(event) {
if (srch.value == currentSrch) { return }; // Если строка ввода не поменялась в результате нажатия клавиши
currentSrch = srch.value;
// Отменяем предыдущий таймер
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
if (currentSrch.length >= setLenVal) {
// Откладываем запрос на debounceDelay миллисекунд
debounceTimeout = setTimeout(function() {
currentQuery = currentSrch;
// Начинаем с последнего успешного прокси
currentProxyIndex = lastSuccessfulProxy;
attemptCount = 0;
makeRequest();
}, debounceDelay);
} else {
rys.style.display = "none";
};
}
function showYaSuggest(event) {
if (arrli.length && (rys.style.display == 'none')) {
if (i >= 0) { arrli[i].style.backgroundColor = "transparent"; i = -1; }
rys.style.display = 'block';
}
}
function makeRequest() {
// Отменяем предыдущий запрос, если он еще выполняется
if (ro) {
ro.abort();
}
ro = new XMLHttpRequest();
ro.timeout = 5000; // 5 секунд таймаут
var targetUrl = "https://suggest.yandex.ru/suggest-ya.cgi?part=" + encodeURIComponent(currentQuery);
var proxyUrl = corsProxies[currentProxyIndex] + targetUrl;
ro.open("GET", proxyUrl, true);
ro.onload = function() {
if (ro.status === 200) {
lastSuccessfulProxy = currentProxyIndex; // Запоминаем успешный прокси
receiveYaSuggest();
} else {
console.log('Proxy ' + getProxyHostname(currentProxyIndex) + ' failed with status:', ro.status);
tryNextProxy();
}
};
ro.onerror = function() {
console.log('Proxy ' + getProxyHostname(currentProxyIndex) + ' network error');
tryNextProxy();
};
ro.ontimeout = function() {
console.log('Proxy ' + getProxyHostname(currentProxyIndex) + ' timeout');
tryNextProxy();
};
ro.send();
}
function tryNextProxy() {
attemptCount++;
if (attemptCount < corsProxies.length) {
currentProxyIndex = (currentProxyIndex + 1) % corsProxies.length;
console.log('Trying fallback proxy ' + getProxyHostname(currentProxyIndex) + ' (attempt ' + (attemptCount + 1) + ')');
makeRequest();
} else {
console.log('All proxies failed after ' + corsProxies.length + ' attempts');
rys.style.display = "none";
}
}
function receiveYaSuggest()
{
if (!ro.responseText) {
rys.style.display = "none";
return;
}
try {
var arr = ro.responseText.substring( ro.responseText.indexOf("[",16)+1, ro.responseText.indexOf("]") ).replace(new RegExp("\"","g"),"").split(",");
if ((arr.length != 0) && (arr[0] != "")) {
var arrs = "<ul class=\"rysul\" id=\"idrysul\">";
arr.forEach(function(item) {arrs = arrs + "<li class=\"rysli\">"+item+"</li>"});
arrs+="</ul>";
document.getElementById('rys').innerHTML = arrs;
rys.style.display = "block";
arrli = Array.from( document.getElementById('idrysul').getElementsByTagName("li") );
arrli.forEach( function(item) {item.addEventListener('mousedown', function() {srch.value = this.innerText; frmsrch.submit()}, false)} );
arrli.forEach( function(item) {item.addEventListener('mouseenter', function() {i = arrli.indexOf(this); HlSel()}, false)} );
i=-1;
} else {
rys.style.display = "none";
}
} catch(e) {
console.log('Error parsing suggestions:', e);
rys.style.display = "none";
}
}
function srchKeydown(event) {
if (arrli.length == 0) {return};
if (rys.style.display == 'block') {
if (event.key == 'ArrowDown') { i++; if (i>(arrli.length-1)) {i=0}; HlSel(); srch.value = arrli[i].innerText; currentSrch = arrli[i].innerText; };
if (event.key == 'ArrowUp') { i--; if (i<0) {i=arrli.length-1}; HlSel(); srch.value = arrli[i].innerText; currentSrch = arrli[i].innerText; };
if ((event.key == 'Enter') || (event.key == 'Escape')) { rys.style.display = "none" };
} else if (arrli.length && (event.key == 'ArrowDown')) {
// Показать выпадающий список заново
if (i >= 0) { arrli[i].style.backgroundColor = "transparent"; i = -1; }
rys.style.display = 'block';
}
}
function HlSel() {
if (arrli.length == 0) {return};
arrli.forEach( function(item) {item.style.backgroundColor = "transparent"} );
if (i >= 0) { arrli[i].style.backgroundColor = setColorSel; }
}
});
.rysul {list-style-type:none; margin:0px}
.rysli {margin-left:-40px; padding-left:10px; padding-top:5px; padding-bottom:6px; cursor:pointer; font:16px sans-serif}
<form action="https://yandex.ru/search" id="frmsrch" style="font-size:16; border-radius:10px; padding:10; background:yellow">
<b>Поиск в <font color="red">Я</font>ндексе:</b>
<input id="srch" name="text" size="140" style="font-size:16"/>
</form>
<div id="rys" style="position:absolute; display:none; border:1px solid black; background-color:#FFFBF0"></div>
Скрипт поддерживает список из нескольких прокси, и в случае отказа одного из них переключается на следующий.
Для того, чтобы не перегружать прокси-сервер, сделана проверка, изменилась ли строка ввода в результате нажатия очередной клавиши (по событию keyup). Кроме того, между окончанием ввода и выполнением запроса предусмотрена задержка в 500 мс (по английски это называется debouncing, настраивается с помощью переменной debounceDelay).
Помимо этого, я внёс небольшие изменения в поведение UI, самое значительное из которых - повторное открытие закрытого списка подсказок по нажатию клавиши ↓ или по событию focus.
Обратите внимание, что главная функция вызывается по событию DOMContentLoaded. Пользовательские скрипты Greasemonkey/Tampermonkey тоже обычно запускаются по этому событию. Хотя для одной HTML страницы вы можете просто поместить содержимое этой функции внутрь тегов <script>...</script>, главное, чтобы оно находилось ниже DOM-элементов страницы, которые в ней используются.
По просьбам трудящихся… Прошу прощения, но крайне редко меня сюда заносит последнее время. Итак — минимальная версия "newtab" для демонстрации того, что при желании можно грабить корованы прямо с глагны.
Создаём три файла: manifest.json newtab.html newtab.js
Микро-манифест, где показано подключение обычной html-странички. Где параметром который нам разрешает доступ наружу — host_permissions (можно ограничить любым сайтом/сайтами в соответствии с перечисленными шаблонами) — я разрешил "всё", чтобы было с чем поиграть.
{
"manifest_version": 3,
"name": "#mini-newtab",
"version": "0.0.1",
"chrome_url_overrides": {
"newtab": "newtab.html"
},
"host_permissions": ["<all_urls>"]
}
Сама страничка. Обращаю внимание на ВАЖНЫЙ момент — в расширениях для браузера весь js должен быть подключаемым из файла. Любой другой фокус с попыткой добавить его в код будет плеваться ошибками.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>#</title>
<script src="newtab.js" defer></script>
</head>
<body>
<input type="text" id="fetchUrl">
<input type="button" value="Fetch" id="buttonFetch">
</body>
</html>
Тот самый js из файла, который будет грабить указанные нами адреса и выводить их в консоль.
const url = document.getElementById('fetchUrl');
const button = document.getElementById('buttonFetch');
button.addEventListener('click', async () => {
const myUrl = url.value;
const response = await fetch(myUrl)
if (response.ok) {
const data = await response.text()
console.log(data);
} else {
alert('Что-то пошло не так!');
}
});
- Запихиваем все три файла в одну папку.
- Заходим в Хром на страницу расширений и включаем режим разработчика.
- Устанавливаем расширение (натравив на нашу папку).
- Открываем новую страницу. С высокой вероятностью при первом запуске всплывёт предупреждение о том, что вы меняете
newtabи поинтересуется вашей уверенности в себе и дальнейших действиях.)
Собственно вся суть написанного мной расширения сводится к тому, что вы можете fetch-ить любую доступную страницу или небинарный файл из тырнета и получать ввывод содержимого в консоль.
Andrew_Sor, завязывай с XMLHttpRequest. "Наркотики — это плохо.)" Слишком он древний во всех смыслах.