// ==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();
})();