Skip to content

Instantly share code, notes, and snippets.

@allenyllee
Forked from mathix420/medium.user.js
Last active January 12, 2026 11:08
Show Gist options
  • Select an option

  • Save allenyllee/05e07d0c12bcf6d67edf5ed87f4b7629 to your computer and use it in GitHub Desktop.

Select an option

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
// ==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();
})();
@ZhymabekRoman
Copy link

thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment