Medium Member Bypass

Modern Medium GUI with multiple bypass services.

目前為 2025-02-24 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Medium Member Bypass
// @license      GPL-3.0-or-later
// @namespace    http://tampermonkey.net/
// @version      13.9.9
// @description  Modern Medium GUI with multiple bypass services.
// @match        *://*/*
// @match        *://medium.com/*
// @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/*
// @match        https://archive.fo/*
// @match        https://archive.md/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(() => {
    'use strict';
    const CLASS_SETTINGS = 'medium-settings';
    const CLASS_NOTIFICATION = 'medium-notification';
    const SELECTOR_FREEDIUM_CLOSE_BUTTON = '.close-button';
    const SELECTOR_SEARCH_BAR = 'div.ax.h';
    const getSetting = (key, def) => GM_getValue(key, def);
    const setSetting = (key, val) => GM_setValue(key, val);
    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/',
            archiveFo: 'https://archive.fo/newest/',
            archiveMd: 'https://archive.md/newest/'
        },
        currentBypassIndex: getSetting('currentBypassIndex', 0),
        autoRedirectDelay: getSetting('redirectDelay', 5000),
        autoRedirectEnabled: getSetting('autoRedirect', true),
        darkModeEnabled: getSetting('darkModeEnabled', false),
        isBypassSession: getSetting('isBypassSession', false)
    };
    const isMediumDomain = () => {
        return window.location.hostname.includes('medium.com') ||
            (document.head.querySelector('meta[property="al:android:url"]')?.content?.includes('medium://p/') === true);
    };
    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: #38444a;
    --text-color: #ffffff;
}
.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: #ffffff;
    background-color: #38444a;
    border-color: #666;
}
.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;
    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-right: 4px;
    margin-left: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.settings-icon {
    width: 32px;
    height: 32px;
    fill: #757575;
    opacity: 0.7;
    display: block;
    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 autoBypass = async (articleUrl, bypassKey) => {
        const bypassUrlValue = config.bypassUrls[bypassKey];
        try {
            let bypassUrl;
            const mediumURL = new URL(decodeURIComponent(articleUrl));
            let pathname = mediumURL.pathname;
            if (bypassKey === 'libmedium') {
                if (pathname.startsWith('/')) pathname = pathname.substring(1);
                bypassUrl = `${bypassUrlValue}${pathname}`;
            } else if (bypassKey.startsWith('archive')) {
                bypassUrl = bypassUrlValue + articleUrl;
            } else {
                const bypassBaseURL = new URL(bypassUrlValue);
                bypassUrl = new URL(mediumURL.pathname, bypassBaseURL).href;
            }
            sessionStorage.setItem('mediumBypassActive', 'true');
            window.location.href = bypassUrl;
            isRedirecting = true;
        } catch (error) {
            console.error(`Error during bypass with ${bypassKey}:`, error);
            stealthNotification(`Bypass failed with ${bypassKey}.`);
        }
    };
    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;
        let 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.style.opacity = '1';
        button.style.pointerEvents = 'auto';
        button.addEventListener('click', toggleMediumSettings);
        return button;
    };
    const performAutoRedirect = async () => {
        if (sessionStorage.getItem('mediumBypassActive')) {
            sessionStorage.removeItem('mediumBypassActive');
            return;
        }
        if (config.autoRedirectEnabled && document.querySelector('.meteredContent') != null && !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 autoCloseMemberBanner = () => {
        const closeButtons = document.querySelectorAll('button[data-testid="close-button"]');
        closeButtons.forEach(button => button.click());
    };
    const insertSettingsButton = () => {
        const settingsIconButton = createSettingsIconButton();
        const searchBar = document.querySelector(SELECTOR_SEARCH_BAR);
        if (searchBar && searchBar.parentNode) {
            searchBar.parentNode.insertBefore(settingsIconButton, searchBar.nextSibling);
        }
        if (document.querySelector('.meteredContent') !== null) {
            GM_registerMenuCommand('Open Medium Settings', showMediumSettings);
        }
    };
    const initializeScript = () => {
        originalArticleUrl = window.location.href;
        injectStyles();
        autoCloseFreediumBanner();
        autoCloseMemberBanner();
        if (isMediumDomain()) {
            insertSettingsButton();
            if (config.autoRedirectEnabled) {
                performAutoRedirect();
            }
        }
    };
    initializeScript();
})();