Как навесить на видео в GetCourse водяной знак и спать спокойно
Как включить видеозащиту без увеличения занимаемого размера видео в два раза на GETcourse подходит даже для внешних видео (Youtube, vimeo, VK)
Знаете, что самое обидное? Ты пилишь контент — по 20 дублей, по 3 часа заливки, под музыку с барабанами, чтоб всё красиво. А потом какой-то Вася скачивает урок и сливает его в левый канал «Бесплатные курсы для бедных душ». Без стыда. Без совести. Без подписки на рассылку.
А теперь представьте: 💦 Водяной знак. На видео. Прям в плеере.
С именем, ID, номером паспорта и, если хочешь, группой крови ученика. Чтобы если кто и слил — то только один раз. А потом краснел в углу и больше не баловался.
В общем, мы сделали простую настройку, которая позволяет:
автоматически добавлять водяной знак прямо поверх видео в GetCourse;
не увеличивать вес файла в два раза (потому что не перерендер);
не писать программистам за 10к, мол, "а можно как-то защититься, ну пожааалуйста".
🎯 Как работает? Очень просто. Заходишь в урок, втыкаешь небольшой блок с кодом (не паникуем, он готовый), и вуаля — при просмотре каждый ученик видит свой уникальный водяной знак. Имя, ID, любимое блюдо — всё, что укажешь. Накладывается сверху, полупрозрачно, но заметно. Чтобы даже если кто и записал экран, потом сам себе боялся в глаза смотреть.
⚠️ А если урок откроет админ или редактор? Ничего не сломается — скрипт умеет определять, кто ты и зачем сюда залез. Так что в режиме редактирования всё по-прежнему красиво и без налёта спецслужб.
💰 Цена вопроса: 0 рублей и 10 минут времени.
Зато минус головная боль, минус сливов, плюс спокойствие на душе и в отчётах перед заказчиком. Если ты продюсер, автор, или просто хочешь чувствовать, что твой контент не утекает как вода в дырявое ведро — сделай себе этот апгрейд. Сегодня. Сейчас. Пока кто-то из учеников не решил, что ты — его личный Netflix по подписке "на халяву".
И так, инструкция:
Вам потребуется выполнить всего несколько простых шагов:
Зайти в урок в режиме Редактирования урока.
Добавить внизу урока блок JavaScript: + Добавить блок -> Показать все блоки -> Вставка -> Javascript-код
Далее нажимаем на появившейся блок и вставляем код из блока ниже
Сохраняем
Наводим на Блок:Javascript-код, нажимаем стрелочку вниз, в выпадающем меню выбираем пункт Настройки
Ставим галочку напротив "Заменять переменные пользователя"
Снова сохраняем, выходим из режима Редактировани
И вуа-ля - ваши водяные знаки появились на видео
(function () {
// --- ПРОВЕРКА НА РЕЖИМ РЕДАКТИРОВАНИЯ GETCOURSE ---
try {
const currentUrl = new URL(window.location.href);
// Проверяем наличие параметра 'editMode' и его значение не равно '0' (0 - обычно режим предпросмотра)
// Либо можно проверять на наличие параметра controlMode=1 (часто используется в GC для админки)
const isEditMode =
(currentUrl.searchParams.has("editMode") &&
currentUrl.searchParams.get("editMode") !== "0") ||
currentUrl.searchParams.has("controlMode"); // Добавим проверку на controlMode
if (isEditMode) {
console.log(
"GetCourse edit/control mode detected. Watermarking script skipped."
);
return; // Полностью прекращаем выполнение скрипта в режиме редактирования/управления
}
} catch (e) {
console.error("Error checking URL for edit mode:", e);
// Если не удалось проверить URL, на всякий случай можно тоже выйти,
// либо продолжить выполнение, если вы уверены, что ошибка не критична.
// return; // Раскомментируйте, если хотите останавливаться при ошибке проверки URL
}
// --- КОНЕЦ ПРОВЕРКИ ---
// --- НАСТРОЙКИ ---
const config = {
userId: "{uid}",
userName: "{real_name}",
watermarkText: (id) => `ID: {id}`, // в каком формате будет отображаться водяной знак
selectors: [
".lt-video",
".lt-video-hosting-with-defence",
".vhi-root",
".video-container",
".player-container",
"video",
'iframe[id^="vhplayeriframe-"]',
'iframe[src*="getcourse.ru/sign-player"]',
'iframe[src*="youtube.com"]',
'iframe[src*="vimeo.com"]',
],
// ... остальные настройки ...
watermarkPositions: [
{ top: "15%", left: "10%" },
{ top: "35%", left: "75%" },
{ top: "75%", left: "20%" },
{ top: "85%", left: "65%" },
{ top: "50%", left: "50%" },
],
watermarkStyle: {
position: "absolute",
color: "rgba(255, 255, 255, 0.1)",
fontSize: "12px",
fontWeight: "normal",
padding: "2px 4px",
textShadow: "1px 1px 1px rgba(0,0,0,0.2)",
userSelect: "none",
pointerEvents: "none",
whiteSpace: "nowrap",
transformOrigin: "center center",
},
gridStyle: {
backgroundImage: `repeating-linear-gradient(45deg, rgba(255,255,255,0.02), rgba(255,255,255,0.02) 60px, transparent 60px, transparent 120px)`,
pointerEvents: "none",
position: "absolute",
top: "0",
left: "0",
width: "100%",
height: "100%",
},
overlayTargetClassName: "watermark-target-container",
overlayClassName: "watermark-overlay",
watermarkItemClassName: "watermark-text-item",
autoWrapperClassName: "auto-watermark-wrapper",
processedAttribute: "data-watermarked",
styleTagId: "inline-watermark-styles",
};
// --- ВЫВОД ПРЕДУПРЕЖДЕНИЯ В КОНСОЛЬ ---
// Определяем стили для заголовка и основного текста
const headlineStyle = [
"color: red;",
"font-size: 4.5em;", // Крупный размер шрифта
"font-weight: bold;",
"text-shadow: 1px 1px 1px rgba(0,0,0,0.3);", // Небольшая тень для объема
].join(" "); // Собираем стили в одну строку
const bodyStyle = [
"font-size: 1.2em;", // Чуть крупнее стандартного
"line-height: 1.5;", // Увеличим межстрочный интервал
].join(" ");
// Сам вывод в консоль
try {
// Обернем в try...catch на случай, если какой-то браузер не поддерживает стилизацию
console.log(
"%cВНИМАНИЕ " + (config?.userName.toUpperCase() || "ПОЛЬЗОВАТЕЛЬ") + "!",
headlineStyle
); // %c - маркер применения стиля
console.log(
"%cЭта функция браузера предназначена в первую очередь для разработчиков.",
bodyStyle
);
console.log(
'%cЕсли кто-то попросил вас скопировать и вставить сюда какой-либо код для получения доступа к видео или "взлома" системы – это мошенничество!',
bodyStyle
);
console.log(
"%cЛюбые действия в консоли, направленные на обход системы защиты или несанкционированное копирование материалов, могут быть зафиксированы и привести к блокировке вашего доступа, а также штрафам в соответствие с условиями Оферты .",
bodyStyle
);
// Можно добавить ID пользователя для усиления эффекта, если он доступен на этом этапе
console.log(
"%cОтправляем информацию о вашей попытке взлома Администрации...",
bodyStyle + " color: green;"
);
} catch (e) {
// Просто игнорируем ошибку, если стилизация не сработала
}
// --- КОНЕЦ ПРЕДУПРЕЖДЕНИЯ ---
// Добавим вывод ID пользователя в консоль
if (config && config.userId) {
console.log(
"%cВаш ID пользователя в системе: " + config.userId,
bodyStyle + " color: orange;"
); // Выделим ID
}
// --- ВНЕДРЕНИЕ СТИЛЕЙ ---
function injectStyles() {
// ... код injectStyles ...
if (document.getElementById(config.styleTagId)) return;
const style = document.createElement("style");
style.id = config.styleTagId;
style.textContent = `
.${config.overlayTargetClassName} { position: relative; overflow: hidden; }
.${config.overlayClassName} { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 99; overflow: hidden; }
.${config.autoWrapperClassName} { position: relative; display: inline-block; vertical-align: top; line-height: 0; }
.${config.autoWrapperClassName} > video, .${config.autoWrapperClassName} > iframe { display: block; max-width: 100%; }
`;
document.head.appendChild(style);
}
// --- СОЗДАНИЕ И ДОБАВЛЕНИЕ ОВЕРЛЕЯ ---
function addWatermarkOverlay(targetContainer) {
// ... код addWatermarkOverlay ...
if (
!targetContainer ||
targetContainer.hasAttribute(config.processedAttribute)
)
return;
if (!targetContainer.classList.contains(config.autoWrapperClassName)) {
const currentPosition = window.getComputedStyle(targetContainer).position;
if (currentPosition === "static")
targetContainer.style.position = "relative";
targetContainer.classList.add(config.overlayTargetClassName);
}
const overlay = document.createElement("div");
overlay.className = config.overlayClassName;
if (config.gridStyle && Object.keys(config.gridStyle).length > 0) {
const grid = document.createElement("div");
Object.assign(grid.style, config.gridStyle);
overlay.appendChild(grid);
}
const userIdText = config.watermarkText(config.userId);
config.watermarkPositions.forEach((pos) => {
const watermark = document.createElement("div");
watermark.className = config.watermarkItemClassName;
watermark.textContent = userIdText;
Object.assign(watermark.style, config.watermarkStyle);
watermark.style.top = pos.top;
watermark.style.left = pos.left;
const angle = Math.random() * 20 - 10;
watermark.style.transform = `rotate(${angle}deg)`;
overlay.appendChild(watermark);
});
targetContainer.appendChild(overlay);
targetContainer.setAttribute(config.processedAttribute, "true");
}
// --- ПОИСК И ОБРАБОТКА ЦЕЛЕВЫХ ЭЛЕМЕНТОВ ---
function applyWatermarks() {
// ... код applyWatermarks ...
const elements = document.querySelectorAll(config.selectors.join(", "));
elements.forEach((el) => {
if (
el.hasAttribute(config.processedAttribute) ||
el.closest(`[${config.processedAttribute}]`)
)
return;
let container = null;
if (el.matches("video, iframe")) {
const existingContainer = el.closest(
config.selectors.filter((s) => !s.match(/^(video|iframe)/)).join(", ")
);
if (
existingContainer &&
window.getComputedStyle(existingContainer).position !== "static"
) {
container = existingContainer;
} else {
if (
el.parentElement &&
!el.parentElement.classList.contains(config.autoWrapperClassName)
) {
const wrapper = document.createElement("div");
wrapper.className = config.autoWrapperClassName;
el.parentNode.insertBefore(wrapper, el);
wrapper.appendChild(el);
container = wrapper;
} else if (
el.parentElement?.classList.contains(config.autoWrapperClassName)
) {
container = el.parentElement;
}
}
} else {
container = el;
}
if (container) {
const hasMedia = container.querySelector("video, iframe");
const isMediaWrapper = container.classList.contains(
config.autoWrapperClassName
);
if (hasMedia || isMediaWrapper) {
// console.log('Attempting to add overlay to:', container); // Убрали логи для чистоты
addWatermarkOverlay(container);
}
}
});
}
// --- ИНИЦИАЛИЗАЦИЯ И НАБЛЮДЕНИЕ ЗА DOM ---
let observerTimeout;
const debouncedApplyWatermarks = () => {
clearTimeout(observerTimeout);
observerTimeout = setTimeout(applyWatermarks, 250);
};
injectStyles();
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", applyWatermarks);
} else {
applyWatermarks();
}
window.addEventListener("load", applyWatermarks);
const observer = new MutationObserver((mutations) => {
let needsUpdate = false;
for (const mutation of mutations) {
if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (
(node.matches(config.selectors.join(", ")) ||
node.querySelector(config.selectors.join(", "))) &&
!node.closest(`[${config.processedAttribute}]`)
) {
needsUpdate = true;
break;
}
}
}
}
if (needsUpdate) break;
}
if (needsUpdate) {
debouncedApplyWatermarks();
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();
Статья-разбор + шаблоны автоворонок, которые работают в 2025 году
Как набрать 1000 подписчиков
в ваш блог с помощью простой автоворонки?
Привлекайте подписчиков, которые готовы покупать, и превращайте трафик в прибыльную аудиторию без слива бюджета!
Бонусом вы также получите вот такую надпись, если пользователь попытается открыть Панель Разработчика:
Разумеется, это просто, так называемая, защита от "дурака". И профессиональных "воришек" вашей интеллектуальной собственности это не отпугнет. Но 99% обычных пользователей точно задумаются