Code Monkey home page Code Monkey logo

feedi's Introduction

feedi

feedi is a web feed reader with a minimal interface akin to a Mastodon or Twitter feed.

Features:

  • Easy local and self-hosted environment setup.
  • Mastodon home and notification streams support.
  • Mobile-friendly UI.
  • Installable as a Progressive Web App.
  • Custom parsers for Reddit, Github and Goodreads.
  • Hackable RSS parsers and ad hoc scrapers.
  • Smart feed sorting options (highlight infrequent sources, auto mark as read).
  • Local article reading and preview using Mozilla's reader mode.
  • Send to Kindle device support.


For background about the development of this project, see this blog post.

Documentation

Installation

feedi requires Python >= 3.9. If you don't have it installed already consider using pyenv or asdf.

To install feedi on a local environment:

git clone https://github.com/facundoolano/feedi.git
cd feedi
make

Then, to run the app:

make run

The application will be available at http://localhost:9988/.

Alternatively, see the instructions for running with Docker or in a non-local setup.

Basic usage

Adding sources

By default, your feed will be empty. You can load content in a number of ways:

  • Manually adding a feed by clicking the + Add Feed button or navigating to /feeds/new.
  • Discovering a website RSS feed by pasting or dragging its url to the search input.
  • Loading a set of default websites included in this repo by running: make feed-load feed-sync.
  • Importing a collection of feeds from an OPML or CSV file.
  • Logging into a mastodon account.

When you first add a feed, the app will fetch its most recent articles, then it will check periodically for new content (every 30 minutes by default).

Browsing the feed

  • Sources can be put into folders, accesible on the left sidebar (desktop) or on the navbar menu (mobile).
  • The feed behavior can be tweaked with the controls on the right sidebar (desktop) or on the navbar menu:
  • Entries are auto-marked as viewed as the user scrolls down the feed. By default, already seen entries are skipped next time the app is open.
  • The default sorting puts least frequent sources at the top. Alternatively, there's are are strict chronological sorting.

Reading articles

There are different ways to interact with a feed entry:

  • If you click on the article title the original website will be open on a new browser tab.
  • In some cases, as with link agreggators like reddit, hacker news or lobste.rs, there will be a separate link for the article discussion.
  • If you click on the content or press Enter when focusing on the entry, the article content will be fetch and displayed on the local reader. This will be a stripped-down version of the article (removing some site links, ads and paywalls) powered by the mozilla/readability library. Note that for this to work you need node >= 20 installed when setting up the project.
    • The reader can also be used to preview arbitrary articles by dragging their url to the searchbox.

Deleting old articles

Entries get deleted automatically some days after their publication (defaulting to 7). Pinned and favorited entries are never deleted. Additionally, a minimum of entries (defaulting to 5) is kept for all sources, regardless of their publication date.

Advanced features

Bulk import/export feeds from csv and OPML files

make feed-load will load feeds from a local feeds.csv file. A sample file is included in the repo in case you want to see some content right away.

There's also a make feed-load-opml to import a list of RSS feeds from a feeds.opml file in the OPML format.

There are analogous make feed-dump and make feed-dump-opml targets to export feed data from the app.

Mastodon account setup

The Mastodon integration allows ingesting both the user home feed and the notification inbox as feeds. feedi first needs to be authorized to access the Mastodon account by navigating to Manage feeds > Mastodon login or to the url /auth/mastodon. After filling the instance name and grating access, feedi will redirect to the add feed form, where either mastodon or mastodon notifications feeds (or both) can be added.

Github notification feed

You can ingest the notifications from GitHub into feedi. To do so, navigate to your home feed at https://github.com/, open the page HTML source and search for an atom feed link. It should look something like:

  <link rel="alternate" type="application/atom+xml" title="ATOM" href="/facundoolano.private.atom?token=<TOKEN>" />

Copy the href url and use it to add a new RSS feed in feedi.

Goodreads home feed

You can ingest the notifications from Goodreads.com into feedi. To do so, navigate to your home feed at https://www.goodreads.com/, open the page HTML source and search for an atom feed link. It should look something like:

<link href='https://www.goodreads.com/home/index_rss/<ID>?key=<KEY>' rel='alternate' title='Goodreads' type='application/atom+xml'>

Copy the href url and use it to add a new RSS feed in feedi.

Reddit feeds

Reddit exposes public pages as RSS feeds by appending .rss to the URL, for example https://www.reddit.com/r/selfhosted.rss or www.reddit.com/user/someuser.rss.

Additionally, authenticated users have access to RSS feeds for private account pages: the front page, saved links, message inbox, etc. Links can be found here. feedi uses special purpose feed parsers both for reddit listing messages and links, and for the user inbox.

Kindle device support

The app allows to register a kindle email to send the cleaned up article HTML to it, yielding better results than the default Amazon Send to Kindle Chrome extension. This requires setting up SMTP credentials for sending email.

Steps to make it work with a basic gmail account:

  1. Register an google email account.
  2. Turn on 2-factor authentication for the account
  3. navigate to the app passwords settings and generate a password for the app (requires 2FA activated to work).
  4. Add SMTP settings to the app config:
FEEDI_EMAIL = "[email protected]"
FEEDI_EMAIL_PASSWORD = "GENERATED APP PASSWORD"
FEEDI_EMAIL_SERVER = "smtp.gmail.com"
FEEDI_EMAIL_PORT = 587
  1. navigate to /auth/kindle or type 'kindle' in the searchbox, and enter the @kindle.com email for your device.
  2. Go to your Amazon Personal Document Settings and add your feedi email address to the approved document email list.

After this setup the "Send to Kindle" command will be available when browsing articles.

Feed parsing

The app works by periodically fetching items from different feed sources (RSS/Atom, Mastodon toots and notifications, custom scrapers) and adjusting them to an Entry db model which more or less matches what we expect to display in the front end.

RSS/Atom feeds

Most RSS feeds should be processed correctly by the default parser, but sometimes it's desirable to add customizations that cleanup or extend the data for a better look and feel. This can be done by subclassing feedi.parsers.rss.BaseParser. The is_compatible static method determines whether a given feed should be parsed with that specific class; the parse_* methods overrides the default logic for each field expected in the front end.

As an example, this parser for the lobste.rs link aggregator is adjusted to inline a summary of external link submissions and distinguish between the source article url and the lobste.rs discussion url:

class LobstersParser(BaseParser):
    def is_compatible(_feed_url, feed_data):
        return 'lobste.rs' in feed_data['feed'].get('link', '')

    def parse_content_short(self, entry):
        # A 'Comments' link is only present on external link submissions
        if 'Comments' in entry['summary']:
            url = self.parse_content_url(entry)
            return (self.fetch_meta(url, 'og:description') or
                    self.fetch_meta(url, 'description'))
        return entry['summary']

    def parse_entry_url(self, entry):
        # return the discussion url, which is different from entry['link']
        # for external links
        if 'Comments' in entry['summary']:
            soup = BeautifulSoup(entry['summary'], 'lxml')
            return soup.find("a", string="Comments")['href']
        return entry['link']

You can see several custom RSS parsers in this module.

Custom feeds

Other than RSS and Mastodon feeds, the app can ingest arbitrary sources with custom parsers. This is useful for scraping websites that don't provide feeds or consuming JSON APIs directly.

To add a custom parser, subclass feedi.parsers.custom.CustomParser. The is_compatible method determines wheter a given url should be parsed with that parser. The fetch method does the actual fetching and parsing of entries. See the feedi.parsers.custom module for some examples.

Once the parser is implemented, it will be used when a new feed of type "Custom" is added in the webapp with the expected url.

Keyboard shortcuts

shortcut when action
Cmd+k focus search input
Enter search focused submit first suggestion
Escape search or suggestion focused hide suggestions
Down Arrow, Ctrl+n search or suggestion focused next suggestion
Up Arrow, Ctrl+n suggestion focused previous suggestion
Enter entry focused open entry content
Cmd+Enter, Cmd+Left Click entry focused open entry content on new tab
Cmd+Shift+Enter, Cmd+Shift+Left Click entry focused open entry discussion on new window
Down Arrow, Tab entry focused focus next entry
Up Arrow, Shift+Tab entry focused focus previous entry
p entry focused pin entry
f entry focused favorite entry
Escape viewing entry content go back

User management

The default app configuration assumes a single-user unauthenticated setup, but authentication can be enabled in case security is necessary, for example to deploy the app on the internet or to support multiple accounts.

To enable user authentication:

  1. Remove the DEFAULT_AUTH_USER setting from the configuration.
  2. If you already have a DB created, reset it with make db-reset. Or, alternatively, remove the default user with make user-del [email protected]. Note that this will also remove feeds and entries associated to it in the DB.
  3. You can create new users by running make user-add [email protected]. The command will prompt for a password.

Note that there's no open user registration functionality exposed to the front end, but it should be straightforward to add it if you need it. Check the auth module and the flask-login documentation for details.

Running with Docker

Get the image from github packages:

docker pull ghcr.io/facundoolano/feedi:latest

Create a volume for persisting the db data:

docker volume create feedidb

Load the default feeds into the default admin user:

docker run -v feedidb:/app/instance ghcr.io/facundoolano/feedi flask --app feedi/app.py feed load feeds.csv [email protected]
docker run -v feedidb:/app/instance ghcr.io/facundoolano/feedi flask --app feedi/app.py feed sync

Run in development mode:

docker run -p 9988:9988 -v feedidb:/app/instance ghcr.io/facundoolano/feedi

To run in production mode, a config file with at least a secret key is expected:

echo "SECRET_KEY = '$(python -c 'import secrets; print(secrets.token_hex())')'" >> production.py
docker run -p 9988:9988 -e FLASK_ENV=production -v feedidb:/app/instance -v $(pwd)/production.py:/app/feedi/config/production.py ghcr.io/facundoolano/feedi

To enable authentication, add DEFAULT_AUTH_USER=None to that production config file.

Non-local setup

You can refer to the Flask documentation for a instructions on how to deploy feedi to a non-local environment. The setup script included in the repository shows an example setup for a Debian server. You can run it remotely with ssh like make prod-install SSH=user@server.

feedi's People

Contributors

dertuxmalwieder avatar facundoolano avatar flxzt avatar julienma avatar sedationh avatar shindakun avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

feedi's Issues

Revisit send to kindle integration

I currently rely on stkclient to send articles to kindle devices. This is sketchy because it mimics a desktop app to get a private key and brittle because the key seems to expire within a couple of days, requiring another manual login. I'm thinking at this point it's probably better to just send the parsed webpages through email.

This would require:

  • setting up an email account or service via configuration (in the production config).
  • allowing the user to enter their kindle device email (a field in the user model that would replace the current kindle related table)
  • whitelisting the feedi instance email in the user amazon account.

[Bug] Feeds list doesn't show feeds without entries

Hey!

Thank you for this project. I plan to test the app in the following days. I love the idea, and hope to use it as my daily driver :)

I imported my old OPML feed (36 entries), but not all of them are listed at /feeds page. I don't know Python very well, but based on the query executed in the feed_list(), it appears that the feeds without entries won't be listed.

[Question] Expand folders to show individual feeds?

Some screenshots make me think that it's possible to open / close folders to see individual feeds.
Also, there's a 2nd zone in bottom left where we can individual feeds: are these favorite feeds? folder-less feeds?

How can I enable / use these options?
Thanks!

image

Feedi migrations incompatible with Ubuntu 20.x (sqlite3 3.31)

Just wanted to note that migrations cannot be performed with sqlite3 < 3.35. The DROP COLUMN syntax used by the sqlalchemy migrations was not present in sqlite3 before that version, and Ubuntu 20's final sqlite3 release was 3.31.

I solved this by installing a binary of sqlite3 manually and using it with make dbshell to manually perform the drops and alembic stamping the version with venv/bin/alembic stamp 0efddf242a41 && venv/bin/alembic upgrade head, repeat until it starts completing successfully.

Perhaps if I'd forced sqlalchemy to recompile by deleting its pyc files it would have used the new sqlite binary, for some reason it was still giving syntax errors after I'd used update-alternatives to make the new version the default.

Might want to make this clear in README and/or add a check in the Debian setup script.

Side note: thanks for the recent updates, the new parsers are great!

Locked out of feed edit page when using '/' in name

Hello, nice project!

I'm trying it out for myself and it seems that using a '/' character in a feed name prevent access to any url using the feed name. I know bugs should be expected, I'm sharing it just in case.

image

STRs:

  • Add the feed https://www.reddit.com/r/selfhosted/top.rss with name /r/selfhosted
  • Get a 404 on the resulting /feeds//r/selfhosted/entries page
  • Can't access or delete the feed afterwards

I had to manually edit the sqlite database to fix this.

I guess you could disallow forward slashes or maybe url-encode them?

make problem

hey great job!
When install the project ,I got the following problem:
image

system: windows/ git bash

Add some basic integration tests

It would be interesting to have test infrastructure to serve arbitrary (per test) rss xml files. With that available, a good start would be to test RSS feed CRUD operations, entry parsing on sync and the frequency feed ordering.

[idea] Generate a link feed from favorite entries

I think it would be interesting to optionally expose the list of favorited urls as a feed (rss and/or json) that could be consumed by external applications. E.g. maybe someone else wants to to follow your favorites as an rss feed, or you want to build a link blog in a personal website using the json endpoint as a data input. A somewhat related feature would be to publish your list of followed sites as an OPML + json endpoint.

I haven't figured out the right approach to this (thoughts appreciated!), but some ideas I had:

  • marking some feeds with a private flag (to be excluded from public feeds) and then adding a user config flag to explicitly enable the favorite feed endpoint.
  • alternatively, always expose the endpoint but password or auth-token protect it.

Truncate entry content with CSS

I currently have a couple of template filters to truncate the entry content (full/text view and compact/pinned view) in Python. This is because I couldn't get the HTML and CSS to show ellipsis on overflow. Ideally containers should be fixed in size (e.g. the height of the image preview) and the CSS would adjust the text to take advantage of the available space, instead of the current solution which isn't consistent and leaves some blanks.

Duplicated entries in feed

Sometimes when I fetch a new page from the feed, I get one or two duplicated entries. This could be some bug in the assumptions of the pagination algorithm (perhaps if new entries are added between the previous and next page fetch). I haven't identified a pattern to reproduce the bug.

Clicking on RSS Entries - PermissionError

Hi,

Absolutely love the project! I'm sure this is just a routes issue, but I don't know enough about nodejs to fix it.

When browsing, if I click on an RSS entry's title, it takes me to a page that displays Error fetching article: PermissionError(13, 'Permission denied'). The daemon doesn't throw any logs from this, which I found very strange. I had a permissions issue with the filesystem around the database previously and there were very good Python error traces in journalctl from the service.

Each entry seems to have a number that's unique per-Feed assigned to it. if I'm on feed ABC, the link is https://my-domain.com/feeds/ABC/entries. When I click on an entry, the link becomes https://my-domain.com/entries/12 or similar. I've tried adding back in the feeds/ABC part to make https://my-domain.com/feeds/ABC/entries/12, but that gives a 404.

Similarly, if I'm on the homepage and click on a post from any feed, it'll give me the link https://my-domain.com/entries/12 or some other number per entry. Same PermissionError results.

webp images not supported by kindle

If the article includes a webp image, it should be converted to another format or removed from the output HTML. The conversion probably requiers PIL or similar and some OS level libraries, which we don't want to impose to all users (not everyone needs kindle support). So it should fail gracefully if the libraries are missing.

Add support to customize bulma styles

The current CSS takes vanilla bulma and customizes it by overridding styles, in some cases by hardcoding and duplicating variables that could be reconfigured if we were building bulma from its Sass source.

  • Add support to build bulma from Sass (the compiled CSS shuold be checked in, the Sass customization shouldn't add setup overhead to run the app).
  • Replacce hardcoded values to bulma variable customizations.
  • Review main.css to look for potential improvements or further bulma customizations.

Sometimes UI starts scrolling and jump by itself

First thank you for this nice idea and implementation !

Sometimes, when reading the posts, UI starts scrolling and jumping back and scrolling again. I guess it may be linked to RSS updates in background, but I don't know where to start to investigate about it.

I'll post some comment if I find something better to characterize the problem.

Version 906d8e0 hosted on fly.io.

Add dark theme

I see a lot of annoying eye floaters, dark theme would help a lot.

Feed avatar improvements

  1. There's a CSS fallback for when the image is missing, but it doesn't seem to work in mobile in safari.
  2. I tried multiple combinations of the favicon fetching logic, and none picks up the ideal icon for all feeds. It would be best to make the field editable to let the user manually correct when the default icon looks bad. If code doesn't get too complicated, we could display all found options so the user knows what's available
  3. It would be better to fetch and store the feed image locally instead of hotlinking.
  4. It would be nice to show the feed avatars in the command lists and the autocomplete options, instead of the awesome icon.

Expose import/export in the web interface

Currently the csv and opml import/export features are only accessible from the command line. It would be convenient to expose them in the web UI, likely in the sidebar of the feed list.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.