-
-
Save allenyllee/05e07d0c12bcf6d67edf5ed87f4b7629 to your computer and use it in GitHub Desktop.
Bypass Medium Paywall - Working at Jan 2026 - Greasy Fork, Violentmonkey, Tampermonkey - Click the RAW button to install
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Medium Paywall Bypass (Manual Button + Badge + Fallback + Offline) | |
| // @namespace Violentmonkey Scripts | |
| // @run-at document-start | |
| // @match *://*.medium.com/* | |
| // @match *://medium.com/* | |
| // @match *://*/* | |
| // @grant none | |
| // @version 3.5 | |
| // @inject-into content | |
| // @updateURL https://gist.githubusercontent.com/allenyllee/05e07d0c12bcf6d67edf5ed87f4b7629/raw/medium.user.js | |
| // @downloadURL https://gist.githubusercontent.com/allenyllee/05e07d0c12bcf6d67edf5ed87f4b7629/raw/medium.user.js | |
| // @website freedium.cfd | |
| // @author Mathix420, ZhymabekRoman, allenyllee | |
| // @description Show a button to open Medium articles via Freedium; badge shows Freedium/Mirror/Offline status with automatic fallback. | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| const PRIMARY = 'https://freedium.cfd/'; | |
| const FALLBACK = 'https://freedium-mirror.cfd/'; | |
| const TIMEOUT_MS = 1500; | |
| // status: 'unknown' | 'checking' | 'primary' | 'fallback' | 'offline' | |
| let endpointStatus = 'unknown'; | |
| let chosenBase = null; | |
| let probeInFlight = null; | |
| function isEditing() { | |
| return location.href.includes('/edit?source='); | |
| } | |
| function isMediumArticle() { | |
| return document.head | |
| ?.querySelector('meta[property="al:android:url"]') | |
| ?.content?.includes('medium://p/'); | |
| } | |
| function shouldShowButton() { | |
| return isMediumArticle() && !isEditing(); | |
| } | |
| function testEndpoint(url, timeout = TIMEOUT_MS) { | |
| return new Promise((resolve, reject) => { | |
| const controller = new AbortController(); | |
| const timer = setTimeout(() => controller.abort(), timeout); | |
| fetch(url, { | |
| method: 'HEAD', | |
| mode: 'no-cors', | |
| signal: controller.signal, | |
| cache: 'no-store', | |
| }) | |
| .then(() => { | |
| clearTimeout(timer); | |
| resolve(true); | |
| }) | |
| .catch(() => { | |
| clearTimeout(timer); | |
| reject(false); | |
| }); | |
| }); | |
| } | |
| async function probeEndpoints(force = false) { | |
| if (!force && (endpointStatus === 'primary' || endpointStatus === 'fallback' || endpointStatus === 'offline')) { | |
| return endpointStatus; | |
| } | |
| if (!force && probeInFlight) return probeInFlight; | |
| endpointStatus = 'checking'; | |
| chosenBase = null; | |
| updateButtonUI(); | |
| probeInFlight = (async () => { | |
| // 1) Try primary | |
| try { | |
| await testEndpoint(PRIMARY); | |
| endpointStatus = 'primary'; | |
| chosenBase = PRIMARY; | |
| return endpointStatus; | |
| } catch { | |
| // continue | |
| } | |
| // 2) Try fallback | |
| try { | |
| await testEndpoint(FALLBACK); | |
| endpointStatus = 'fallback'; | |
| chosenBase = FALLBACK; | |
| return endpointStatus; | |
| } catch { | |
| // continue | |
| } | |
| // 3) Both failed | |
| endpointStatus = 'offline'; | |
| chosenBase = null; | |
| return endpointStatus; | |
| })(); | |
| try { | |
| await probeInFlight; | |
| } finally { | |
| probeInFlight = null; | |
| updateButtonUI(); | |
| } | |
| return endpointStatus; | |
| } | |
| function getRedirectUrl() { | |
| if (!chosenBase) return null; | |
| return chosenBase + location.href; | |
| } | |
| function ensureButton() { | |
| if (document.getElementById('__freedium_btn_wrap__')) return; | |
| const wrap = document.createElement('div'); | |
| wrap.id = '__freedium_btn_wrap__'; | |
| Object.assign(wrap.style, { | |
| position: 'fixed', | |
| right: '16px', | |
| bottom: '16px', | |
| zIndex: 2147483647, | |
| }); | |
| const btn = document.createElement('button'); | |
| btn.id = '__freedium_btn__'; | |
| btn.type = 'button'; | |
| btn.textContent = 'Bypass on Freedium'; | |
| Object.assign(btn.style, { | |
| position: 'relative', | |
| padding: '10px 14px', | |
| borderRadius: '10px', | |
| border: '1px solid rgba(0,0,0,.2)', | |
| background: '#fff', | |
| color: '#000', | |
| fontSize: '14px', | |
| fontFamily: 'system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif', | |
| cursor: 'pointer', | |
| boxShadow: '0 6px 18px rgba(0,0,0,.15)', | |
| userSelect: 'none', | |
| }); | |
| const badge = document.createElement('span'); | |
| badge.id = '__freedium_badge__'; | |
| badge.textContent = '?'; | |
| Object.assign(badge.style, { | |
| position: 'absolute', | |
| right: '-8px', | |
| bottom: '-8px', | |
| minWidth: '18px', | |
| height: '18px', | |
| padding: '0 6px', | |
| borderRadius: '999px', | |
| display: 'inline-flex', | |
| alignItems: 'center', | |
| justifyContent: 'center', | |
| fontSize: '11px', | |
| lineHeight: '18px', | |
| border: '1px solid rgba(0,0,0,.15)', | |
| boxShadow: '0 4px 10px rgba(0,0,0,.12)', | |
| background: '#eee', | |
| color: '#000', | |
| pointerEvents: 'none', | |
| }); | |
| btn.appendChild(badge); | |
| btn.addEventListener('click', async (e) => { | |
| e.preventDefault(); | |
| // If offline, treat click as "retry" | |
| if (endpointStatus === 'offline') { | |
| await probeEndpoints(true); | |
| // after retry, if still offline, do nothing (UI already updated) | |
| return; | |
| } | |
| // Ensure we have a decided endpoint (primary/fallback/offline) | |
| await probeEndpoints(false); | |
| const url = getRedirectUrl(); | |
| if (url) { | |
| location.href = url; | |
| } | |
| // If offline after probe, UI shows offline and click does nothing | |
| }); | |
| wrap.appendChild(btn); | |
| document.documentElement.appendChild(wrap); | |
| updateButtonUI(); | |
| // background probe | |
| probeEndpoints(false); | |
| } | |
| function removeButton() { | |
| document.getElementById('__freedium_btn_wrap__')?.remove(); | |
| } | |
| function setButtonText(btn, badge, text) { | |
| btn.textContent = text; | |
| btn.appendChild(badge); // keep badge after text reset | |
| } | |
| function updateButtonUI() { | |
| const btn = document.getElementById('__freedium_btn__'); | |
| const badge = document.getElementById('__freedium_badge__'); | |
| if (!btn || !badge) return; | |
| if (endpointStatus === 'checking') { | |
| setButtonText(btn, badge, 'Bypass (checking...)'); | |
| btn.title = 'Checking freedium.cfd and freedium-mirror.cfd...'; | |
| btn.disabled = true; | |
| btn.style.opacity = '0.85'; | |
| btn.style.cursor = 'wait'; | |
| badge.textContent = '…'; | |
| badge.style.background = '#FDE68A'; // yellow | |
| badge.style.color = '#111827'; | |
| } else if (endpointStatus === 'primary') { | |
| setButtonText(btn, badge, 'Bypass via Freedium'); | |
| btn.title = 'Will open via freedium.cfd'; | |
| btn.disabled = false; | |
| btn.style.opacity = '1'; | |
| btn.style.cursor = 'pointer'; | |
| badge.textContent = 'F'; | |
| badge.style.background = '#86EFAC'; // green | |
| badge.style.color = '#064E3B'; | |
| } else if (endpointStatus === 'fallback') { | |
| setButtonText(btn, badge, 'Bypass via Mirror'); | |
| btn.title = 'Will open via freedium-mirror.cfd'; | |
| btn.disabled = false; | |
| btn.style.opacity = '1'; | |
| btn.style.cursor = 'pointer'; | |
| badge.textContent = 'M'; | |
| badge.style.background = '#FDBA74'; // orange | |
| badge.style.color = '#7C2D12'; | |
| } else if (endpointStatus === 'offline') { | |
| setButtonText(btn, badge, 'Freedium offline (click to retry)'); | |
| btn.title = 'Both freedium.cfd and freedium-mirror.cfd are unreachable. Click to retry.'; | |
| btn.disabled = false; // allow retry | |
| btn.style.opacity = '1'; | |
| btn.style.cursor = 'pointer'; | |
| badge.textContent = 'X'; | |
| badge.style.background = '#FCA5A5'; // red | |
| badge.style.color = '#7F1D1D'; | |
| } else { | |
| setButtonText(btn, badge, 'Bypass on Freedium'); | |
| btn.title = 'Open this article via Freedium (status unknown)'; | |
| btn.disabled = false; | |
| btn.style.opacity = '1'; | |
| btn.style.cursor = 'pointer'; | |
| badge.textContent = '?'; | |
| badge.style.background = '#E5E7EB'; // gray | |
| badge.style.color = '#111827'; | |
| } | |
| } | |
| function updateUI() { | |
| if (!document.documentElement) return; | |
| if (shouldShowButton()) ensureButton(); | |
| else removeButton(); | |
| } | |
| function observe() { | |
| const init = () => { | |
| updateUI(); | |
| const title = document.querySelector('title'); | |
| if (title) { | |
| new MutationObserver(updateUI).observe(title, { childList: true, subtree: true }); | |
| } | |
| if (document.head) { | |
| new MutationObserver(updateUI).observe(document.head, { childList: true, subtree: true }); | |
| } | |
| }; | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', init, { once: true }); | |
| } else { | |
| init(); | |
| } | |
| } | |
| observe(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
thanks!