Techical Solution

Easily Migrate from Medium by Exporting all of your content with single Javascript Script.


Wish to export all of your medium blogs, so that you can move to another platform? This article demonstrate how you can eaisly download your medium content in bulk.

Moving away from Medium? Easy Peasy

Medium is a fantastic venue for getting plenty of engagement. It is a fantastic platform for someone who is not technically savvy to start with. The platform is fantastic and ensures that your content get the exposure it merits. However, there is a catch. The first is that if you are not from a list of countries, your content will not be able to be monetized, which means you are not making any mone. The second is that your content analytics are not entirely under your control.

I recently made the decision to switch from medium because I want to publish my work under my name, have complete control over it, and have it monetized. The unpleasant part is that Medium doesn't make it simple to export your material and move it to another platform or your own platform. This makes moving a large number of articles very difficult and time-consuming.

So I developed a script to move all of my stuff in a few easy steps, as any developer would do. I created a script that would collect your material in the form of MD files and your photos, which you could then move to any location with ease.

**Interesting Huh? **

Let's start with the work I managed to complete and the justification behind it. A technical person would be required to export all of your content. Or you may just get in touch with me, and I'd be happy to assist you as well.

The very step would be to get all of the content links that you want to move in a JSON format.

[
"https://medium.com/javascript-in-plain-english/react-native-foreground-service-f7fc8e617fba"
]

The process can be broken down into 3 simple steps. Using the JSON, getting the MD markup of the article and save into a file Reading those files to get the Images URL Downloading those Images and saving them into a Folder

Getting the markup from a medium should be our first step. We'll be using an NPM module, therefore let's use NPX remote execution to have the markup saved into a folder rather than downloading it.

const SaveFilesToFolder = async ({ link, title, fileName }) => {
 console.log(`Starting to write to path: ./articles/${fileName}/content.md`);
 try {
   await execPromise(`mkdir ./articles/${fileName}`);
   await execPromise(`mkdir ./articles/${fileName}/images`);
   await execPromise(
     `npx mediumexporter ${link} > articles/${fileName}/content.md`
   );
 } catch (error) {
   console.log({ error });
 }
};

The above code snippet's fundamental purpose is to construct some folders called "articles" and "images" inside of them. After that, using mediumexporter to get the MD from Medium Link and paste it in, a folder structure similar to this will be created. articles/article-title/content.md

In the above code, you can see a function being used often us execPromise and all it do is execute a command in CMD.

const execPromise = (url) => {
 return new Promise(async (res, rej) => {
   await exec(url, async (error, stdout, stderr) => {
     if (error) {
       rej(error);
     }

     setTimeout(() => {
       res(stdout);
     }, 0);
   });
 });
};

The next step is to re-read the generated file by using FS and extracting images.

const getImagesFromMd = ({ fileURL }) => {
 const images = [];
 const a = fs.readFileSync(fileURL).toString();
 const re =
   /!\[[^\]]*\]\((?<filename>.*?)(?=\"|\))(?<optionalpart>\".*\")?\)/gm;
 while ((match = re.exec(a)) !== null) {
   images.push(match["groups"].filename);
 }

 return images;
};

The above code just has a regex and it reads the file and extracts all the images. Now Lets save this image into a local directory.

const downloadImages = async ({ fileURL, fileName }) => {
 const imagesURL = getImagesFromMd({ fileURL });
 for (let i = 0; i < imagesURL.length; i++) {
   const element = imagesURL[i];
   const imageLocalPath = `${__dirname}/articles/${fileName}/images/`;
   const { filename } = await downloadImage(element, imageLocalPath).catch(
     console.log
   );
   await replaceOldImageinMdWithNew({
     oldImage: element,
     newImage: filename,
     fileURL,
   });
 }
};

The function downloadImage download the images into a directory.


const downloadImage = function (uri, filename) {
 const options = {
   url: uri,
   dest: filename, // will be saved to /path/to/dest/image.jpg
 };

 console.log({ options });
 return download.image(options);
};

And replaceOldImageinMdWithNew function replaces old images url with the new one.

const replaceOldImageinMdWithNew = async ({ oldImage, newImage, fileURL }) => {
 let a = fs.readFileSync(fileURL).toString();
 console.log({ oldImage, newImage });
 a = a.replace(oldImage, newImage);

 fs.writeFileSync(fileURL, a);
};

The above functionality will download the images and content of the article in a folder structure like below.

Folder Structure

And that's all; with this simple method, you can easily download all of your material to a local PC and then upload it to your CMS at a later time.

Full File :

const { exec } = require("child_process");
const {
  init,
  writeContent,
  getUploadReplaceUpdate,
} = require("./contentfulImport");
const articleLinks = require("./links.json");
const fs = require("fs");
const { request } = require("https");
const download = require("image-downloader");

const MEDIUM_PUBLISHER = "https://medium.com/javascript-in-plain-english/";
const MEDIUM_USER = "https://medium.com/@supersami/";

const GetArticlesTitleByName = () =>
  articleLinks.map((e) => {
    let a = e;
    if (a.includes(MEDIUM_PUBLISHER)) {
      a = a.split(MEDIUM_PUBLISHER)[1];
    }
    if (a.includes(MEDIUM_USER)) {
      a = a.split(MEDIUM_USER)[1];
    }
    let b = a.split("-");
    b = b.slice(0, b.length - 1);

    return b.join(" ").toUpperCase();
  });

const execPromise = (url) => {
  return new Promise(async (res, rej) => {
    await exec(url, async (error, stdout, stderr) => {
      if (error) {
        rej(error);
      }

      setTimeout(() => {
        res(stdout);
      }, 0);
    });
  });
};

const SaveFilesToFolder = async ({ link, title, fileName }) => {
  console.log(`Starting to write to path: ./articles/${fileName}/content.md`);
  try {
    await execPromise(`mkdir ./articles/${fileName}`);
    await execPromise(`mkdir ./articles/${fileName}/images`);
    await execPromise(
      `npx mediumexporter ${link} > articles/${fileName}/content.md`
    );
  } catch (error) {
    console.log({ error });
  }
};

const FetchAndReturnWithContentObject = () => {
  const arr = [];
  const exp = /^#(.*)$/m;
  const folder = "./articles";
  const files = fs.readdirSync(folder);

  for (let index = 0; index < files.length; index++) {
    const e = files[index];
    const file = fs.readFileSync(`${folder}/${e}`).toString();
    const match = exp.exec(file);
    let title = "";
    try {
      title = match[1];
    } catch (e) {
      console.log(e);
    }
    if (title) {
      const obj = {
        title,
        file,
        slug: e.split(".")[0],
        tags: ["javascript"],
        category: "Development",
      };
      arr.push(obj);
    }
  }

  return arr;
};

const getImagesFromMd = ({ fileURL }) => {
  const images = [];
  const a = fs.readFileSync(fileURL).toString();
  const re =
    /!\[[^\]]*\]\((?<filename>.*?)(?=\"|\))(?<optionalpart>\".*\")?\)/gm;
  while ((match = re.exec(a)) !== null) {
    images.push(match["groups"].filename);
  }

  return images;
};

const downloadImage = function (uri, filename) {
  const options = {
    url: uri,
    dest: filename, // will be saved to /path/to/dest/image.jpg
  };

  console.log({ options });
  return download.image(options);
};

const replaceOldImageinMdWithNew = async ({ oldImage, newImage, fileURL }) => {
  let a = fs.readFileSync(fileURL).toString();
  console.log({ oldImage, newImage });
  a = a.replace(oldImage, newImage);

  fs.writeFileSync(fileURL, a);
};

const downloadImages = async ({ fileURL, fileName }) => {
  const imagesURL = getImagesFromMd({ fileURL });
  for (let i = 0; i < imagesURL.length; i++) {
    const element = imagesURL[i];
    const imageLocalPath = `${__dirname}/articles/${fileName}/images/`;
    const { filename } = await downloadImage(element, imageLocalPath).catch(
      console.log
    );
    await replaceOldImageinMdWithNew({
      oldImage: element,
      newImage: filename,
      fileURL,
    });
  }
};

const start = async () => {
  const articles = GetArticlesTitleByName();
  for (let i = 0; i < articles.length; i++) {
    const e = articles[i];
    await SaveFilesToFolder({
      link: articleLinks[i],
      fileName: e.toLowerCase().split(" ").join("-"),
    });
    await downloadImages({
      fileName: e.toLowerCase().split(" ").join("-"),
      fileURL: `./articles/${e.toLowerCase().split(" ").join("-")}/content.md`,
    });

  }

};
start();

Raja Osama

I Describe Myself as a Polyglot ~ Tech Agnostic ~ Rockstar Software Engineer. I Specialise in Javascript-based tech stack to create fascinating applications.

I am also open for freelance work, and in-case you want to hire me, you can contact me at rajaosama.me@gmail.com or contact@rajaosama.me