Skip to content

Instantly share code, notes, and snippets.

@mathix420
Last active April 26, 2024 17:16
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.3
// @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) {
// I miss typescript...
const mediumPostUrlProperty = ((document.head || {}).querySelector ? document.head.querySelector('meta[property="al:android:url"]') : {}) || {}
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)
(mediumPostUrlProperty.content && mediumPostUrlProperty.content.includes('medium://p/'))
) {
window.location.href = 'https://freedium.cfd/' + window.location.href;
} else if (initCall && /(.*\.|^)medium\.com$/.test(window.location.host)) {
new MutationObserver(function(mutations) {
if (mutations[0].target.textContent) mediumRedirecter();
}).observe(
document.querySelector('title'),
{ subtree: true, characterData: true, childList: true }
);
}
}
mediumRedirecter(true);
@kimdre
Copy link

kimdre commented Nov 30, 2023

Thank you very much for this Gist, it works like a charm!

@mathix420
Copy link
Author

You're welcome!

@lentil32
Copy link

Adding // @inject-into content is required for Violentmonkey.

@samisnotinsane
Copy link

Brilliant, works like a charm! Thank you 🚀

@stopmosk
Copy link

Thanks!!!

@rvente
Copy link

rvente commented Dec 30, 2023

Thank you for your work. If you also write on medium, currently editing your publication results in an error page. One might choose to apply the change.

20c19
<   if (window.location.href.endsWith('#bypass')) {
---
>   if (window.location.href.endsWith('#bypass') || window.location.href.includes("/edit?source=")) {

To prevent the redirect that causes you to reach the error page.

@mathix420
Copy link
Author

Nice addition @rvente, thanks!

@ZhymabekRoman
Copy link

ZhymabekRoman commented Jan 17, 2024

I think the @run-at property should be set to document-body, because with document-start it sometimes does not perform the redirects. Problem occurs in Firefox, everything is fine in Chrome.

@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.

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