Jayson DeLancey
Posted on April 8, 2023
Introduced in 2020, the GitHub user profile README allow individuals to give a long-form introduction. This multi-part tutorial explains how I setup my own profile to create dynamic content to aid discovery of my projects:
- with the Liquid template engine and Shields (Part 1 of 4)
- using GitHub's GraphQL API to query dynamic data about all my repos (Part 2 of 4)
- fetching RSS blog post details from third-party sites (keep reading below)
- automating updates with GitHub Actions (Part 4 of 4)
You can visit github.com/j12y to see the final result of what I came up with for my own profile page.
Display Most Recent Blog Posts
The goal is to create a learning section that is populated by the most recent blog posts or articles from various sources. For example, if I publish a new tutorial like the one you are reading now I would like it to appear on my profile to improve discoverability of new content.
Typically, I publish long-form blog content about what I'm working on in a few specific sites:
✔️ Dev.to
✔️ Medium.com
✔️ Dolby.io
Fortunately, each of these provides either an Atom or RSS feed so that we can subscribe.
Parsing an RSS Feed for Blog Posts
Really Simple Syndication (RSS) has been around for quite some time as a remnant of the popularity behind creating a semantic web by treating web sites themselves like data. Not too long ago I encountered a web developer who was surprised anybody still uses it, but I often use feeds to get new content notifications in Slack and I frequently use Feedly as my preferred way of reading the Internet. Technically, there is an alternative spec in Atom feeds as well.
Looking around a little bit I found rss-parser which fit my requirements to be lightweight, straightforward to use, and provided sample code that shows how to use it in a TypeScript project.
import Parser from 'rss-parser'
parser = new Parser()
const feed = await this.parser.parseURL(url);
for (const item of feed.items) {
console.log(item.title);
console.log(item.link);
}
The attributes and version of the feed will largely determine the structure of the data and rss-parser
will allow for the custom fields. The feeds I want to parse are pretty standard so didn't require much customization.
Similar to the gallery I want the blog posts to display a small image. To do this, I'll need to extract social card data from the website itself.
Unpacking Open Graph Social Cards
When you include the URL of a website in a social media application like LinkedIn, Twitter, Facebook or into a messaging app like Slack or Discord the link will expand into something more graphically interesting.
This happens because specific <meta>
tags have been defined in the <head>
of the web site.
<meta name="twitter:image:src" content="https://thepracticaldev.s3.amazonaws.com/i/6hqmcjaxbgbon8ydw93z.png">
<meta property="og:image" content="https://thepracticaldev.s3.amazonaws.com/i/6hqmcjaxbgbon8ydw93z.png">
The description, title, author are all typically included in the RSS feed but the images are not. To solve for this, we can follow the link
from the RSS feed and fetch the page itself.
Naively, I initially considered using something like axios to fetch the HTML. I could then use a library like jsdom or cheerio to parse it. Fortunately, I found open-graph-scraper which like rss-parser met my technical requirements. Additionally, as part of my evaluation criteria I noted that the project has a reasonable MIT license, a sizable number of weekly downloads, and a repos with the code that has a fair number of stars, watchers, and ongoing maintenance.
const ogs = require('open-graph-scraper')
// RSS fetch loop to retrieve each item from the feed
await ogs({url: item.link, ogImageFallback: true})
.then((data: any) => {
const { error, result, response } = data;
image = result.ogImage.url;
snippet = result.ogDescription;
});
In a few feeds, the snippet
may be the first x characters from the story itself. In addition to the meta
image, the description
is often a succinct summary that is written for SEO purposes and would be more appropriate for the cards.
Fetch the latest Blog Post and Display
The last piece is not dissimilar to what is described in how-to use liquid template engine.
We fetch the most latest article from each source. The way the URL for feeds is constructed is a bit esoteric, but you can see the examples below for each of Medium, Dev.to, and Dolby.io.
let feed_url = `https://${process.env.MEDIUM_ID}.medium.com/feed`;
const medium = await feeds.getRecentArticles(feed_url);
scope['medium_post'] = medium[0];
feed_url = `https://dev.to/feed/${process.env.DEVTO_ID}`;
const devto = await feeds.getRecentArticles(feed_url);
scope['devto_post'] = devto[0];
feed_url = `https://dolby.io/blog/author/${process.env.DOLBYIO_ID}/feed/`;
const dolbyio = await feeds.getRecentArticles(feed_url);
scope['dolbyio_post'] = dolbyio[0];
And we can layout the results in the template. The shield adds a nice little decorator that can link to the full activity feed page while the item details are there to link to the content itself.
<td width="25%" valign="top" style="padding-top: 20px; padding-bottom: 20px; padding-left: 30px; padding-right: 30px;">
<div align="center"><a href="https://dev.to/@j12y" target="_blank"><img src="https://img.shields.io/badge/dev.to-0A0A0A?style=for-the-badge&logo=devdotto&logoColor=white"/></a></div>
<img src="{{ devto_post.image }}"/>
<p><b><a href="{{ devto_post.url }}">{{ devto_post.title }}</a></b></p>
<p>{{ devto_post.snippet | slice: 0,150 }}...</p>
</td>
Learn more
I hope this overview helps fetch your own learning resources. The next article will dive into how the dynamic components are assembled.
- Automating GitHub Profile Updates with Actions (Part 4 of 4)
Did this help you get your own profile started? Let me know and follow to get notified with updates when the next article comes out.
Posted on April 8, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.