Writing a script to cross-post to DEV.to

29 September 2021
·
nodejs

After publishing a post to my site, I usually cross-post it to DEV.to. Originally I would use their RSS feature and cross-posting was quite a painless process, but over time I've added new features to my blog like:

Which meant that I had to keep making manual changes to all my posts to make them ready for publishing on DEV. To save me some time, I wrote a script to automate this process.

If you've never written a script before, I've written a post on renaming files with Node.js scripts which should give you a good overview.

The cross-posting workflow I follow now is this:

  1. Publish a new post on my personal blog.
  2. Go to DEV.to and refresh my RSS feed (explained below).
  3. Run my devto.js script.
  4. Double-check the draft on DEV.to, and then hit publish.

Hook up your RSS feed to DEV.to

I cross-post my posts to DEV.to via my site’s RSS feed. This way, I get the “Originally published at” message to show up underneath the title of my posts:

"DEV.to post showing originally published at emgoto.com"

If you go to your DEV.to settings page, and click the Extensions option, you’ll have the opportunity to add an RSS feed:

"RSS feed options in DEV.to settings page"

Once you have hooked up your RSS feed, DEV.to will periodically check it to see if there are any new posts, and add the post in DEV as a draft.

After I publish a post on my own site, I go into DEV.to and hit the “Fetch feed now” button to make it show up straightaway. Unfortunately DEV doesn't have an API to do this step from within my script.

Run the script to update the draft post in DEV.to

To run this script, you’ll need your own DEV API key. I store mine in a .env file in my site’s repository:

.env
DEV_API_KEY=<key_goes_here>

The script makes use of two of the DEV API’s endpoints:

My posts are stored on my repository with Markdown and frontmatter, in a format like this:

---
title: "Hello! This is the markdown file"
date: 2021-09-25
tags: ["react"]
---

Content of the post goes here.

![Image with alt text](./image.png)

The script will transform it into this on DEV:

---
title: "Hello! This is the markdown file"
published: false
tags: ["react"]
---

Content of the post goes here.

![Image with alt text](https://emgoto.com/slug/image.png)

There’s three things to point out here:

  • I make sure the frontmatter has published: false so it stays in draft mode
  • I remove the date field. If you leave this value, DEV will set it as having been published at midnight of the date you specified. This can reduce the chance of your post actually getting views on DEV’s home page since it’s considered an “old” post.
  • There’s no DEV image API so you'll need to host the image yourself

The full version of the script is available on my site’s Github repository, and I’ve got a shortened version below that you can copy-paste.

#!/usr/bin/env node

const { readFile } = require('fs');
const { join } = require('path');
const glob = require('glob');
const fetch = require('node-fetch');

// I store my API key in a .env file
require('dotenv').config(); 

const updateArticle = (devArticleId, content) => {
    fetch(`https://dev.to/api/articles/${devArticleId}`, {
        method: 'PUT',
        headers: {
            'api-key': process.env.DEV_API_KEY,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            article: {
                body_markdown: content,
            },
        }),
    })
};

const updateFile = (content, slug) => {
    // Update frontmatter
    content = content.replace(/date: .*\n/, 'published: false\n');
    
    // Update images
    content = content.replace(
        /!\[(.+?)\]\(\.\/(.+?)\)/g,
        `![$1](https://emgoto.com/${slug}/$2)`,
    );

	// TODO: Any other changes to your content

    return content;
}

const devto = () => {
    // Looks for folders inside of "posts" folder that matches the given slug.
    const slug = process.argv[1];
    const file = [
        ...glob.sync(join(process.cwd(), 'posts', slug, 'index.mdx')),
    ][0];

    readFile(file, 'utf8', (err, content) => {
        if (err) reject(err);

        const title = content.match(/title: "(.*)"\n/)[1];
        content = updateFile(content, slug);

        fetch('https://dev.to/api/articles/me/unpublished', {
            headers: { 'api-key': process.env.DEV_API_KEY },
        })
            .then((response) => response.json())
            .then((response) => {
                if (response.length > 0) {
                    const draftTitle = response[0].title;
                    if (draftTitle === title) {
                        const devArticleId = response[0].id;
                        updateArticle(devArticleId, content);
                    }
                }
            })
    });
};

devto();

Comments