1
0

12 Commits

Author SHA1 Message Date
George Mandis
b0741837d5 Merge branch 'main' into github-action-publishing 2021-05-09 11:49:43 -07:00
George Mandis
92a9012b7e Merge branch 'master' into github-action-publishing 2020-02-04 10:09:44 -05:00
George Mandis
6cf062e42d Update and rename publish.yml to publish.yml--example
Added "--example" to extension to prevent workflow from running on this repo
2020-02-04 10:05:58 -05:00
George Mandis
6a5dee1f41 Update publish.yml
Commenting out "on" block to prevent trigger
2020-02-04 10:04:35 -05:00
George Mandis
987a0445f1 Update publish.yml
Commenting out the cron scheduling because I don't actually want this to run on the repo
2020-02-04 10:02:29 -05:00
George Mandis
1b665ea9ba Merge branch 'master' into github-action-publishing 2020-02-04 09:44:35 -05:00
George Mandis
72062d5335 Merge branch 'master' into github-action-publishing 2020-02-04 09:43:18 -05:00
George Mandis
6bcb0ca5e1 Merge branch 'master' into github-action-publishing 2020-02-04 09:28:33 -05:00
George Mandis
e805fcc954 Merge branch 'master' into github-action-publishing 2020-02-04 09:01:51 -05:00
George Mandis
2c056603f0 Merge branch 'master' into github-action-publishing 2020-02-04 08:59:48 -05:00
George Mandis
33761d7e20 Updating README 2020-02-04 08:57:48 -05:00
George Mandis
e4a26fef91 Maded changes to GitHub Actions based publishing 2020-02-04 08:45:40 -05:00
9 changed files with 2041 additions and 164 deletions

4
.github/FUNDING.yml vendored
View File

@@ -1,7 +1,7 @@
# These are supported funding model platforms # These are supported funding model platforms
github: georgemandis github: georgemandis
patreon: # patreon: georgemandis
open_collective: # Replace with a single Open Collective username open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username otechie: # Replace with a single Otechie username
custom: # custom: https://george.mand.is/sponsor

36
.github/workflows/publish.yml--example vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Publish with GitHub Actions
# Uncommen the "on" block below to use
on:
push:
branches:
- master
schedule:
# Run this script every 15 minutes
- cron: '*/15 * * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout master branch
uses: actions/checkout@v2
with:
ref: master
- name: Setup Node.js and install dependencies
uses: actions/setup-node@v1
with:
node-version: 11.4.0
- run: npm install
- name: Run build and parse RSS feeds
- run: npm run build --if-present
- name: Commit latest build to ./output folder
uses: github-actions-x/commit@v2.3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
push-branch: 'master'
commit-message: 'Published latest changes to RSS feeds'
force-add: 'true'
files: output/*
name: GitHub Action Bubo Bot
email: action@github.com

1
.gitignore vendored
View File

@@ -1,2 +1 @@
node_modules/* node_modules/*
output/index.html

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 George Mandis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

104
README.md
View File

@@ -1,11 +1,57 @@
# 🦉 Bubo Reader # 🦉 Bubo Reader
Bubo Reader is a hyper-minimalist <acronym title="Really Simple Syndication">RSS</acronym> and <acronym title="JavaScript Object Notation">JSON</acronym> feed reader you can deploy on your own server, [Netlify](https://netlify.com) in a few steps or [Glitch](https://glitch.com) in even fewer steps! The goal of the project is to generate a webpage that shows a list of links from a collection of feeds organized by category and website. That's it. Bubo Reader is a somewhat irrationally minimalist <acronym title="Really Simple Syndication">RSS</acronym> and <acronym title="JavaScript Object Notation">JSON</acronym> feed reader you can deploy on [Netlify](https://netlify.com) in a few steps or [Glitch](https://glitch.com) in even fewer steps! The goal of the project is to generate a webpage that shows a list of links from a collection of feeds organized by category and website. That's it.
It is named after this [silly robot owl](https://www.youtube.com/watch?v=MYSeCfo9-NI) from Clash of the Titans (1981). It is named after this [silly robot owl](https://www.youtube.com/watch?v=MYSeCfo9-NI) from Clash of the Titans (1981).
You can read more about how this project came about in my blog post '[Introducing Bubo RSS: An Absurdly Minimalist RSS Feed Reader](https://george.mand.is/2019/11/introducing-bubo-rss-an-absurdly-minimalist-rss-feed-reader/)' You can read more about how this project came about in my blog post '[Introducing Bubo RSS: An Absurdly Minimalist RSS Feed Reader](https://george.mand.is/2019/11/introducing-bubo-rss-an-absurdly-minimalist-rss-feed-reader/)'
## Getting Started
How to deploy Bubo Reader in a few easy steps with Netlify or Glitch:
### Deploying to Glitch
The quickest way is to remix the project on Glitch:
[https://glitch.com/edit/#!/bubo-rss](https://glitch.com/edit/#!/bubo-rss)
Just changed some feeds in `./src/feeds.json` file and you're set! If you'd like to modify the style or the template you can changed `./output/style.css` file or the `./src/template.html` file respectively.
There is also a special `glitch` branch you can clone if you prefer:
[https://github.com/georgemandis/bubo-rss/tree/glitch](https://github.com/georgemandis/bubo-rss/tree/glitch)
The only difference between this branch and `master` is that it spins up a server using [Express](https://expressjs.com/) to serve your `./output/index.html` file on Glitch. Everything else is the same.
### Deploying to Netlify
- [Fork the repository](https://github.com/georgemandis/bubo-rss/fork)
- From your forked repository go to and edcit `src/feeds.json` to manage your feeds and categories
- [Create a new site](https://app.netlify.com/start) on Netlify from GitHub
The deploy settings should automatically import from the `netlify.toml` file. All you'll need to do is confirm and you're ready to go!
### Keeping Feeds Updated
#### Using Netlify Webhooks
To keep your feeds up to date you'll want to [setup a Build Hook](https://www.netlify.com/docs/webhooks/#incoming-webhooks) for your Netlify site and use another service to ping it every so often to trigger a rebuild. I'd suggest looking into:
- [IFTTT](https://ifttt.com/)
- [Zapier](https://zapier.com/)
- [EasyCron](https://www.easycron.com/)
If you already have a server running Linux and some command-line experience it might be simpler to setup a [cron job](https://en.wikipedia.org/wiki/Cron).
#### Using GitHub Actions
This approach is a little different and requires some modifications to the repository. Netlify started billing for [build minutes](https://www.netlify.com/pricing/faq/) very shortly after I published this project. Running `npm build` and downloading all of the RSS feeds took up a substantial number of this minutes, particulary if you had some kind of process pinging the webhook and trigger a build every 15 minutes or so.
How is the The GitHub Action-based approach different? The same build process runs, but this time it's on GitHub's servers via the Action. It then **commits** the newly created file generated at `./output/index.html` back into the repository. Netlify still gets pinged when the repository is updated, but skips the `npm run build` step on their end, which significantly reduces the number of build minutes required.
**Short Answer**: use the [`github-action-publishing`](https://github.com/georgemandis/bubo-rss/tree/github-action-publishing) branch for now if you'd prefer to use GitHub Actions to run your builds.
The GitHub Action is setup to build and commit directly to the `master` branch, which is not the best practice. I'd suggest creating a separate branch to checkout and commit changes to in the Action. You could then specify that same branch as the one to checkout and publish on Netlify.
## Anatomy of Bubo Reader ## Anatomy of Bubo Reader
- `src/index.html` - a [Nunjucks](https://mozilla.github.io/nunjucks/) template that lets you change how the feeds are displayed - `src/index.html` - a [Nunjucks](https://mozilla.github.io/nunjucks/) template that lets you change how the feeds are displayed
@@ -22,62 +68,6 @@ You can view live demos here:
Not the most exciting-looking demos, I'll admit, but they work! Not the most exciting-looking demos, I'll admit, but they work!
**Getting Started**
- [Deploying to Glitch](#glitch)
- [Deploying to Netlify](#netlify)
- [Keeping feeds updated](#updated)
<a id="glitch"></a>
## Deploying to Glitch
The quickest way is to remix the project on Glitch:
[https://glitch.com/edit/#!/bubo-rss](https://glitch.com/edit/#!/bubo-rss)
Just changed some feeds in `./src/feeds.json` file and you're set! If you'd like to modify the style or the template you can changed `./output/style.css` file or the `./src/template.html` file respectively.
There is also a special `glitch` branch you can clone if you prefer:
[https://github.com/georgemandis/bubo-rss/tree/glitch](https://github.com/georgemandis/bubo-rss/tree/glitch)
The only difference between this branch and `master` is that it spins up a server using [Express](https://expressjs.com/) to serve your `./output/index.html` file on Glitch. Everything else is the same.
<a id="netlify"></a>
## Deploying to Netlify
- [Fork the repository](https://github.com/georgemandis/bubo-rss/fork)
- From your forked repository go to and edit `src/feeds.json` to manage your feeds and categories
- [Create a new site](https://app.netlify.com/start) on Netlify from GitHub
The deploy settings should automatically import from the `netlify.toml` file. All you'll need to do is confirm and you're ready to go!
<a id="updated"></a>
### Keeping Feeds Updated
#### Using Netlify Webhooks
To keep your feeds up to date you'll want to [setup a Build Hook](https://www.netlify.com/docs/webhooks/#incoming-webhooks) for your Netlify site and use another service to ping it every so often to trigger a rebuild. I'd suggest looking into:
- [IFTTT](https://ifttt.com/)
- [Zapier](https://zapier.com/)
- [EasyCron](https://www.easycron.com/)
If you already have a server running Linux and some command-line experience it might be simpler to setup a [cron job](https://en.wikipedia.org/wiki/Cron).
#### Using GitHub Actions
This approach is a little different and requires some modifications to the repository. Netlify started billing for [build minutes](https://www.netlify.com/pricing/faq/) very shortly after I published this project. Running `npm build` and downloading all of the RSS feeds took up a substantial number of these, particulary if you had a process pinging the webhook and triggering a build every 15 minutes or so.
How is the The GitHub Action-based approach different? The same build process runs, but this time it's on GitHub's servers via the Action. It then **commits** the newly created file generated at `./output/index.html` back into the repository. Netlify still gets pinged when the repository is updated, but skips the `npm run build` step on their end. This significantly reduces the number of build minutes required.
**TLDR**: use the [`github-action-publishing`](https://github.com/georgemandis/bubo-rss/tree/github-action-publishing) branch for now if you'd prefer to use GitHub Actions.
**Note:** The GitHub Action is setup to build and commit directly to the `master` branch, which is not the best practice. I'd suggest creating a separate branch to checkout and commit changes to in the action. You could then specify that same branch as the one to checkout and publish on Netlify.
## Support ## Support
If you found this useful please consider sponsoring me or this project. If you'd rather run this on your own server please consider using one of these affiliate links to setup a micro instance on [Linode](https://www.linode.com/?r=8729957ab02b50a695dcea12a5ca55570979d8b9), [Digital Ocean](https://m.do.co/c/31f58d367777) or [Vultr](https://www.vultr.com/?ref=8403978). If you found this useful please consider sponsoring me or this project. If you'd rather run this on your own server please consider using one of these affiliate links to setup a micro instance on [Linode](https://www.linode.com/?r=8729957ab02b50a695dcea12a5ca55570979d8b9), [Digital Ocean](https://m.do.co/c/31f58d367777) or [Vultr](https://www.vultr.com/?ref=8403978).
## Showcase
Here are some websites using Bubo Reader:
- [Kevin Fiol](https://kevinfiol.com/reader/) ([repo](https://github.com/kevinfiol/reader))

View File

@@ -1,4 +1,3 @@
[build] [build]
command = "npm run build"
publish = "output/" publish = "output/"

1960
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "bubo-reader", "name": "bubo-reader",
"version": "1.0.1", "version": "1.0.0",
"description": "A simple but effective feed reader (RSS, JSON)", "description": "A somewhat dumb but effective feed reader (RSS, JSON & Twitter)",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
"build": "node src/index.js > output/index.html", "build": "node src/index.js > output/index.html",

View File

@@ -1,13 +1,9 @@
/* /**
* 🦉 Bubo RSS Reader * 🦉 Bubo RSS Reader
* ==== * ====
* Dead simple feed reader that renders an HTML * Dead, dead simple feed reader that renders an HTML
* page with links to content from feeds organized by site * page with links to content from feeds organized by site
* *
* Code: https://github.com/georgemandis/bubo-rss
* Copyright (c) 2019 George Mandis (https://george.mand.is)
* Version: 1.0.1 (11/14/2021)
* Licensed under the MIT License (http://opensource.org/licenses/MIT)
*/ */
const fetch = require("node-fetch"); const fetch = require("node-fetch");
@@ -19,17 +15,14 @@ const env = nunjucks.configure({ autoescape: true });
const feeds = require("./feeds.json"); const feeds = require("./feeds.json");
/** env.addFilter("formatDate", function(dateString) {
* Global filters for my Nunjucks templates
*/
env.addFilter("formatDate", function (dateString) {
const formattedDate = new Date(dateString).toLocaleDateString() const formattedDate = new Date(dateString).toLocaleDateString()
return formattedDate !== 'Invalid Date' ? formattedDate : dateString; return formattedDate !== 'Invalid Date' ? formattedDate : dateString;
}); });
env.addGlobal('now', (new Date()).toUTCString()); env.addGlobal('now', (new Date()).toUTCString() );
// parse RSS/XML or JSON feeds // parse XML or JSON feeds
function parseFeed(response) { function parseFeed(response) {
const contentType = response.headers.get("content-type") const contentType = response.headers.get("content-type")
? response.headers.get("content-type").split(";")[0] ? response.headers.get("content-type").split(";")[0]
@@ -51,44 +44,13 @@ function parseFeed(response) {
const jsonFeed = [contentType] const jsonFeed = [contentType]
.map(item => .map(item =>
["application/json", "application/feed+json"].includes(item) ? response.json() : false ["application/json"].includes(item) ? response.json() : false
) )
.filter(_ => _)[0]; .filter(_ => _)[0];
return rssFeed || jsonFeed || false; return rssFeed || jsonFeed || false;
} }
/*
There's a little inconcistency with how feeds report certain things like
title, links and timestamps. These helpers try to normalize that bit and
provide an order-of-operations list of properties to look for.
Note: these are tightly-coupled to the template and a personal preference.
*/
const getLink = (obj) => {
const link_values = ["link", "url", "guid", "home_page_url"];
const keys = Object.keys(obj);
const link_property = link_values.find(link_value => keys.includes(link_value));
return obj[link_property];
}
// fallback to URL for the title if not present (coupled to my template)
const getTitle = (obj) => {
const title_values = ["title", "url", "link"]; // fallback to url/link as title if omitted
const keys = Object.keys(obj);
const title_property = title_values.find(title_value => keys.includes(title_value));
return obj[title_property];
}
// More dependable way to get timestamps
const getTimestamp = (obj) => {
const timestamp = new Date(obj.pubDate || obj.isoDate || obj.date || obj.date_published).getTime();
return isNaN(timestamp) ? (obj.pubDate || obj.isoDate || obj.date || obj.date_published) : timestamp;
}
// fetch the feeds and build the object for our template
(async () => { (async () => {
const contentFromAllFeeds = {}; const contentFromAllFeeds = {};
const errors = []; const errors = [];
@@ -104,15 +66,13 @@ const getTimestamp = (obj) => {
typeof body === "string" ? await parser.parseString(body) : body; typeof body === "string" ? await parser.parseString(body) : body;
contents.feed = feeds[group][index]; contents.feed = feeds[group][index];
contents.title = getTitle(contents); contents.title = contents.title ? contents.title : contents.link;
contents.link = getLink(contents);
contentFromAllFeeds[group].push(contents); contentFromAllFeeds[group].push(contents);
// try to normalize date attribute naming // try to normalize date attribute naming
contents?.items?.forEach(item => { contents.items.forEach(item => {
item.timestamp = getTimestamp(item); const timestamp = new Date(item.pubDate || item.isoDate || item.date).getTime();
item.title = getTitle(item); item.timestamp = isNaN(timestamp) ? (item.pubDate || item.isoDate || item.date) : timestamp;
item.link = getLink(item);
}); });
} catch (error) { } catch (error) {