mirror of
https://github.com/wezm/wezm.net.git
synced 2024-12-18 18:29:54 +00:00
Add Feedlynx post
This commit is contained in:
parent
c3e3f76f09
commit
02b5bffb41
6 changed files with 164 additions and 9 deletions
131
v2/content/posts/2024/announcing-feedlynx/index.md
Normal file
131
v2/content/posts/2024/announcing-feedlynx/index.md
Normal file
|
@ -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.
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
### 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
|
BIN
v2/content/posts/2024/announcing-feedlynx/notification.png
Normal file
BIN
v2/content/posts/2024/announcing-feedlynx/notification.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -358,7 +358,7 @@ ul.projects {
|
||||||
img, svg {
|
img, svg {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.25em;
|
||||||
}
|
}
|
||||||
.emoji-img {
|
.emoji-img {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -409,6 +409,10 @@ ul.projects {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-left {
|
||||||
|
float: left;
|
||||||
|
margin: 0.5em 1em 1em 0;
|
||||||
|
}
|
||||||
.image-right {
|
.image-right {
|
||||||
float: right;
|
float: right;
|
||||||
margin: 0.5em 0 1em 1em;
|
margin: 0.5em 0 1em 1em;
|
||||||
|
|
12
v2/static/images/feedlynx.svg
Normal file
12
v2/static/images/feedlynx.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 14 KiB |
|
@ -34,6 +34,13 @@
|
||||||
<p>A selection of projects I've built or contributed to:</p>
|
<p>A selection of projects I've built or contributed to:</p>
|
||||||
|
|
||||||
<ul class="projects">
|
<ul class="projects">
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/wezm/feedlynx" class="no-border">
|
||||||
|
<img src="{{ config.base_url }}/images/feedlynx.svg" width="32" alt="">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/wezm/feedlynx">Feedlynx</a>
|
||||||
|
<p>Collect links to read or watch later in an RSS feed.</p>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/wezm/rsspls" class="no-border">
|
<a href="https://github.com/wezm/rsspls" class="no-border">
|
||||||
<span class="emoji-img">📰</span>
|
<span class="emoji-img">📰</span>
|
||||||
|
@ -43,17 +50,10 @@
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/yeslogic/allsorts">
|
<a href="https://github.com/yeslogic/allsorts">
|
||||||
<img src="images/allsorts.svg">Allsorts
|
<img src="images/allsorts.svg" width="32" alt="" style="margin-right: 0.5em">Allsorts
|
||||||
</a>
|
</a>
|
||||||
<p>Font parser, shaping engine, and subsetter implemented in Rust.</p>
|
<p>Font parser, shaping engine, and subsetter implemented in Rust.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<a href="https://dewpoint.7bit.org/" class="no-border">
|
|
||||||
<span class="emoji-img">💧</span>
|
|
||||||
</a>
|
|
||||||
<a href="https://dewpoint.7bit.org/">Dewpoint</a>
|
|
||||||
<p>View a 7-day dewpoint forecast for a selected location.</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/wezm/git-grab" class="no-border">
|
<a href="https://github.com/wezm/git-grab" class="no-border">
|
||||||
<span class="emoji-img">⤵️</span>
|
<span class="emoji-img">⤵️</span>
|
||||||
|
@ -61,6 +61,13 @@
|
||||||
<a href="https://github.com/wezm/git-grab">Git Grab</a>
|
<a href="https://github.com/wezm/git-grab">Git Grab</a>
|
||||||
<p>Clone a git repository into a standard location organised by domain and path.</p>
|
<p>Clone a git repository into a standard location organised by domain and path.</p>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://dewpoint.7bit.org/" class="no-border">
|
||||||
|
<span class="emoji-img">💧</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://dewpoint.7bit.org/">Dewpoint</a>
|
||||||
|
<p>View a 7-day dewpoint forecast for a selected location.</p>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://bitcannon.net/">
|
<a href="https://bitcannon.net/">
|
||||||
<svg id="bookmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#85144b">
|
<svg id="bookmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#85144b">
|
||||||
|
|
1
v2/templates/shortcodes/float_svg.html
Normal file
1
v2/templates/shortcodes/float_svg.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<img src="{{ config.base_url }}/{{ image }}" width="{{ width }}" alt="{{ alt }}" class="image-{{ float }}" />
|
Loading…
Reference in a new issue