Medium Member Bypass

Modern Medium GUI with multiple bypass services and fallback with availability checks.

Stan na 06-01-2025. Zobacz najnowsza wersja.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==UserScript==
// @name         Medium Member Bypass
// @author       UniverseDev
// @license      GPL-3.0-or-later
// @namespace    http://tampermonkey.net/
// @version      13.6
// @description  Modern Medium GUI with multiple bypass services and fallback with availability checks.
// @match        *://*.medium.com/*
// @match        https://freedium.cfd/*
// @match        https://readmedium.com/*
// @match        https://md.vern.cc/*
// @match        https://archive.is/*
// @match        https://archive.li/*
// @match        https://archive.vn/*
// @match        https://archive.ph/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      freedium.cfd
// @connect      readmedium.com
// @connect      md.vern.cc
// @connect      archive.is
// @connect      archive.li
// @connect      archive.vn
// @connect      archive.ph
// ==/UserScript==

(() => {
    'use strict';

    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: GM_getValue('currentBypassIndex', 0),
        autoRedirectDelay: GM_getValue('redirectDelay', 5000),
        autoRedirectEnabled: GM_getValue('autoRedirect', true),
        darkModeEnabled: GM_getValue('darkModeEnabled', false),
        isBypassSession: GM_getValue('isBypassSession', false),
    };

    const bypassServiceKeys = Object.keys(config.bypassUrls);

    const injectStyles = () => {
        const style = document.createElement('style');
        style.textContent = `
            .medium-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;
            }
            .medium-settings.dark {
                --background-color: #333;
                --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;
            }
            .medium-settings-input#redirectDelay {
                width: 70px;
            }
            .medium-settings-input#bypassSelector {
                width: 120px;
                appearance: auto;
                -webkit-appearance: auto;
                -moz-appearance: auto;
                background-repeat: no-repeat;
                background-position: right 10px center;
            }
            .medium-settings.dark .medium-settings-input#bypassSelector {
                border-color: #666;
            }
            .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;
            }
            .medium-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;
            }
            .medium-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);
            }
        `;
        document.head.appendChild(style);
    };

    const stealthNotification = (message) => {
        const notification = document.createElement('div');
        notification.className = 'medium-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 = () => {
        return bypassServiceKeys[config.currentBypassIndex % bypassServiceKeys.length];
    };

    const switchToNextBypassService = () => {
        config.currentBypassIndex++;
        GM_setValue('currentBypassIndex', config.currentBypassIndex);
        stealthNotification(`Trying next bypass service: ${getCurrentBypassService()}`);
    };

    const isServiceAvailable = async (url) => {
        try {
            const response = await fetch(url, { method: 'HEAD', mode: 'no-cors' });
            return response.ok || response.type === 'opaque';
        } catch (error) {
            console.error(`Service unavailable: ${url}`, error);
            return false;
        }
    };

    const autoBypass = async (articleUrl, bypassKey, attemptNumber = 1) => {
        const bypassUrlValue = config.bypassUrls[bypassKey];
        const testUrl = bypassUrlValue.endsWith('/') ? bypassUrlValue : bypassUrlValue + '/';

        const isAvailable = await isServiceAvailable(testUrl);
        if (!isAvailable) {
            stealthNotification(`Service unavailable: ${bypassKey}`);
            switchToNextBypassService();
            const nextBypassService = getCurrentBypassService();
            autoBypass(articleUrl, nextBypassService, attemptNumber + 1);
            return;
        }

        try {
            let bypassUrl;
            if (bypassKey === 'libmedium') {
                const mediumUrl = decodeURIComponent(articleUrl);
                const articleIdentifier = mediumUrl.substring(mediumUrl.lastIndexOf('/') + 1);
                bypassUrl = `${bypassUrlValue}feedium/${articleIdentifier}`;
            } else {
                bypassUrl = bypassUrlValue.endsWith('/')
                    ? bypassUrlValue + window.location.pathname
                    : bypassUrlValue + window.location.pathname;
            }

            window.location.href = bypassUrl;

            setTimeout(() => {
                if (window.location.hostname.includes('medium.com') && document.querySelector('p.bf.b.bg.z.bk')) { // Hardcoded selector
                    stealthNotification(`Bypass with ${bypassKey} failed.`);
                    switchToNextBypassService();
                    const nextBypassService = getCurrentBypassService();
                    autoBypass(articleUrl, nextBypassService, attemptNumber + 1);
                } else {
                    console.log(`Bypass with ${bypassKey} likely successful.`);
                }
            }, 2000);
        } catch (error) {
            console.error(`Error attempting bypass with ${bypassKey}`, error);
            stealthNotification(`Bypass with ${bypassKey} failed due to an error.`);
            switchToNextBypassService();
            const nextBypassService = getCurrentBypassService();
            autoBypass(articleUrl, nextBypassService, attemptNumber + 1);
        }
    };

    const showMediumSettings = () => {
        let existingPanel = document.querySelector('.medium-settings');
        if (existingPanel) {
            existingPanel.style.display = 'block';
            return;
        }

        const settingsContainer = document.createElement('div');
        settingsContainer.className = `medium-settings ${config.darkModeEnabled ? 'dark' : ''}`;

        settingsContainer.innerHTML = `
            <div class="medium-settings-header">Medium Settings</div>
            <div class="medium-settings-toggle">
                <span>Auto-Redirect</span>
                <button class="medium-settings-button" id="toggleRedirect">${config.autoRedirectEnabled ? 'ON' : 'OFF'}</button>
            </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>
                <button class="medium-settings-button" id="toggleDarkMode">${config.darkModeEnabled ? 'ON' : 'OFF'}</button>
            </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>
        `;

        document.body.appendChild(settingsContainer);

        let isDragging = false;
        let startX, startY;

        settingsContainer.addEventListener('mousedown', (e) => {
            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';
        });

        settingsContainer.querySelector('#bypassSelector').addEventListener('change', (e) => {
            const selectedKey = e.target.value;
            config.currentBypassIndex = bypassServiceKeys.indexOf(selectedKey);
            GM_setValue('currentBypassIndex', config.currentBypassIndex);
            stealthNotification(`Bypass service set to ${selectedKey}`);
        });

        settingsContainer.querySelector('#toggleRedirect').addEventListener('click', () => {
            config.autoRedirectEnabled = !config.autoRedirectEnabled;
            GM_setValue('autoRedirect', config.autoRedirectEnabled);
            settingsContainer.querySelector('#toggleRedirect').textContent = config.autoRedirectEnabled ? 'ON' : 'OFF';
            stealthNotification('Auto-Redirect toggled');
        });

        settingsContainer.querySelector('#toggleDarkMode').addEventListener('click', () => {
            config.darkModeEnabled = !config.darkModeEnabled;
            GM_setValue('darkModeEnabled', config.darkModeEnabled);
            settingsContainer.classList.toggle('dark', config.darkModeEnabled);
            settingsContainer.querySelector('#toggleDarkMode').textContent = config.darkModeEnabled ? 'ON' : 'OFF';
            stealthNotification('Dark Mode toggled');
        });

        settingsContainer.querySelector('#bypassNow').addEventListener('click', async () => {
            const articleUrl = encodeURIComponent(window.location.href);
            const bypassService = getCurrentBypassService();
            await autoBypass(articleUrl, bypassService);
        });

        settingsContainer.querySelector('#resetDefaults').addEventListener('click', () => {
            config.autoRedirectDelay = 5000;
            config.autoRedirectEnabled = true;
            config.darkModeEnabled = false;
            config.currentBypassIndex = 0;

            GM_setValue('redirectDelay', config.autoRedirectDelay);
            GM_setValue('autoRedirect', config.autoRedirectEnabled);
            GM_setValue('darkModeEnabled', config.darkModeEnabled);
            GM_setValue('currentBypassIndex', config.currentBypassIndex);

            settingsContainer.querySelector('#redirectDelay').value = config.autoRedirectDelay;
            settingsContainer.querySelector('#toggleRedirect').textContent = 'ON';
            settingsContainer.querySelector('#toggleDarkMode').textContent = 'OFF';
            settingsContainer.querySelector('#bypassSelector').selectedIndex = 0;
            settingsContainer.classList.remove('dark');
            stealthNotification('Settings reset to defaults');
        });

        settingsContainer.querySelector('#saveSettings').addEventListener('click', () => {
            const newDelay = parseInt(settingsContainer.querySelector('#redirectDelay').value, 10);
            if (!isNaN(newDelay) && newDelay >= 0) {
                config.autoRedirectDelay = newDelay;
                GM_setValue('redirectDelay', newDelay);
                stealthNotification('Settings saved');
            }
        });

        settingsContainer.querySelector('#closeSettings').addEventListener('click', () => {
            settingsContainer.style.display = 'none';
        });

        settingsContainer.style.display = 'block';
    };

    const performAutoRedirect = async () => {
        if (config.isBypassSession) {
            GM_setValue('isBypassSession', false);
            return;
        }

        const checkAllServices = async () => {
            const unavailableServices = [];
            for (const key of bypassServiceKeys) {
                const testUrl = config.bypassUrls[key];
                const isAvailable = await isServiceAvailable(testUrl);
                if (!isAvailable) {
                    unavailableServices.push(key);
                }
            }

            if (unavailableServices.length === bypassServiceKeys.length) {
                stealthNotification('All bypass services are currently unavailable.');
                return true; // Indicate all services are unavailable
            }
            return false; // Indicate some services might be available
        };

        if (config.autoRedirectEnabled && document.querySelector('p.bf.b.bg.z.bk')) { // Hardcoded selector
            if (await checkAllServices()) {
                return; // Don't attempt bypass if all services are down
            }
            setTimeout(async () => {
                const articleUrl = encodeURIComponent(window.location.href);
                const bypassService = getCurrentBypassService();

                await autoBypass(articleUrl, bypassService);
            }, config.autoRedirectDelay);
            stealthNotification(`Attempting bypass with ${getCurrentBypassService()}...`);
        }
    };

    const cleanBypassFragment = () => {
        const archiveDomains = ['archive.is', 'archive.li', 'archive.vn', 'archive.ph'];
        const currentDomain = window.location.hostname;

        if (archiveDomains.includes(currentDomain) && window.location.hash === '#bypass') {
            const cleanUrl = window.location.href.replace('#bypass', '');
            window.location.replace(cleanUrl);
        }
    };

    const initializeScript = () => {
        cleanBypassFragment();

        injectStyles();

        const isBypassPage = Object.values(config.bypassUrls).some((url) => window.location.href.startsWith(url) || bypassServiceKeys.some(key => key.startsWith('archive') && window.location.href.startsWith(config.bypassUrls[key])));

        if (isBypassPage) {
            GM_setValue('isBypassSession', true);
        } else if (window.location.href.startsWith('https://medium.com/')) {
            GM_registerMenuCommand('Open Medium Settings', showMediumSettings);
            performAutoRedirect();
        }
    };

    initializeScript();
})();