Как навесить на видео в GetCourse водяной знак и спать спокойно
Как включить видеозащиту без увеличения занимаемого размера видео в два раза на GETcourse
подходит даже для внешних видео (Youtube, vimeo, VK)
Знаете, что самое обидное?
Ты пилишь контент — по 20 дублей, по 3 часа заливки, под музыку с барабанами, чтоб всё красиво. А потом какой-то Вася скачивает урок и сливает его в левый канал «Бесплатные курсы для бедных душ». Без стыда. Без совести. Без подписки на рассылку.

А теперь представьте:
💦 Водяной знак.
На видео. Прям в плеере.

С именем, ID, номером паспорта и, если хочешь, группой крови ученика. Чтобы если кто и слил — то только один раз. А потом краснел в углу и больше не баловался.

В общем, мы сделали простую настройку, которая позволяет:
  • автоматически добавлять водяной знак прямо поверх видео в GetCourse;
  • не увеличивать вес файла в два раза (потому что не перерендер);
  • не писать программистам за 10к, мол, "а можно как-то защититься, ну пожааалуйста".

🎯 Как работает?
Очень просто. Заходишь в урок, втыкаешь небольшой блок с кодом (не паникуем, он готовый), и вуаля — при просмотре каждый ученик видит свой уникальный водяной знак.
Имя, ID, любимое блюдо — всё, что укажешь. Накладывается сверху, полупрозрачно, но заметно. Чтобы даже если кто и записал экран, потом сам себе боялся в глаза смотреть.

⚠️ А если урок откроет админ или редактор?
Ничего не сломается — скрипт умеет определять, кто ты и зачем сюда залез. Так что в режиме редактирования всё по-прежнему красиво и без налёта спецслужб.

💰 Цена вопроса:
0 рублей и 10 минут времени.

Зато минус головная боль, минус сливов, плюс спокойствие на душе и в отчётах перед заказчиком.
Если ты продюсер, автор, или просто хочешь чувствовать, что твой контент не утекает как вода в дырявое ведро — сделай себе этот апгрейд.
Сегодня. Сейчас.
Пока кто-то из учеников не решил, что ты — его личный Netflix по подписке "на халяву".
И так, инструкция:
  1. Вам потребуется выполнить всего несколько простых шагов:
  2. Зайти в урок в режиме Редактирования урока.
  3. Добавить внизу урока блок JavaScript: + Добавить блок -> Показать все блоки -> Вставка -> Javascript-код
  4. Далее нажимаем на появившейся блок и вставляем код из блока ниже
  5. Сохраняем
  6. Наводим на Блок:Javascript-код, нажимаем стрелочку вниз, в выпадающем меню выбираем пункт Настройки
  7. Ставим галочку напротив "Заменять переменные пользователя"
  8. Снова сохраняем, выходим из режима Редактировани
  9. И вуа-ля - ваши водяные знаки появились на видео

(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% обычных пользователей точно задумаются