Medium Member Bypass Modified

Modern Medium GUI with multiple bypass services.

От 20.02.2025. Виж последната версия.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Medium Member Bypass Modified
// @author       UniverseDev
// @license      GPL-3.0-or-later
// @namespace    http://tampermonkey.net/
// @version      13.9.8
// @description  Modern Medium GUI with multiple bypass services.
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(() => {
  'use strict';
  const CLASS_SETTINGS = 'medium-settings';
  const CLASS_NOTIFICATION = 'medium-notification';
  const SELECTOR_MEMBER_WALL_CHECK = 'div.s.u.w.fg.fh.q';
  const SELECTOR_FREEDIUM_CLOSE_BUTTON = '.close-button';
  const SELECTOR_SEARCH_BAR = 'div.ax.h';
  const SELECTOR_MEMBER_INDICATOR = 'div.bm > div > svg + p';
  const getSetting = (key, defaultValue) => GM_getValue(key, defaultValue);
  const setSetting = (key, value) => GM_setValue(key, value);
  const config = {
    bypassUrls: {
      freedium: 'https://freedium.cfd',
      readmedium: 'https://readmedium.com',
      libmedium: 'https://md.vern.cc/',
      archive: 'https://archive.is/newest/',
      archiveLi: 'https://archive.li/newest/',
      archiveVn: 'https://archive.vn/newest/',
      archivePh: 'https://archive.ph/newest/'
    },
    currentBypassIndex: getSetting('currentBypassIndex', 0),
    autoRedirectDelay: getSetting('redirectDelay', 5000),
    autoRedirectEnabled: getSetting('autoRedirect', true),
    darkModeEnabled: getSetting('darkModeEnabled', false),
    isBypassSession: getSetting('isBypassSession', false)
  };
  let bypassServiceKeys = Object.keys(config.bypassUrls);
  if (window.location.hostname !== 'medium.com') {
    bypassServiceKeys = bypassServiceKeys.filter(key => key !== 'libmedium');
  }
  let isRedirecting = false;
  let originalArticleUrl;
  let isSettingsVisible = false;
  const injectStyles = () => {
    const style = document.createElement('style');
    style.textContent = `
      .${CLASS_SETTINGS} {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 360px;
        background-color: var(--background-color, white);
        box-shadow: 0 4px 15px rgba(0,0,0,0.2);
        border-radius: 16px;
        font-family: Arial, sans-serif;
        z-index: 10000;
        padding: 20px;
        display: none;
        color: var(--text-color, #333);
        cursor: grab;
        user-select: none;
      }
      .${CLASS_SETTINGS}.dark {
        --background-color: #121212;
        --text-color: white;
      }
      .medium-settings-header {
        font-size: 22px;
        font-weight: bold;
        margin-bottom: 20px;
        text-align: center;
      }
      .medium-settings-toggle {
        margin: 15px 0;
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      .medium-settings-toggle > span { flex-grow: 1; }
      .medium-settings-input {
        margin-left: 10px;
        padding: 8px 10px;
        border: 1px solid #ccc;
        border-radius: 8px;
        box-sizing: border-box;
        color: #333;
        background-color: white;
      }
      .medium-settings.dark .medium-settings-input {
        color: white;
        background-color: #333;
        border-color: #666;
      }
      .medium-settings-input#redirectDelay { width: 70px; }
      .medium-settings-input#bypassSelector {
        width: 120px;
        appearance: auto;
        background-repeat: no-repeat;
        background-position: right 10px center;
        font-family: Arial, sans-serif;
        font-size: 16px;
        line-height: 1.4;
      }
      .medium-settings-input#bypassSelector option {
        padding: 8px;
        background-color: white;
        color: #333;
        transition: background-color 0.2s ease;
      }
      .medium-settings-input#bypassSelector option:hover {
        background-color: #1a8917;
        color: white;
      }
      .medium-settings.dark .medium-settings-input#bypassSelector option {
        background-color: #444;
        color: white;
      }
      .medium-settings.dark .medium-settings-input#bypassSelector option:hover {
        background-color: #555;
      }
      .medium-settings-button {
        background-color: var(--button-bg-color, #1a8917);
        color: var(--button-text-color, white);
        border: none;
        padding: 8px 14px;
        border-radius: 20px;
        cursor: pointer;
        font-weight: bold;
        transition: background-color 0.3s;
      }
      .medium-settings-button:hover { background-color: #155c11; }
      .${CLASS_NOTIFICATION} {
        position: fixed;
        bottom: 20px;
        right: 20px;
        background-color: #1a8917;
        color: white;
        padding: 15px;
        border-radius: 20px;
        box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        font-family: Arial, sans-serif;
        z-index: 10000;
        opacity: 0;
        transform: translateY(20px);
        transition: all 0.3s ease;
      }
      .${CLASS_NOTIFICATION}.show {
        opacity: 1;
        transform: translateY(0);
      }
      .medium-settings-input:focus {
        outline: none;
        border-color: #1a8917;
        box-shadow: 0 0 5px rgba(26,137,23,0.3);
      }
      .switch {
        position: relative;
        display: inline-block;
        width: 40px;
        height: 24px;
      }
      .switch input { opacity: 0; width: 0; height: 0; }
      .slider {
        position: absolute;
        cursor: pointer;
        top: 0; left: 0; right: 0; bottom: 0;
        background-color: #ccc;
        transition: .4s;
      }
      .slider:before {
        position: absolute;
        content: "";
        height: 16px;
        width: 16px;
        left: 4px;
        bottom: 4px;
        background-color: white;
        transition: .4s;
      }
      input:checked + .slider { background-color: #1a8917; }
      input:focus + .slider { box-shadow: 0 0 1px #1a8917; }
      input:checked + .slider:before { transform: translateX(16px); }
      .slider.round { border-radius: 34px; }
      .slider.round:before { border-radius: 50%; }
      .settings-icon-button {
        background: none;
        border: none;
        cursor: pointer;
        padding: 4px;
        margin: 0 4px 0 8px;
        display: flex;
        align-items: center;
        justify-content: center;
      }
      .settings-icon {
        width: 32px;
        height: 32px;
        fill: #757575;
        opacity: 0.7;
        transition: opacity 0.3s ease;
      }
      .settings-icon-button:hover .settings-icon {
        fill: #333;
        opacity: 1;
      }
    `;
    document.head.appendChild(style);
  };
  const stealthNotification = message => {
    const notification = document.createElement('div');
    notification.className = CLASS_NOTIFICATION;
    notification.textContent = message;
    document.body.appendChild(notification);
    setTimeout(() => notification.classList.add('show'), 50);
    setTimeout(() => {
      notification.classList.remove('show');
      setTimeout(() => notification.remove(), 300);
    }, 3000);
  };
  const getCurrentBypassService = () => bypassServiceKeys[config.currentBypassIndex % bypassServiceKeys.length];
  const switchToNextBypassService = () => {
    config.currentBypassIndex++;
    setSetting('currentBypassIndex', config.currentBypassIndex);
    stealthNotification(`Trying next bypass service: ${getCurrentBypassService()}`);
  };
  const tryNextBypass = async (articleUrl, attemptNumber) => {
    switchToNextBypassService();
    const nextBypassService = getCurrentBypassService();
    if (nextBypassService) {
      autoBypass(articleUrl, nextBypassService, attemptNumber + 1);
    } else {
      isRedirecting = false;
      stealthNotification("All bypass attempts failed.");
    }
  };
  const autoBypass = async (articleUrl, bypassKey, attemptNumber = 1) => {
    if (sessionStorage.getItem('mediumAntiLoop')) return;
    try {
      let bypassUrl;
      const mediumURL = new URL(decodeURIComponent(originalArticleUrl));
      let pathname = mediumURL.pathname;
      if (bypassKey === 'libmedium') {
        if (pathname.startsWith('/')) pathname = pathname.substring(1);
        bypassUrl = `${config.bypassUrls[bypassKey]}${pathname}`;
      } else if (bypassKey.startsWith('archive')) {
        bypassUrl = config.bypassUrls[bypassKey] + originalArticleUrl;
      } else {
        const bypassBaseURL = new URL(config.bypassUrls[bypassKey]);
        bypassUrl = new URL(mediumURL.pathname, bypassBaseURL).href;
      }
      sessionStorage.setItem('mediumAntiLoop', 'true');
      if (bypassKey.startsWith('archive')) {
        window.open(bypassUrl, '_self');
      } else {
        window.location.href = bypassUrl;
      }
      isRedirecting = true;
    } catch (error) {
      tryNextBypass(articleUrl, attemptNumber);
    }
  };
  const attachSettingsListeners = settingsContainer => {
    settingsContainer.querySelector('#bypassSelector').addEventListener('change', e => {
      const selectedKey = e.target.value;
      config.currentBypassIndex = bypassServiceKeys.indexOf(selectedKey);
      setSetting('currentBypassIndex', config.currentBypassIndex);
      stealthNotification(`Bypass service set to ${selectedKey}`);
    });
    settingsContainer.querySelector('#toggleRedirectCheckbox').addEventListener('change', () => {
      config.autoRedirectEnabled = settingsContainer.querySelector('#toggleRedirectCheckbox').checked;
      setSetting('autoRedirect', config.autoRedirectEnabled);
      stealthNotification('Auto-Redirect toggled');
    });
    settingsContainer.querySelector('#toggleDarkModeCheckbox').addEventListener('change', () => {
      config.darkModeEnabled = settingsContainer.querySelector('#toggleDarkModeCheckbox').checked;
      setSetting('darkModeEnabled', config.darkModeEnabled);
      settingsContainer.classList.toggle('dark', config.darkModeEnabled);
      stealthNotification('Dark Mode toggled');
    });
    settingsContainer.querySelector('#bypassNow').addEventListener('click', async () => {
      stealthNotification('Attempting bypass...');
      setSetting('isBypassSession', true);
      await autoBypass(originalArticleUrl, getCurrentBypassService());
    });
    settingsContainer.querySelector('#resetDefaults').addEventListener('click', () => {
      config.autoRedirectDelay = 5000;
      config.autoRedirectEnabled = true;
      config.darkModeEnabled = false;
      config.currentBypassIndex = 0;
      setSetting('redirectDelay', config.autoRedirectDelay);
      setSetting('autoRedirect', config.autoRedirectEnabled);
      setSetting('darkModeEnabled', config.darkModeEnabled);
      setSetting('currentBypassIndex', config.currentBypassIndex);
      settingsContainer.querySelector('#redirectDelay').value = config.autoRedirectDelay;
      settingsContainer.querySelector('#toggleRedirectCheckbox').checked = config.autoRedirectEnabled;
      settingsContainer.querySelector('#toggleDarkModeCheckbox').checked = config.darkModeEnabled;
      settingsContainer.querySelector('#bypassSelector').innerHTML = bypassServiceKeys.map((key, index) => `
        <option value="${key}" ${index === config.currentBypassIndex ? 'selected' : ''}>${key}</option>
      `).join('');
      settingsContainer.classList.remove('dark');
      stealthNotification('Settings reset to defaults');
    });
    const saveButton = settingsContainer.querySelector('#saveSettings');
    saveButton.addEventListener('click', () => {
      const delayInput = settingsContainer.querySelector('#redirectDelay');
      const newDelay = parseInt(delayInput.value, 10);
      if (!isNaN(newDelay) && newDelay >= 0) {
        config.autoRedirectDelay = newDelay;
        setSetting('redirectDelay', newDelay);
        saveButton.textContent = 'Saved!';
        setTimeout(() => { saveButton.textContent = 'Save'; }, 1500);
      } else {
        stealthNotification('Invalid redirect delay. Please enter a positive number.');
        delayInput.value = config.autoRedirectDelay;
      }
    });
    settingsContainer.querySelector('#closeSettings').addEventListener('click', () => { hideMediumSettings(); });
  };
  const showMediumSettings = () => {
    let existingPanel = document.querySelector(`.${CLASS_SETTINGS}`);
    if (existingPanel) {
      existingPanel.style.display = 'block';
      isSettingsVisible = true;
      return;
    }
    const settingsContainer = document.createElement('div');
    settingsContainer.className = `${CLASS_SETTINGS} ${config.darkModeEnabled ? 'dark' : ''}`;
    settingsContainer.innerHTML = `
      <div class="medium-settings-header">Medium Settings</div>
      <div class="medium-settings-toggle">
        <span>Auto-Redirect</span>
        <label class="switch">
          <input type="checkbox" id="toggleRedirectCheckbox" ${config.autoRedirectEnabled ? 'checked' : ''}>
          <span class="slider round"></span>
        </label>
      </div>
      <div class="medium-settings-toggle">
        <span>Redirect Delay (ms)</span>
        <input type="number" class="medium-settings-input" id="redirectDelay" value="${config.autoRedirectDelay}" />
      </div>
      <div class="medium-settings-toggle">
        <span>Dark Mode</span>
        <label class="switch">
          <input type="checkbox" id="toggleDarkModeCheckbox" ${config.darkModeEnabled ? 'checked' : ''}>
          <span class="slider round"></span>
        </label>
      </div>
      <div class="medium-settings-toggle">
        <span>Bypass Service</span>
        <select id="bypassSelector" class="medium-settings-input">
          ${bypassServiceKeys.map((key, index) => `
            <option value="${key}" ${index === config.currentBypassIndex ? 'selected' : ''}>${key}</option>
          `).join('')}
        </select>
      </div>
      <div class="medium-settings-toggle">
        <button class="medium-settings-button" id="bypassNow">Bypass Now</button>
      </div>
      <div class="medium-settings-toggle">
        <button class="medium-settings-button" id="resetDefaults">Reset to Default</button>
      </div>
      <div class="medium-settings-toggle">
        <button class="medium-settings-button" id="saveSettings">Save</button>
        <button class="medium-settings-button" id="closeSettings">Close</button>
      </div>
    `;
    attachSettingsListeners(settingsContainer);
    let isDragging = false, startX, startY;
    settingsContainer.addEventListener('mousedown', e => {
      if (e.target.closest('.medium-settings-input, .medium-settings-button, label')) return;
      isDragging = true;
      startX = e.clientX - settingsContainer.offsetLeft;
      startY = e.clientY - settingsContainer.offsetTop;
      settingsContainer.style.cursor = 'grabbing';
    });
    document.addEventListener('mousemove', e => { if (!isDragging) return; settingsContainer.style.left = `${e.clientX - startX}px`; settingsContainer.style.top = `${e.clientY - startY}px`; });
    document.addEventListener('mouseup', () => { isDragging = false; settingsContainer.style.cursor = 'grab'; });
    document.body.appendChild(settingsContainer);
    settingsContainer.style.display = 'block';
    isSettingsVisible = true;
  };
  const hideMediumSettings = () => {
    const settingsPanel = document.querySelector(`.${CLASS_SETTINGS}`);
    if (settingsPanel) { settingsPanel.style.display = 'none'; isSettingsVisible = false; }
  };
  const toggleMediumSettings = () => { isSettingsVisible ? hideMediumSettings() : showMediumSettings(); };
  const createSettingsIconButton = () => {
    const button = document.createElement('button');
    button.className = 'settings-icon-button';
    button.innerHTML = `<svg class="settings-icon" viewBox="0 0 24 24"><path d="M19.14 12.94c.04-.3.06-.61.06-.94s-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.6-.94l-.37-2.54c-.05-.25-.28-.43-.53-.43h-3.82c-.25 0-.48.17-.53.43l-.37 2.54c-.56.25-1.09.56-1.6.94l-2.39-.96c-.22-.07-.47 0-.59.22L2.74 8.87c-.11.2-.06.47.12.61l2.03 1.58c-.05.3-.07.61-.07.94s.02.64.07.94L2.86 14.51c-.18.14-.24.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.6.94l.37 2.54c.05.25.28.43.53.43h3.82c.25 0 .48-.17.53-.43l.37-2.54c.56-.25 1.09-.56 1.6-.94l2.39.96c.22.07.47 0 .59-.22l1.92-3.32c.12-.21.07-.47-.12-.61l-2.01-1.56zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"></path></svg>`;
    button.addEventListener('click', toggleMediumSettings);
    return button;
  };
  const autoCloseMemberBanner = () => {
    const closeButtons = document.querySelectorAll('button[data-testid="close-button"]');
    closeButtons.forEach(button => button.click());
  };
  const performAutoRedirect = async () => {
    if (sessionStorage.getItem('mediumAntiLoop')) return;
    const wallElement = document.querySelector(SELECTOR_MEMBER_WALL_CHECK);
    const indicator = document.querySelector(SELECTOR_MEMBER_INDICATOR);
    const isMemberArticle = wallElement || (indicator && indicator.textContent.includes('Member-only story'));
    if (config.autoRedirectEnabled && isMemberArticle && !isRedirecting) {
      isRedirecting = true;
      let currentBypass = getCurrentBypassService();
      if (currentBypass) {
        stealthNotification(`Attempting bypass with ${currentBypass}...`);
        setTimeout(async () => {
          setSetting('isBypassSession', true);
          await autoBypass(originalArticleUrl, currentBypass);
        }, config.autoRedirectDelay);
      } else {
        stealthNotification("No available bypass services to try.");
        isRedirecting = false;
      }
    }
  };
  const autoCloseFreediumBanner = () => {
    if (window.location.hostname === 'freedium.cfd') {
      window.addEventListener('load', () => {
        const closeButton = document.querySelector(SELECTOR_FREEDIUM_CLOSE_BUTTON);
        if (closeButton) closeButton.click();
      });
    }
  };
  const insertSettingsButton = () => {
    const settingsIconButton = createSettingsIconButton();
    const searchBar = document.querySelector(SELECTOR_SEARCH_BAR);
    if (searchBar && searchBar.parentNode) {
      searchBar.parentNode.insertBefore(settingsIconButton, searchBar.nextSibling);
    }
  };
  const memberCheck = () => {
    const wallElement = document.querySelector(SELECTOR_MEMBER_WALL_CHECK);
    const indicator = document.querySelector(SELECTOR_MEMBER_INDICATOR);
    return wallElement || (indicator && indicator.textContent.includes('Member-only story'));
  };
  const initializeScript = () => {
    originalArticleUrl = window.location.href;
    injectStyles();
    autoCloseFreediumBanner();
    if (memberCheck()) {
      GM_registerMenuCommand('Open Medium Settings', showMediumSettings);
      insertSettingsButton();
    }
    performAutoRedirect();
    autoCloseMemberBanner();
  };
  if (Object.values(config.bypassUrls).some((url) => window.location.href.startsWith(url) ||
      bypassServiceKeys.some(key => key.startsWith('archive') && window.location.href.startsWith(config.bypassUrls[key])))) {
    sessionStorage.setItem('mediumAntiLoop', 'true');
    isRedirecting = false;
  } else if (document.head?.querySelector('meta[property="al:android:url"]')?.content?.includes('medium://p/')) {
    initializeScript();
  }
})();