Ранее мы уже рассказывали, как создать расширение, убирающее UTM-ссылки при копировании, и как создать расширение, убирающее Shorts на YouTube, — кто читал, не даст соврать, насколько сильно может упростить жизнь пара минут, уделенная воспроизведению примеров из наших гайдов. Сегодня же мы расскажем о том, как с помощью браузерных расширений копировать с сайтов, где включена защита от копирования. Погнали!
Два важных момента
Сразу уточним, что, хоть слог статьи и пронизан гайдовым вайбом, расписывать каждый шаг и объяснять, что именно вы в нем делаете, мы не будем — банально потому, что в таком случае базовая инфа из этой статьи попросту раз за разом дублировалась бы. Посему изучение «пары минут ликбеза», если вы еще не читали, оставляем на откуп вам. Там прям азы для тех, кто впервые создает собственное расширение. Если вы не читали ликбез, но уже создавали расширения — можете скипнуть.
Также немаловажно обратить внимание на потенциальный шанс появления багов/лагов, который будет возрастать пропорционально «возрасту» статьи. Иными словами, если вы читаете ее в 2030-м и что-то не работает — такая Life. Связано это с тем, что защита от копирования может быть как типичной, так и кастомной. И хотя на момент написания статьи найти сайт, где что-то не получилось бы скопировать, автору не удалось — теоретически он может существовать. Но 99,9% будут копироваться.
Как создать для обхода антикопи
А теперь перейдем, собственно, к созданию. Состоять наше расширение будет из следующих файлов:
manifest.json — собственно, манифест, обязательный для абсолютно любого браузерного расширения (справедливости ради, изначально — у любого chromium-браузерного расширения, но в наши дни уже даже Apple начал использовать манифесты). Если не понимаете, о чем речь, — вам на два абзаца выше, туда, где мы давали ссылку на базовую инфу.
content.js — основная логика расширения, где, собственно, будут отрабатываться 99,9% современных способов защитить текст от копирования и использовать соответствующее противодействие.
popup.html — простенький элемент интерфейса, для возможности удобного отключения/подключения обхода защиты от копирования на тех или иных сайтах. Можно было бы и без него, но тогда был бы риск, что расширение нарушит работу других расширений или скриптов на сайтах. А так, все опционально. Надо — включили. Не надо — выключили.
popup.js — логика описанной выше включалки/выключалки.
Как показать в картинке, что нажатие ПКМ не открывает контекстное меню, автор не знает — поэтому просто поверьте, что там есть защита :D
Собственно, начнем:
1. Создаем папку.
2. В ней создаем 4 текстовых файла.
3. Первый файл обзываем manifest.json
4. В него копируем:
{
"manifest_version": 3,
"name": "Enable Text Copy",
"version": "1.0",
"description": "Разрешает выделение и копирование текста на защищенных страницах с управлением по сайтам.",
"permissions": [
"storage",
"tabs"
],
"action": {
"default_popup": "popup.html",
"default_title": "Управление копированием текста"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_start",
"all_frames": true
}
]
}
5. Сохраняем
6. Второй файл обзываем content.js
7. В него копируем:
(function () {
function getCurrentHostname() {
try {
return location.hostname || "";
} catch (error) {
return "";
}
}
function createUnblockStyle() {
const styleElement = document.createElement("style");
const cssText = `
* {
user-select: text !important;
-webkit-user-select: text !important;
-moz-user-select: text !important;
-ms-user-select: text !important;
}
*::selection {
background: rgba(30, 144, 255, 0.25) !important;
}
`;
styleElement.textContent = cssText;
return styleElement;
}
function injectStyle() {
const styleNode = createUnblockStyle();
document.documentElement.appendChild(styleNode);
}
function attachEventBlockers() {
const eventList = [
"selectstart",
"mousedown",
"mouseup",
"copy",
"contextmenu",
"keydown",
"dragstart"
];
function stopHandler(event) {
try {
event.stopPropagation();
} catch (error) {}
}
for (let i = 0; i < eventList.length; i++) {
document.addEventListener(eventList[i], stopHandler, true);
}
}
function activateCopyFreedom() {
injectStyle();
attachEventBlockers();
}
function loadSettingsAndRun() {
const hostname = getCurrentHostname();
chrome.storage.sync.get(["enabledSites"], function (result) {
const sites = Array.isArray(result.enabledSites)
? result.enabledSites
: [];
let allowed = false;
for (let i = 0; i < sites.length; i++) {
if (sites[i] === hostname) {
allowed = true;
break;
}
}
if (!allowed) {
return;
}
activateCopyFreedom();
});
}
loadSettingsAndRun();
})();
8. Сохраняем
9. Третий файл обзываем popup.html
10. В него копируем:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body style="font-family: sans-serif; width: 320px; padding: 12px;">
<h2 style="margin: 0 0 10px 0;">Управление сайтами</h2>
<div style="margin-bottom: 10px; font-size: 12px; opacity: 0.75;">
Здесь можно включать или отключать разрешение копирования текста для конкретных сайтов.
</div>
<div id="currentSiteBox" style="margin-bottom: 10px; padding: 8px; border: 1px solid #ddd;">
<div style="font-size: 12px;">Текущий сайт:</div>
<div id="currentSiteText" style="font-weight: bold;"></div>
<button id="toggleCurrentSiteButton" style="margin-top: 6px; width: 100%; padding: 6px;"></button>
</div>
<div style="margin-bottom: 6px; font-size: 13px;">
Список разрешенных сайтов:
</div>
<div id="sitesList" style="border: 1px solid #ddd; padding: 6px; max-height: 160px; overflow-y: auto;"></div>
<button id="addCurrentSiteButton" style="margin-top: 10px; width: 100%; padding: 6px;">
Добавить текущий сайт
</button>
<script src="popup.js"></script>
</body>
</html>
11. Сохраняем
12. Четвертый файл обзываем popup.js
13. В него копируем:
const sitesListElement = document.getElementById("sitesList");
const currentSiteTextElement = document.getElementById("currentSiteText");
const toggleCurrentSiteButton = document.getElementById("toggleCurrentSiteButton");
const addCurrentSiteButton = document.getElementById("addCurrentSiteButton");
let currentHostname = "";
let enabledSites = [];
function getCurrentTab() {
return new Promise((resolve) => {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
resolve(tabs && tabs[0] ? tabs[0] : null);
});
});
}
function extractHostname(url) {
try {
return new URL(url).hostname;
} catch (error) {
return "";
}
}
function loadEnabledSites() {
return new Promise((resolve) => {
chrome.storage.sync.get(["enabledSites"], function (result) {
resolve(Array.isArray(result.enabledSites) ? result.enabledSites : []);
});
});
}
function saveEnabledSites(list) {
return new Promise((resolve) => {
chrome.storage.sync.set({ enabledSites: list }, function () {
resolve();
});
});
}
function isSiteEnabled(hostname) {
return enabledSites.includes(hostname);
}
function renderSitesList() {
sitesListElement.innerHTML = "";
if (enabledSites.length === 0) {
sitesListElement.innerHTML = "<div style='font-size:12px; opacity:0.6;'>Список пуст</div>";
return;
}
for (let i = 0; i < enabledSites.length; i++) {
const site = enabledSites[i];
const row = document.createElement("div");
row.style.display = "flex";
row.style.justifyContent = "space-between";
row.style.marginBottom = "6px";
row.style.padding = "4px";
row.style.borderBottom = "1px solid #eee";
const name = document.createElement("div");
name.textContent = site;
name.style.fontSize = "12px";
const removeButton = document.createElement("button");
removeButton.textContent = "Удалить";
removeButton.style.fontSize = "11px";
removeButton.onclick = async function () {
const updated = enabledSites.filter(function (s) {
return s !== site;
});
enabledSites = updated;
await saveEnabledSites(updated);
renderSitesList();
updateCurrentSiteUI();
};
row.appendChild(name);
row.appendChild(removeButton);
sitesListElement.appendChild(row);
}
}
function updateCurrentSiteUI() {
currentSiteTextElement.textContent = currentHostname;
if (isSiteEnabled(currentHostname)) {
toggleCurrentSiteButton.textContent = "Выключить на этом сайте";
} else {
toggleCurrentSiteButton.textContent = "Включить на этом сайте";
}
}
function attachCurrentSiteToggle() {
toggleCurrentSiteButton.onclick = async function () {
if (isSiteEnabled(currentHostname)) {
enabledSites = enabledSites.filter(function (s) {
return s !== currentHostname;
});
} else {
enabledSites.push(currentHostname);
}
await saveEnabledSites(enabledSites);
renderSitesList();
updateCurrentSiteUI();
};
}
function attachAddCurrentSite() {
addCurrentSiteButton.onclick = async function () {
if (!currentHostname) return;
if (!isSiteEnabled(currentHostname)) {
enabledSites.push(currentHostname);
await saveEnabledSites(enabledSites);
}
renderSitesList();
updateCurrentSiteUI();
};
}
async function init() {
const tab = await getCurrentTab();
if (!tab) return;
currentHostname = extractHostname(tab.url);
enabledSites = await loadEnabledSites();
renderSitesList();
updateCurrentSiteUI();
attachCurrentSiteToggle();
attachAddCurrentSite();
}
document.addEventListener("DOMContentLoaded", init);
14. Сохраняем.
15. Открывает браузер, ищем «три точки справа вверху».
16. Затем тыкаем на «Расширения».
17. Далее на «Управления расширениями».
18. После чего находим «Загрузить распакованное расширение».
19. Тыкаем туда и выбираем созданную вначале папку.
20. Тестируем.
Для примеров отлично подходит сайт онлайн-библиотеки «Литнет». Но в целом сгодится любой сайт с защитой от копирования. Также с помощью расширения можно тестировать эффективность защиты на своем сайте.
Все работает, а сайт корректно запоминается.
Саммари
Как видите, обходить защиту сайтов от копирования текстов и спокойно копипастить любую интересующую вас инфу без использования режима разработчика или превращения страницы в PDF, не так уж и сложно. И можете быть уверены, это далеко не последнее QOF-расширение, упрощающее жизнь, процессом создания которого мы готовы поделиться. Посему подписывайтесь на нашу Телегу — новые гайды выйдут уже совсем скоро!