// ==UserScript== // @name Medium Member Bypass // @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 // @downloadURL none // ==/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) => ` `).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 = `