Skip to content

Instantly share code, notes, and snippets.

@mathix420
Last active December 10, 2024 12:27
Show Gist options
  • Save mathix420/e0604ab0e916622972372711d2829555 to your computer and use it in GitHub Desktop.
Save mathix420/e0604ab0e916622972372711d2829555 to your computer and use it in GitHub Desktop.
Bypass Medium Paywall - Working late 2023 - Greasy Fork, Violentmonkey, Tampermonkey - Click the RAW button to install
// ==UserScript==
// @name Medium Paywall Bypass
// @namespace Violentmonkey Scripts
// @run-at document-start
// @match *://*.medium.com/*
// @match *://medium.com/*
// @match *://*/*
// @grant none
// @version 2.4
// @inject-into content
// @updateURL https://gist.githubusercontent.com/mathix420/e0604ab0e916622972372711d2829555/raw/medium.user.js
// @downloadURL https://gist.githubusercontent.com/mathix420/e0604ab0e916622972372711d2829555/raw/medium.user.js
// @website https://freedium.cfd
// @author Mathix420, ZhymabekRoman
// @description Don't forget to remove `@match` filters you don't want.
// ==/UserScript==
// initCall is telling us if we need to inject the title observer
function mediumRedirecter(initCall = false) {
if (
// Allow seeing original articles that were already redirected to freedium.
!window.location.href.endsWith('#bypass') &&
// Do not redirect when editing on medium.
!window.location.href.includes("/edit?source=") &&
// Detect if we are on a medium website (regardless of the domain)
document.head?.querySelector('meta[property="al:android:url"]')?.content?.includes('medium://p/')
) {
window.location.href = 'https://freedium.cfd/' + window.location.href;
} else if (initCall && /(.*\.|^)medium\.com$/.test(window.location.host)) {
// Observe <title> changes
new MutationObserver(function(mutations) {
// If title change is detected, check if a freedium redirect is required
if (mutations[0].target.textContent) mediumRedirecter();
}).observe(
document.querySelector('title'),
{ subtree: true, characterData: true, childList: true }
);
}
}
mediumRedirecter(true);
@mathix420
Copy link
Author

@ZhymabekRoman interesting, and yes it might be possible that the body is not fully loaded when document.head is run I guess.
But I can't find any reference to document-body online, where did you find this value? Maybe it is defaulting to document-end if it's recognized as a bad value. Or most likely I just didn't find a great source of documentation.

@ZhymabekRoman
Copy link

ZhymabekRoman commented Jan 17, 2024

But I can't find any reference to document-body online, where did you find this value? Maybe it is defaulting to document-end if it's recognized as a bad value. Or most likely I just didn't find a great source of documentation.

Hmmm, yeah, I think document-body is only Tampermonkey specific : https://www.tampermonkey.net/documentation.php?locale=en#meta:run_at

Violentmonkey doesn't support it: https://violentmonkey.github.io/api/metadata-block/#run-at

@lonelam
Copy link

lonelam commented Jan 18, 2024

to automatically redirect to the freedium site:

// ==UserScript==
// @name        Medium Paywall Bypass
// @namespace   Violentmonkey Scripts
// @run-at      document-start
// @match       *://*.medium.com/*
// @match       *://medium.com/*
// @match       *://*/*
// @grant       none
// @version     1.5
// @inject-into content
// @updateURL   https://gist.githubusercontent.com/mathix420/e0604ab0e916622972372711d2829555/raw/medium.user.js
// @downloadURL https://gist.githubusercontent.com/mathix420/e0604ab0e916622972372711d2829555/raw/medium.user.js
// @website     https://freedium.cfd
// @author      Mathix420, ZhymabekRoman
// @description Don't forget to remove `@match` filters you don't want.
// ==/UserScript==

(setInterval(function () {
  // 1. Allow seeing original articles that were already redirected to freedium.
  // 2. Do not redirect when editing on medium.
  if (window.location.href.endsWith('#bypass') || window.location.href.includes("/edit?source=")) {
    return;
  }

  const mediumPostUrlProperty = document.head.querySelector('meta[property="al:android:url"]')
  if ((mediumPostUrlProperty || {}).content && mediumPostUrlProperty.content.includes('medium://p/')
      && window.location.href.match(/^https?:\/\/(www\.)?(medium\.com\/|[\w-]+\.medium\.com\/|[\w-]+\.[\w-]+\/).*/
)) {
    window.location.href = 'https://freedium.cfd/' + window.location.href;
  }
}), 1000);

@mathix420
Copy link
Author

Hi @ZhymabekRoman

Violentmonkey doesn't support it

Surprisingly I just saw it in the settings of my Violentmonkey. Anyway I'm surprised that using document-body as the docs says The script will be injected **if** the body element exists. maybe it's a typo but all the other descriptions says when. It's pretty strange.
Sorry for being this picky on this change, but as it drastically increase the time of redirect I'm a bit reluctant to it. Also it works perfectly on my end with violentmonkey, would you consider trying with this one ? Might also be better for your privacy.


Hello @lonelam, thanks for your recommendation, but I don't understand your change can you give me some context? As the condition checking for medium meta tag meta[property="al:android:url"] already allows us to redirect every medium article (even those not hosted by the medium domain).

@ZhymabekRoman
Copy link

I'm using Violentmonkey, maybe the other extensions are breaking headers loading (I have about ~45 extensions).

@lonelam
Copy link

lonelam commented Jan 20, 2024

Hi @ZhymabekRoman

Violentmonkey doesn't support it

Surprisingly I just saw it in the settings of my Violentmonkey. Anyway I'm surprised that using document-body as the docs says The script will be injected **if** the body element exists. maybe it's a typo but all the other descriptions says when. It's pretty strange. Sorry for being this picky on this change, but as it drastically increase the time of redirect I'm a bit reluctant to it. Also it works perfectly on my end with violentmonkey, would you consider trying with this one ? Might also be better for your privacy.

Hello @lonelam, thanks for your recommendation, but I don't understand your change can you give me some context? As the condition checking for medium meta tag meta[property="al:android:url"] already allows us to redirect every medium article (even those not hosted by the medium domain).

The Medium site is an SPA and navigating to a new page will not trigger a 'load' event. And therefore, the script will not triggered if I navigate from a medium's page to another,
So a manual refresh is needed before I jump to freedium, do the detection intervally will prevent the extra manual operation.

@mathix420
Copy link
Author

@ZhymabekRoman okay, let me know if I still need to change the code !


@lonelam Oh I see! Sorry I missed the setTimeout part from your code! I also noticed this behavior earlier, I was thinking about implementing something like this https://stackoverflow.com/q/6390341/9799292

@lonelam
Copy link

lonelam commented Jan 21, 2024

@ZhymabekRoman okay, let me know if I still need to change the code !

@lonelam Oh I see! Sorry I missed the setTimeout part from your code! I also noticed this behavior earlier, I was thinking about implementing something like this https://stackoverflow.com/q/6390341/9799292

That's an awesome performance impovement, Listening the events like locationchange or hashchange will be more economically and more effective for the detection.

@mathix420
Copy link
Author

mathix420 commented Jan 21, 2024

@lonelam I just updated the script, it can now detect title changes (only when using medium.com domain as custom domains does not seems to host SPAs) and trigger a redirect if needed!
PS: small bug fix, make sure to be on V2.1

@ZhymabekRoman
Copy link

Achtung!

Sooo, it was going to take a while, but now we have it. Our whole Github organization is not public for now.

Reddit community, that was beginning all of that also gone - https://www.reddit.com/r/paywall/comments/15jsr6z/bypass_mediumcom_paywall/

We have moved to Codeberg - https://codeberg.org/Freedium-cfd

Medium, thank you >.

@jt-z
Copy link

jt-z commented Mar 8, 2024

Thanks all Freedium-cfd members!

@ZhymabekRoman
Copy link

@mathix420 Can you fix this error? Sometimes this error appears:

TypeError: can't access property "querySelector", document.head is null

@mathix420
Copy link
Author

@ZhymabekRoman I've fixed the type error, but if the head tag is missing it won't be able to detect the medium page before js load.
Sorry for the delay, I got lot of work these days!

@ZhymabekRoman
Copy link

@mathix420 Thank you! I really appreciate your efforts

@a-pav
Copy link

a-pav commented Apr 18, 2024

I think you can simplify the below lines using Optional chaining (?.):

First, completely remove

- const mediumPostUrlProperty = ((document.head || {}).querySelector ? document.head.querySelector('meta[property="al:android:url"]') : {}) || {}

Then inside the first if statement, replace

- (mediumPostUrlProperty.content && mediumPostUrlProperty.content.includes('medium://p/'))
+ document.head?.querySelector('meta[property="al:android:url"]')?.content?.includes('medium://p/')

Personally, I'm not going to use // @match *://*/*. So I put this into a detectMediumWebsite() function and commented it out.

Thanks for your work.

P.S. By the way, are you sure that the MutationObserver is actually registered?

@rodolfogoulart
Copy link

Can you remove? // @match *://*/*

do not make sense to be for all sites.

@ZhymabekRoman
Copy link

do not make sense to be for all sites.

Medium has too many sub-domains

@a-pav
Copy link

a-pav commented Apr 25, 2024

Medium has too many sub-domains

// @match *://*.medium.com/* Should be enough for matching sub-domains.

@ZhymabekRoman
Copy link

Should be enough for matching sub-domains.

What about devopsquare.com, blog.devops.dev, blog.stackademic.com, ai.plainenglish.io, bettermarketing.pub and etc? It's impossible for us to say how many Medium sites there are.

@a-pav
Copy link

a-pav commented Apr 25, 2024

You are right. I didn't know those type of sites exist. But they are just different domains, technically speaking.

To recap:
// @match *://*.medium.com/* Is enough for matching sub-domains.
// @match *://*/* Is needed for matching any possible domain who happens to be a Medium website.

@yluom
Copy link

yluom commented Apr 26, 2024

freedium is offline so unfortunately this script doesn't work anymore for now

@ZhymabekRoman
Copy link

All works as expected. Yeah, we made some codebase refactor, to improve speed and some big bug fixes. And there were some minor downtimes.

@mathix420
Copy link
Author

mathix420 commented May 14, 2024

Thanks @a-pav I wasn't aware that optional chaining was supported on tampermonkey. Yes, the mutationobserver is being registered, try commenting the section and click a link in this page for example https://medium.com/@francais you'll notice no redirect.

@rjmsilveira
Copy link

What a wonderful script. Thank you for sharing 🙇

@zzJinux
Copy link

zzJinux commented Jul 16, 2024

In case freedium goes unstable, how about disabling redirects for free posts? This is the predicate to check if it is a premium content: document.querySelector('.meteredContent') != null

@mathix420
Copy link
Author

@zzJinux yes, I thought about that too a couple months ago, I will try to implement it once I have more free time

@zzJinux
Copy link

zzJinux commented Jul 16, 2024

@mathix420 I have already locally patched the script to add the predicate. I hope your version will do so.

@mathix420
Copy link
Author

@zzJinux if you want you can fork this gist and update it so I can replicate your changes

@zzJinux
Copy link

zzJinux commented Jul 18, 2024

@mathix420 Just a few lines change: https://gist.github.com/zzJinux/364725e7c61810719286d94e88a4e38c I haven't had any issues.

@ZhymabekRoman
Copy link

Guys, sorry, yeah I known that's my fault. I need your help: Freedium-cfd/web#16 (comment)

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