Freediumify Medium

Bypasses Medium paywall using Freedium

// ==UserScript==
// @name         Freediumify Medium
// @namespace    https://greasyfork.org/users/3786
// @version      20251016
// @description  Bypasses Medium paywall using Freedium
// @description:ru  Добавляет кнопку для чтения платных статей на medium.com через freedium
// @author       Maranchuk Sergey <[email protected]>
// @match        https://medium.com/*
// @match        https://*.medium.com/*
// @icon         https://icons.duckduckgo.com/ip2/freedium.cfd.ico
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  const BUTTON_ID = "freedium-button-container";
  const FREEDIUM_BASE_URL = "https://freedium-mirror.cfd/";
  const DEBOUNCE_DELAY = 1000;

  const SELECTORS = {
    jsonLd: 'script[type="application/ld+json"]',
    meteredContent: ".meteredContent",
  };

  function isMemberOnlyArticle() {
    // Check JSON-LD structured data first (most reliable)
    try {
      const jsonLdScript = document.querySelector(SELECTORS.jsonLd);
      if (jsonLdScript) {
        const data = JSON.parse(jsonLdScript.textContent);
        if (data.isAccessibleForFree === false) {
          return true;
        }
      }
    } catch (e) {}

    // Check for meteredContent class
    return !!document.querySelector(SELECTORS.meteredContent);
  }

  function createButton() {
    const button = document.createElement("div");
    button.id = BUTTON_ID;
    button.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            display: flex;
            align-items: center;
            gap: 8px;
            background: linear-gradient(135deg, #22C55E 0%, #16A34A 100%);
            color: white;
            padding: 12px 20px;
            border-radius: 12px;
            cursor: pointer;
            font-size: 15px;
            font-weight: 600;
            box-shadow: 0 4px 12px rgba(34, 197, 94, 0.3);
            transition: all 0.3s ease;
            z-index: 999999;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        `;

    const iconSvg = `
            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="white" viewBox="0 0 24 24">
                <path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/>
            </svg>
        `;
    button.innerHTML = iconSvg + "<span>Read Free</span>";

    button.addEventListener("mouseenter", function () {
      this.style.transform = "translateY(-2px) scale(1.05)";
      this.style.boxShadow = "0 6px 20px rgba(34, 197, 94, 0.4)";
    });

    button.addEventListener("mouseleave", function () {
      this.style.transform = "translateY(0) scale(1)";
      this.style.boxShadow = "0 4px 12px rgba(34, 197, 94, 0.3)";
    });

    button.addEventListener("click", function () {
      const freediumUrl = FREEDIUM_BASE_URL + window.location.href;
      window.open(freediumUrl, "_blank");
    });

    return button;
  }

  function addFreediumButton() {
    // Check if button already exists
    if (document.getElementById(BUTTON_ID)) {
      return;
    }

    // Check if this is a member-only article
    if (!isMemberOnlyArticle()) {
      return;
    }

    const button = createButton();
    document.body.appendChild(button);
  }

  function removeButton() {
    const button = document.getElementById(BUTTON_ID);
    if (button) {
      button.remove();
    }
  }

  // Initial check
  addFreediumButton();

  // Setup SPA navigation observer
  let currentUrl = window.location.href;
  let debounceTimer = null;

  const observer = new MutationObserver(() => {
    if (window.location.href !== currentUrl) {
      currentUrl = window.location.href;
      removeButton();

      clearTimeout(debounceTimer);
      debounceTimer = setTimeout(addFreediumButton, DEBOUNCE_DELAY);
    }
  });

  observer.observe(document, {
    childList: true,
    subtree: true,
  });
})();