From 02b5bffb415cd4b9147bfd98d77ae6d69f464622 Mon Sep 17 00:00:00 2001 From: Wesley Moore Date: Mon, 29 Jul 2024 09:44:58 +1000 Subject: [PATCH] Add Feedlynx post --- .../posts/2024/announcing-feedlynx/index.md | 131 ++++++++++++++++++ .../2024/announcing-feedlynx/notification.png | Bin 0 -> 15256 bytes v2/sass/screen.scss | 6 +- v2/static/images/feedlynx.svg | 12 ++ v2/templates/index.html | 23 +-- v2/templates/shortcodes/float_svg.html | 1 + 6 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 v2/content/posts/2024/announcing-feedlynx/index.md create mode 100644 v2/content/posts/2024/announcing-feedlynx/notification.png create mode 100644 v2/static/images/feedlynx.svg create mode 100644 v2/templates/shortcodes/float_svg.html diff --git a/v2/content/posts/2024/announcing-feedlynx/index.md b/v2/content/posts/2024/announcing-feedlynx/index.md new file mode 100644 index 0000000..f7b2b28 --- /dev/null +++ b/v2/content/posts/2024/announcing-feedlynx/index.md @@ -0,0 +1,131 @@ ++++ +title = "Announcing Feedlynx" +date = 2024-07-29T09:43:57+10:00 + +#[extra] +#updated = 2024-07-26T10:34:50+10:00 ++++ + +{{ float_svg(image="images/feedlynx.svg", width=75, float="left", + alt="Feedlynx logo: a caricature of a Lynx with a stem in its mouth. At the end of the stem is the orange RSS logo.") }} + +My latest project, [Feedlynx], is a self-hosted tool that allows you to +collect links in an RSS feed[^1]. You subscribe to the feed in your RSS reader of +choice and read or watch later at your leisure. Plus it has an adorable mascot! + +Feedlynx runs on most mainstream operating systems including Linux, macOS, BSD, +and Windows and has no runtime dependencies. Check out [the latest release][releases] to +download pre-compiled binaries for some common platforms. + +After a few weeks using Feedlynx myself I think it's ready for others to check out. +Read on for more information about my motivations behind building Feedlynx. + + + +### Motivation + +Since [moving all my YouTube subscriptions to my RSS +reader](@/posts/2024/youtube-subscriptions-opml/index.md) there was one thing I +missed from using YouTube directly: Watch Later. For videos that I'd encounter +on social media, or shared in chats I missed having a way to quickly stash +them for later. This is what motivated me to build Feedlynx. + +You might wonder, though, why I didn't use one of the existing bookmarking +or read-later services. Well, the main reason was that I wanted the links to +show up in the same place that I was already watching videos: in my RSS reader. +Of course, I also really like building little self-hosted tools to solve my own +problems. + +### Usage + +The Feedlynx server is implemented in Rust (as is tradition) and provides HTTP +endpoints to accept new links and serve the RSS feed. + +On a real computer new links are added via [the Firefox browser extension][feedlynx-ext] I +wrote. Click the icon and a moment later a notification is shown indicating the +link was added. + +{{ figure(image="posts/2024/announcing-feedlynx/notification.png", + link="posts/2024/announcing-feedlynx/notification.png", + width=303, + alt="TODO", + caption="Notification from Firefox extension.") }} + +On my phone, I've set up a workflow using the Shortcuts app that lets me add +links directly from the share sheet.. The Shortcut can be installed on iOS, +iPadOS, and macOS. It's linked in [the README][Feedlynx]. + +When a new link is submitted to the server it fetches the page to try to +extract [OpenGraph metadata][OpenGraph] to help fill out the item in the RSS +feed. The title of the tab is also submitted by the browser extension as a fall +back. + +The fall back is particularly necessary for YouTube since it seems they often +[block simple requests for the HTML of the video page][block]. Instead of the +video page they return a `200 Ok` response with a "prove you're not a robot +page" and a generic title and description. + +Adding new links via `cURL` is also quite simple, should you want to do so from +a script: + + curl -d 'url=https://example.com/' \ + -d 'token=ExampleExampleExampleExample1234' \ + http://localhost:8001/add + +### Implementation Notes + +Some notes on the implementation: + +- I _tried_ to keep the number of dependencies low and favoured dependencies + that had few dependencies of their own. +- I vendored code from a few crates since I only needed a small piece + of their functionality. These are acknowledged in the README and in + the code. +- Since this is only intended to serve sporadic requests for one person it + doesn't use async Rust. Regular synchronous code is more than enough and + avoids the need to pull in a whole async runtime. +- The RSS feed is the data store, no need for a DB or anything like that. + The file is guarded by a lock. +- Adding a feed and fetching the feed requires two different tokens that + are read from the environment when the server starts. +- The browser extension requests the bare minimum permissions necessary to get + the job done. It is not able to see the content of pages. +- YouTube links are detected and an `iframe` embed is included in the RSS + item to allow watching in your RSS reader. + +### Future Work/Ideas + +My primary use-case was stashing videos to watch later but as I was building +Feedlynx it made sense to make it work for any link. It seems like an logical +extension to have Feedlynx manage multiple feeds so that you can send videos +to one feed and other links to one or more other feeds. + +I only use Firefox so that's the only browser extension that exists so far. It +should be straightforward to port the extension to other browsers +(contributions welcome). I haven't submitted the extension to +addons.mozilla.org yet. I'll do that soon. + +Since Feedlynx manages an RSS feed on disk it could be useful to have a mode +where the server is not run, instead relying on an existing HTTP server like +`nginx` to serve the feed. A command like `feedlynx add some-url` could be +used to add new entries. + +I've been considering offering low-cost paid hosting for tools like Feedlynx +and [RSS Please] for folks that don't want to deal with their own server. If +you'd be interested in that let me know. + +### Links + +- [Source code][Feedlynx] +- [Browser extension code][feedlynx-ext] + +---- + +[^1]: I refer to the feed as an RSS feed throughout but it's actually an Atom feed. + +[Feedlynx]: https://github.com/wezm/feedlynx +[RSS Please]: https://rsspls.7bit.org/ +[feedlynx-ext]: https://github.com/wezm/feedlynx-ext +[OpenGraph]: https://ogp.me/ +[block]: https://github.com/iv-org/invidious/issues/4734 +[releases]: https://github.com/wezm/feedlynx/releases/latest diff --git a/v2/content/posts/2024/announcing-feedlynx/notification.png b/v2/content/posts/2024/announcing-feedlynx/notification.png new file mode 100644 index 0000000000000000000000000000000000000000..5d0f685399afb36b19744c832d48e2794a94607d GIT binary patch literal 15256 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 XD-TZAM4eRJ^iDxWMY=@tmEZpWd_qN$ literal 0 HcmV?d00001 diff --git a/v2/sass/screen.scss b/v2/sass/screen.scss index f94224c..45b2bd9 100644 --- a/v2/sass/screen.scss +++ b/v2/sass/screen.scss @@ -358,7 +358,7 @@ ul.projects { img, svg { width: 32px; vertical-align: middle; - margin-right: 0.5em; + margin-right: 0.25em; } .emoji-img { display: inline-block; @@ -409,6 +409,10 @@ ul.projects { display: block; } +.image-left { + float: left; + margin: 0.5em 1em 1em 0; +} .image-right { float: right; margin: 0.5em 0 1em 1em; diff --git a/v2/static/images/feedlynx.svg b/v2/static/images/feedlynx.svg new file mode 100644 index 0000000..1b5445f --- /dev/null +++ b/v2/static/images/feedlynx.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/v2/templates/index.html b/v2/templates/index.html index 15c8845..8d18421 100644 --- a/v2/templates/index.html +++ b/v2/templates/index.html @@ -34,6 +34,13 @@

A selection of projects I've built or contributed to: