<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Tobias Alexander Franke</title>
        <description>Tobias Alexander Franke</description>
        <link>https://www.tobias-franke.eu/</link>
        <atom:link href="https://www.tobias-franke.eu/rss/index.xml" rel="self" type="application/rss+xml" />
        
        
        <item>
            <title>Agent OPML</title>
            <description>
                
                
&lt;div class=&quot;figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2026_05_30_agent-opml-logo.jpg&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2026_05_30_agent-opml-logo.jpg&quot; alt=&quot;&quot; title=&quot;&quot;&gt;
    
    &lt;/a&gt;
    
    
&lt;/div&gt;

&lt;h1 id=&quot;same-procedure-as-last-year&quot;&gt;Same procedure as last year&lt;/h1&gt;

&lt;p&gt;We do not need to debate the merits of AI; this has been discussed to death on the web and reminds me of one of the many programming language wars I was involved in in the early 2000s, where a single language was somehow the solution to world hunger and memory allocation bugs.&lt;/p&gt;

&lt;p&gt;I wanted to talk about a part of my day to day work. I need to evaluate a lot of publications - blog posts, articles from various hardware vendors, Github releases, papers, conference programs &amp;hellip; - to judge if they match our business use case or can be feasibly integrated into a prototype. Naturally, I have a lot of constraints around what I can and cannot do: Something must be open source and MIT or equivalently licensed, must meet certain hardware requirements, must be using a subset of GPU APIs, must be part of a certain graphics domain etc.&lt;/p&gt;

&lt;p&gt;I read a lot of sources every day that publish new and interesting stuff, like Jendrik Illner&amp;rsquo;s most excellent &lt;a href=&quot;https://www.jendrikillner.com/tags/weekly/&quot;&gt;Graphics Programming Weekly&lt;/a&gt; blog, individual bloggers, conference programs and more.&lt;/p&gt;

&lt;p&gt;As an example, a single &lt;a href=&quot;https://www.siggraph.org/&quot;&gt;SIGGRAPH conference&lt;/a&gt; can sport around 100 publications that I need to review. Each publication comes with a session, title, an abstract and a full PDF. Naturally, there&amp;rsquo;s a progression how I get to the juicy parts: Discard sessions I don&amp;rsquo;t care for first, search for interesting sounding titles, review their abstract and eventually keep the most interesting matches to read later in full.&lt;/p&gt;

&lt;p&gt;I am subscribed to several conferences, around 250 personal blogs and several newsletters of companies, all in the computer graphics domain, all of them crunching out several articles a day. This takes an &lt;em&gt;insane&lt;/em&gt; amount of time off from my day, &lt;strong&gt;every day&lt;/strong&gt;, and the process is almost completely monotonous. &lt;em&gt;Clanker-like&lt;/em&gt; one might say.&lt;/p&gt;

&lt;p&gt;So here&amp;rsquo;s my AI use-case, and how my obsession with RSS &lt;em&gt;feeds&lt;/em&gt; directly into all of this.&lt;/p&gt;

&lt;h1 id=&quot;pipeline-overview&quot;&gt;Pipeline Overview&lt;/h1&gt;

&lt;div class=&quot;figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2026_05_30_agent-opml-diag.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2026_05_30_agent-opml-diag.png&quot; alt=&quot;&quot; title=&quot;&quot;&gt;
    
    &lt;/a&gt;
    
    
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Agent OPML&lt;/strong&gt; is a local LLM-based agent (NanoClaw, Pi, OpenCode, OpenClaw, whatever) that uses a tool to fetch publications from various sources and serializes all new entries into a fresh JSON file. Each entry looks like this:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;authors&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;summary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once the data is fetched, the agent&amp;rsquo;s task, described in a skill, is to analyze each entry. The agent first analyzes the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;summary&lt;/code&gt; (which in many cases is the entire publication pulled via RSS), and if this is unsatisfactory - because it is just a shortened RSS &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;description&amp;gt;&lt;/code&gt;, is metadata about the publication but  &lt;em&gt;linking&lt;/em&gt; to it or is a URL of a video - go to the original &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;url&lt;/code&gt; and try to fetch more using extra tooling. For instance in case of Arxiv, there may be a HTML version of the paper, and if not there may be a PDF instead, in which case the agent proceeds to download the file and converts it to Markdown for processing.&lt;/p&gt;

&lt;p&gt;The skill describes what is interesting, what sounds interesting, etc. Each entry is analyzed for its relevance and goals, and more importantly if it fits within certain constraints. If all of these criteria are met, create a mini-summary and rating why this was deemed useful. The report is produced as a Markdown document, with annotations into a JSON file. Both are combined and attached to a new feed file that is served on some httpd.&lt;/p&gt;

&lt;p&gt;The outcome is this: A single feed I can subscribe to which updates me daily with those publications I truly want, and the pipeline/algorithm to judge all of it entirely under my control.&lt;/p&gt;

&lt;div class=&quot;basic-figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2026_05_30_agent-opml-feed.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2026_05_30_agent-opml-feed.png&quot; alt=&quot;&quot; title=&quot;&quot;&gt;
    
    &lt;/a&gt;
    
    
&lt;/div&gt;

&lt;h1 id=&quot;routing-data-to-the-agent&quot;&gt;Routing data to the agent&lt;/h1&gt;

&lt;p&gt;The input for the agent is a stream or feed of new publications that is updated regularly.&lt;/p&gt;

&lt;p&gt;To do this, we will use an &lt;a href=&quot;https://opml.org/&quot;&gt;OPML file&lt;/a&gt;. For the uninitated: An OPML is a XML file pointing to multiple URLs of feeds. A feed in turn is a structured file containing blog posts or any other updates from a webpage, and those come in three major formats: &lt;a href=&quot;https://validator.w3.org/feed/docs/atom.html&quot;&gt;Atom&lt;/a&gt;, &lt;a href=&quot;https://www.rssboard.org/rss-specification&quot;&gt;RSS 2.0&lt;/a&gt; or &lt;a href=&quot;https://www.jsonfeed.org/&quot;&gt;JSON Feed&lt;/a&gt;. Most importantly however is that everything in those files is &lt;em&gt;machine-readable&lt;/em&gt;. Usually OPML files and feeds are used with a feed-reader such as &lt;a href=&quot;https://netnewswire.com/&quot;&gt;NetNewsWire&lt;/a&gt; and are a simple way to store all your subscriptions.&lt;/p&gt;

&lt;p&gt;We are going to use the OPML instead to let the agent automatically fetch new content from webpages for automagic analysis. The OPML should capture every feed of information that you&amp;rsquo;d have to wade through manually, but want the agent to do it for you.&lt;/p&gt;

&lt;p&gt;To use my interests as an example, the OPML contains the following types of feed subscriptions:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;GPU Vendor&lt;/strong&gt;: ARM, NVIDIA, Qualcomm, AMD, &amp;hellip;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Graphics API&lt;/strong&gt;: DirectX blog, Khronos blog, Apple Metal, SLANG, &amp;hellip;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Game Engines&lt;/strong&gt;: Godot, Unreal, Unity, O3DE, Unigine, OGRE, Stride, &amp;hellip;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Tools&lt;/strong&gt;: Renderoc, Pix, NSIGHT, &amp;hellip;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Research Blogs&lt;/strong&gt;: Activision, EA Seed&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Paper Feeds&lt;/strong&gt;: Arxiv CS section, The Journal of Graphics Techniques (JGCT), Siggraph Digital Library entries, &amp;hellip;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Author Feeds&lt;/strong&gt;: DBLP and Google Scholar, individual blogs&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Github Commits&lt;/strong&gt;: Microsoft hlsl-spec, Gigi, &amp;hellip;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Conference Social Media&lt;/strong&gt;: GPC, GDC, Siggraph, EGSR, EG, I3D, &amp;hellip;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Extras&lt;/strong&gt;: One feed set up using &lt;a href=&quot;https://www.rsslibrarian.ch/librarian.php&quot;&gt;RSS-Librarian&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last two here are worth elaborating on:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Conference social media&lt;/em&gt; feed might sound odd at first: These are subscriptions from Mastodon, Bluesky and Twitter (through Nitter) that will have a special rule in the skill to catch announcements of a program URL for an upcoming conference. This way, the agent can grab more publications before they might appear anywhere else.&lt;/li&gt;
  &lt;li&gt;The &lt;em&gt;Extras&lt;/em&gt; feed serves the outlier case: If some article source is &lt;strong&gt;not&lt;/strong&gt; captured by the OPML, i.e. some interesting but completely random blog post that appears somewhere you cannot subscribe to or that is otherwise irrelevant, then this individual article can be added to the &lt;em&gt;Extras&lt;/em&gt; feed manually.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The OPML is used when running a script called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch_publications.py&lt;/code&gt;, which goes through every subscription one by one, fetches the feeds and then combines all new entries it has not seen since the last run into a JSON file that is then used by the agent for analysis.&lt;/p&gt;

&lt;h1 id=&quot;setting-up-agent-opml&quot;&gt;Setting up Agent OPML&lt;/h1&gt;

&lt;div class=&quot;figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2026_05_30_agent-opml-htop.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2026_05_30_agent-opml-htop.png&quot; alt=&quot;&quot; title=&quot;&quot;&gt;
    
    &lt;/a&gt;
    
    
&lt;/div&gt;

&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;This setup includes a bunch of things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A spare Raspberry Pi (optional)&lt;/li&gt;
  &lt;li&gt;Python&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://opencode.ai/download&quot;&gt;OpenCode&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pypi.org/project/markitdown/&quot;&gt;Markitdown&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pandoc.org/installing.html&quot;&gt;Pandoc&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Your OPML file&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#code-snippets&quot;&gt;A bunch of scripts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start by setting up a Raspberry Pi with Raspbian, and create a separate non-sudo account for the agent. If you want to run this on your local machine, you can skip this step. Then simply run the following steps.&lt;/p&gt;

&lt;p&gt;On your sudo account:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;pandoc cython3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On the agent account:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; https://opencode.ai/install | bash
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pip &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; venv ~/.venv
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; ~/.venv/bin/activate
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;markitdown feedparser
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;code-snippets&quot;&gt;Code snippets&lt;/h2&gt;

&lt;h3 id=&quot;fetch_publicationspy&quot;&gt;fetch_publications.py&lt;/h3&gt;

&lt;p&gt;This mini feed-aggregator &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch_publications.py&lt;/code&gt; is a simple Python script: Loop through a list of subscriptions from an OPML given via argument &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--opml&lt;/code&gt; and fetch the feeds, parse feed entries, reformat them into JSON dictionaries and use the URLs as IDs to remove duplicates. The outputs are written to a file given by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--output&lt;/code&gt;  that contains the extracted data. A temporary file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feed_state.json&lt;/code&gt; is kept in the same output directory to mark already visited articles.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/env python3
&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hashlib&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;feedparser&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argparse&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xml.etree.ElementTree&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ET&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fake_useragent&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserAgent&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;typing&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Optional&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pathlib&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;load_opml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opml_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tree&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opml_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getroot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;urls&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outline&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;findall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.//outline&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;xmlUrl&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;urls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;urls&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;load_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;save_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dump&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ensure_ascii&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;entry_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;guid&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;link&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;composite&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;published&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hashlib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sha256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;composite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hexdigest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;candidate&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;normalize_entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;feed_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;link&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[])]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;authors&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetch_feed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;User-Agent&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;raise_for_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# retry without user agent
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;raise_for_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;feedparser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;load_existing_entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;save_entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dump&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ensure_ascii&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argparse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ArgumentParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_argument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-i&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--opml&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_argument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-o&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--output&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse_args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opml&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Parameters missing&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;feeds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;load_opml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;feeds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;No feeds found in OPML.&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;state_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;feed_state.json&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;load_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;new_entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;feed_url&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;feeds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;parsed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetch_feed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;feed_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Failed to fetch &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;feed_url&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;seen_for_feed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;feed_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;new_ids&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parsed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;eid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;entry_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eid&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;seen_for_feed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ne&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;normalize_entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;feed_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;new_entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ne&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;new_ids&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;eid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;seen_for_feed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;eid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setdefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;feed_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[])&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;feed_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_ids&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;save_entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;No new entries found.&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;save_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Collected &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; new entries&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Re-running the script should only produce a file if any new entries have been found since the last run.&lt;/p&gt;

&lt;h3 id=&quot;convert-pdf-to-mdsh&quot;&gt;convert-pdf-to-md.sh&lt;/h3&gt;

&lt;p&gt;OpenCode has a directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.config/opencode/tools&lt;/code&gt; which contains small scripts the agent can use to do something without requiring it to go through the process manually, thus saving tokens.&lt;/p&gt;

&lt;p&gt;This tool uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;markitdown&lt;/code&gt; to convert a PDF to Markdown. This is helpful when the agent downloads a PDF and needs to process/read it. The easiest way to do that is to simply convert it to a structured text file.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;INPUT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OUTPUT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;INPUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;%.*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

markitdown &lt;span class=&quot;nv&quot;&gt;$INPUT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$OUTPUT&lt;/span&gt;.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;the-filter-skills&quot;&gt;The Filter Skills&lt;/h3&gt;

&lt;p&gt;This is the heart of the analysis and filtering process. These files describe what the agent should do, structured into these three general steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Read the combined entries that &lt;a href=&quot;#fetch_publicationspy&quot;&gt;fetch_publications.py&lt;/a&gt; spits out&lt;/li&gt;
  &lt;li&gt;Analyse each entry if it&amp;rsquo;s worth your time&lt;/li&gt;
  &lt;li&gt;Create a report of what passed the filter&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter-publications&lt;/code&gt; skill, which generically describes how to parse through the JSON file with all the new entries and how to collect them together. Save it as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.config/opencode/skills/filter-publications/SKILL.md&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
name: filter-publications
description: Given a list of new publications, find those relevant to our business use-case and produce a Markdown report on the agents findings.
---

# When to use this skill

Use this skill when the user wants the agent to evaluate and filter publications such as papers, blog posts, announcements, conference proceedings and so on.

Rank each publication by whether its idea, approximation, pipeline, or result could realistically transfer to the users specified use-case.

Be conservative and evidence-grounded. Clearly separate what the publication actually measured from what the agent infers as possible goals that can be reached with high relevance. Do not invent performance estimates, deployment claims, or results that are not supported by the inspected sources.

Use these three steps outlined below:
1. Read Input
2. Analyze Publications
3. Create Output

# Read Input

The agent is given one JSON file by the user (most often called `new_entries.json`). It contains a list of publications, where each entry has: a `title`, a `url`, a `summary`, and a list of `authors`. 

If the file the agent was given by the user is empty or does not exist, immediately halt and do not progress any further! Do not read any other json files or try to get data from somewhere else.

Each entry represents one new publication that was just discovered. The agent will need to analyze each one independently for its merits. Try to read the `summary` first. The summary may or may not contain the full publication. If it seems too short, try to open the URL and read the contents from there instead. If the `summary` is a short message from a social media page and contains a URL, follow that URL, especially if it contains a full program of a scientific conference.

Execute the [Analyze Publications](#analyze-publications) forstep described in the next section for all entries.

# Analyze Publications

## Required behavior

1. Read one publication entry source from the supplied JSON list.
2. Extract or read the best available information from the `summary` field.
3. Summarize the publication&apos;s core idea, evidence, and claimed results.
4. Evaluate the publication using the `review-publication` skill and produce an assessment with a clear verdict.

Report missing abstracts, inaccessible PDFs, broken links, paywalls, failed document extraction, low-quality extracted text, missing project pages, missing code, or insufficient evidence.

## Execution strategy

Do not treat a preliminary title/session-based inference as a confirmed analysis. If only title, session, program metadata, or DOI was inspected, label the evidence as **Limited** and make the score conservative.

Before finalizing top recommendations, do a detailed inspection of the strongest candidates. A publication should not appear as a final **Validated Top Recommendations** with a high score unless the agent has inspected substantive evidence such as an abstract, project page, PDF/text extraction, code repository, results, or limitations. If a publication cannot be analyzed in detail because the PDF is too large, paywalled, inaccessible, or time-limited, list it as a **Potential Top Candidates** rather than presenting it as fully validated.

These recommendations should not be homogeneous. Prefer a diverse shortlist that covers different topics that match the *Relevance* and *Goals* sections of the `review-publication` skill.

Prefer lightweight sources first: program page, DOI or publisher pages, abstracts, project pages, code repositories, videos, and supplemental descriptions. Do not download very large PDFs or supplemental files unless necessary. If a large file is skipped, report that limitation.

# Create Output

Create a subdirectory called `output`. All files the agent generates, even intermediate files, need to be written into this directory!

## Markdown report

Create a file called `report.md`. The file must start with a frontmatter. Use this exact style, with no blank line before the closing delimiter:

```yaml
---
title: Daily Report
author: Agent OPML
date: \today
documentclass: report
colorlinks: true
urlcolor: Maroon
linkcolor: Maroon
---
```

Do not modify this frontmatter. The report must only use Markdown headings, bullet points, and tables. Do not use `---` after the frontmatter, and no HTML tags. Structure the report into the following sections and no other headings:

1. **Overview**: A summary - as a list - of the overall statistics of the analysis, how many entries the agent processed, the themes the agent discovered and what the agent discarded. The list can be structured by source or category.
2. **Validated Top Recommendations**: Publications supported by substantive evidence. Use the the output of the `review-publication` skill for each publication.
3. **Potential Top Candidates**: Publications that look promising from title/session/metadata but lack enough evidence for a confident recommendation. Use the the output of the `review-publication` skill for each publication.
4. **Overall Verdict**: A summary of the agents findings and the papers that were recommended.

## JSON list

Create a file called `report.json` which follows the [Markdown report](#markdown-report), and use the JSON outputs from `review-publication` to populate the `validated_top_recommendations` and `potential_top_candidates` lists.

```json
{
  &quot;overview&quot;: &quot;{OVERVIEW}&quot;,
  &quot;validated_top_recommendations&quot;: [],
  &quot;potential_top_candidates&quot;: [],
  &quot;overall_verdict&quot;: &quot;{OVERALL_VERDICT}&quot;
}
```
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this file you will need adjust and modify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;# Analyze Publications&lt;/code&gt; section and/or provide a customized skill &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;review-publication&lt;/code&gt; that judges and summarizes each individual entry the filtering process goes through.&lt;/p&gt;

&lt;p&gt;Next is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;review-publication&lt;/code&gt; skill that will guide the agent to judge an individual publication. Save it as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.config/opencode/skills/review-publication/SKILL.md&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
name: review-publication
description: Judge a publication based on relevance, constraints and goals, and describes how to score the publication
---

# When to use this skill

The user asks the agent to produce a summary or judgement of a publication.

# Preprocessing

- If the publication is a social media post ...
- If the publication is a video, first ...
- ...

# Relevance

[Summarize here what is considered a relevant publication to you.]

# Constraints

[Describe all constraints you have and whether they can be circumvented.]

# Goals

[Describe the goals you want to achieve by using or implementing something described in a publication.]

# Final Scores

Use a 1 to 10 relevance score. The score should reflect the how well the publications fits the items in [Relevance](#relevance), [Constraints](#constraints) and [Goals](#goals).

...

Generate the following **Scores**:

- **Overall score**: 1 to 10.
- ...

# Output

- **Summary**: Title, authors, source, and core idea.
- **Evidence**: Abstract, PDF, project page, code, video, supplemental, or pasted metadata.
- **Measurements**: 
- ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that some fields are being referenced in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter-publications&lt;/code&gt; skill that drives it, most importantly the &lt;strong&gt;Goals, Constraints and Relevance&lt;/strong&gt; sections. Filling these sections should already give you a working set to get a summary of the inputs.&lt;/p&gt;

&lt;h3 id=&quot;create_feedsh&quot;&gt;create_feed.sh&lt;/h3&gt;

&lt;p&gt;This script reads the Markdown report from the agent, converts it into HTML and creates a feed file ready to deploy to a host where feed readers can subscribe to it. It only adds one entry. This could benefit from some polishing later.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;REPORT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;pandoc &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; markdown &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; html &lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; +%Y-%m-%d&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;UPDATE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-R&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;GUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$REPORT&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sha256sum&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{print $1}&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$2&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&amp;gt;
&amp;lt;rss version=&quot;2.0&quot;&amp;gt;
&amp;lt;channel&amp;gt;
  &amp;lt;title&amp;gt;Publication Filter Feed&amp;lt;/title&amp;gt;
  &amp;lt;generator&amp;gt;Agent OPML&amp;lt;/generator&amp;gt;
  &amp;lt;lastBuildDate&amp;gt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$UPDATE&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&amp;lt;/lastBuildDate&amp;gt;
  &amp;lt;item&amp;gt;
    &amp;lt;author&amp;gt;Agent OPML&amp;lt;/author&amp;gt;
    &amp;lt;title&amp;gt;Daily Report - &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DATE&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&amp;lt;/title&amp;gt;
    &amp;lt;description&amp;gt;&amp;lt;![CDATA[ &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$REPORT&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt; ]]&amp;gt;&amp;lt;/description&amp;gt;
    &amp;lt;pubDate&amp;gt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$UPDATE&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&amp;lt;/pubDate&amp;gt;
    &amp;lt;guid&amp;gt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$GUID&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&amp;lt;/guid&amp;gt;
&amp;lt;/item&amp;gt;
&amp;lt;/channel&amp;gt;
&amp;lt;/rss&amp;gt;
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;the-cronjob&quot;&gt;The Cronjob&lt;/h3&gt;

&lt;p&gt;A small script &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;run.sh&lt;/code&gt; to implement the &lt;a href=&quot;#pipeline-overview&quot;&gt;pipeline&lt;/a&gt;: Fetch new publications, run OpenCode to filter those and upload the resulting feed item.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;FEED_FILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;feeds.opml&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OUTPUT_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./output&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;JSON_FILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OUTPUT_DIR&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/new_entries.json&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;ARCHIVE_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OUTPUT_DIR&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; +%s&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; ~/.venv/bin/activate

&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/ai-paper-filter

&lt;span class=&quot;c&quot;&gt;# create output dir&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$OUTPUT_DIR&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# fetch publications&lt;/span&gt;
python fetch_publications.py &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$FEED_FILE&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$JSON_FILE&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# generate filtered report&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$JSON_FILE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    ~/.opencode/bin/opencode run &lt;span class=&quot;s2&quot;&gt;&quot;Use filter-publications on &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$JSON_FILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# upload new feed file&lt;/span&gt;
    sh create_feed.sh &lt;span class=&quot;nv&quot;&gt;$OUTPUT_DIR&lt;/span&gt;/report.md &lt;span class=&quot;nv&quot;&gt;$OUTPUT_DIR&lt;/span&gt;/report.xml    
    curl &lt;span class=&quot;nt&quot;&gt;-T&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$OUTPUT_DIR&lt;/span&gt;/report.xml &lt;span class=&quot;s2&quot;&gt;&quot;ftp://USER:PASS@MYFTP/PATH/report.xml&quot;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# archive everything&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$ARCHIVE_DIR&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;mv&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$OUTPUT_DIR&lt;/span&gt;/report&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$ARCHIVE_DIR&lt;/span&gt;

    &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$JSON_FILE&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Put this into a Cron job by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crontab -e&lt;/code&gt; on your target machine:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;0 8 * * * ~/run.sh &amp;gt; log.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will execute the pipeline every day at 8:00 in the morning and produce a new report. If &lt;a href=&quot;#fetch_publicationspy&quot;&gt;fetch_publications.py&lt;/a&gt; does not produce any new entries, the pipeline will not run. If it does however, the findings will be uploaded as a feed file to some FTP after the agent finished execution.&lt;/p&gt;

&lt;h1 id=&quot;future-ideas&quot;&gt;Future ideas&lt;/h1&gt;

&lt;h2 id=&quot;more-extraction-tooling&quot;&gt;More Extraction Tooling&lt;/h2&gt;

&lt;p&gt;The agent has to read or interpret content, and in some cases it boils down to the extraction mechanism behind it all: Scrape a URL and convert it to Markdown. Through my work on &lt;a href=&quot;https://www.rsslibrarian.ch/librarian.php&quot;&gt;RSS-Librarian&lt;/a&gt; I&amp;rsquo;ve already found ways to deal with text extraction. For non-raw-text document formats Markitdown and Pandoc already do a great job. I want to find ways however to also address videos (maybe generate dubs automatically) and presentations (either XLSX or PDF), which are harder to parse because of missing structure tags.&lt;/p&gt;

&lt;h2 id=&quot;recommendation-system-for-rss-categories&quot;&gt;Recommendation system for RSS categories&lt;/h2&gt;

&lt;p&gt;A second thing I plan to test is my own &lt;em&gt;recommendation algorithm&lt;/em&gt; for video subscriptions from Youtube/Odysee/Vimeo/PeerTube/etc. A general complaint from those who never grew up on RSS is that subscribing to video channels can quickly drown out good videos in a lot of nonsense (shorts mostly, but also irrelevant videos from channels that veer off topic), whereas the Youtube recommendation algorithm can easily get sidetracked if you click on one wrong video and suddenly everything recommended is just funny slop videos.&lt;/p&gt;

&lt;p&gt;I am testing a skill that ingests various video platform feeds (I continue to be amazed that these still exist on Youtube at all), sorts out feed items based on their description and the subtitles downloaded by Markitdown, and then repackages what is left into a new RSS feed that I subscribe to. This feed has one nice property: It is not a discovery feed, meaning the algorithm can never steer from its original goal of giving me &lt;em&gt;only&lt;/em&gt; that what I subscribe to. It merely removes entries rather and recommends the highlights in the data stream.&lt;/p&gt;

&lt;h2 id=&quot;agentic-rss&quot;&gt;Agentic RSS&lt;/h2&gt;

&lt;p&gt;Taking the same idea further I can imagine expanding this recommendation algorithm to the entire subscription library: One could subscribe - very liberally - to RSS feeds and end up with thousands of items per day, but the agent runs through them and instead of generating a report, decides whether to keep an item or not, then attaches the ones that were kept to a filtered feed. The agent can have arbitrary many input feeds, but you only ever subscribe to the output feed that has the sorted-out content based on custom rules.&lt;/p&gt;

&lt;p&gt;I currently run several digests similar to the paper filter already, but I&amp;rsquo;d like them to act as pure filters on feed files by abstracting some of the scripting logic more.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;This pipeline is not perfect, but has reduced the time I spend on reviewing new publications (especially around SIGGRAPH each year) significantly. Instead of mindlessly eyeballing loads of paper titles and spending hours &lt;em&gt;discarding&lt;/em&gt; irrelevant stuff I can concentrate on the things that matter to me the most. Sure the agent might miss something, but so do I when I stare at thousands of unread items in my feed-reader. All I keep doing now is adding publications I find on random sources to a &lt;a href=&quot;https://www.rsslibrarian.ch/librarian.php&quot;&gt;RSS-Librarian&lt;/a&gt; feed and adjust the OPML every now and then.&lt;/p&gt;

&lt;div class=&quot;figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2026_05_30_agent-opml-good-vibes.jpg&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2026_05_30_agent-opml-good-vibes.jpg&quot; alt=&quot;&quot; title=&quot;&quot;&gt;
    
    &lt;/a&gt;
    
    
&lt;/div&gt;

&lt;p&gt;The setup is minimal, and the &lt;em&gt;algorithm&lt;/em&gt; behind it all is not some opaque logic from a subscription service, but something I fully control end-to-end. This particular use-case might appeal to you as well: Wading through tons of items for sorting is a common task almost everyone will run into. You may write a paper and need to find new related publications to yours in a sea of articles. You may have too many RSS items in your feed reader. You may need to setup a more in-depth filtering mechanism than a simple keyword search.&lt;/p&gt;

&lt;p&gt;Whatever it is, if your task boils down to a for loop with some slightly more complex word-bingo search inside a bunch of feeds, and you&amp;rsquo;ve set up this process as well, I&amp;rsquo;m interested in your story. Write a blog post about it perhaps?&lt;/p&gt;

                
                
                
                
            </description>
            <pubDate>Sat, 30 May 2026 00:00:00 +0200</pubDate>
            <link>https://www.tobias-franke.eu/log/2026/05/30/agent-opml.html</link>
            <guid>LS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS0KCmlRRlBCQUFCQ0FBNUZpRUUxVWxUbHFPVUY1a3NFZCszT0RETU5qa2l2RThGQW1vcHpwSWJGSUFBQUFBQUJBQU8KYldGdWRUSXNNaTQxS3pFdU1USXNNQ3d6QUFvSkVEZ3d6RFk1SXJ4UGxna0gvMTh5cjNrK2JqOWJ1VlprRGpzMApjRDZ1VW9wK1FqUFo1Wm0vQ09xdEFFQmFKUXo1WHVzMjFCcTNXckJlTHNKVW5YYUdDNkFkdnBaU3hlV1N5MWhlCnZENU9ZVzFOZ1VseWJVeWl0UDE1bVNIdHU1N3RSMkRCS1RkWUdaaUg5eEM3YUdxYW5YRHRZQTFpRWRaWFd1MG0KUHRVZnNMN1l6WlhybjF4REpXRzJLOWpCR1dESksyOWxrSkNsbmJmVXQwNklFaEtmN2hETHdvT05TM2ZEQjNJUwo4UDVqV0NaWHRsUDBMcEVQeDhjN2piQ0tUaSsvZlZBQnMyeVhiU051K015SUlsRmFnb3BKdVZKUU1UU1p3eFpBCjI2cDR1NFFjcjZwRVhlbk5OaVJ5d2wzZk9OL3lIbnNDa3hUVmo0Y295N3RacUxUd3JIcVVSS0hTRXA0T3hPdGcKQW0wPQo9bnJDNwotLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS0K</guid>
            <category>AI</category><category>RSS</category>
            <comments>https://graphics.social/@thefranke</comments>
        </item>
        
        <item>
            <title>The RSS Librarian</title>
            <description>
                
                
&lt;div class=&quot;figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2025_11_08_rsslibfavicon.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2025_11_08_rsslibfavicon.png&quot; alt=&quot;&quot; title=&quot;&quot;&gt;
    
    &lt;/a&gt;
    
    
&lt;/div&gt;

&lt;h1 id=&quot;knowledge-is-power&quot;&gt;Knowledge is power&lt;/h1&gt;

&lt;p&gt;Many of you may know me as a huge Warhammer 40,000 nerd. I like the absurd and over-the-top nature of it, the grimdark setting and stagnant vision of the future where, what is left of humanity, slowly rots from within. Many small quotes encapsulate the universe very well, but the following one from &lt;a href=&quot;https://www.dawnofwar.com/&quot;&gt;Dawn of War&lt;/a&gt; is one of my favorites:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&amp;ldquo;Knowledge is power, hide it well.&amp;rdquo;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://dow.fandom.com/wiki/Dawn_of_War/Librarian&quot;&gt;Astartes Librarian&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Similar to a Black Mirror episode, this quote perfectly reflects our current strange reality of hiding most of the Internet&amp;rsquo;s articles worth reading behind walls of ads, paywalls or questionable design ideas. Knowledge has not become entirely inaccessible, but similar to what happened in the academic community there is a cost to accessing articles: Your privacy, your sanity, or both.&lt;/p&gt;

&lt;p&gt;Saving articles for later-reading has turned into a small ecosystem: Safari has a built-in method to store pages. Mozilla, until recently, ran Pocket &lt;a href=&quot;https://support.mozilla.org/en-US/kb/future-of-pocket&quot;&gt;and then shut it down&lt;/a&gt;. &lt;a href=&quot;https://instapaper.com/&quot;&gt;Instapaper&lt;/a&gt; remains stable and clean commercial solution I really like, and on the open-source front &lt;a href=&quot;https://wallabag.org/&quot;&gt;Wallabag&lt;/a&gt; is a viable alternative. There are great offline apps such as &lt;a href=&quot;https://apps.apple.com/ch/app/later-save-links-read-later/id1507396839&quot;&gt;Later&lt;/a&gt;. Saving articles can also take an archival character, which &lt;a href=&quot;https://archivebox.io/&quot;&gt;ArchiveBox&lt;/a&gt; supports nicely.&lt;/p&gt;

&lt;p&gt;I already have &lt;a href=&quot;https://netnewswire.com/&quot;&gt;my feed-reader&lt;/a&gt; where I read everything else. All of these services are extra layers and apps that are too heavy for someone who just wants to read articles, offline, in a clean layout, without ads, without intrusive layouts or pop-up videos or spyware. This particular problem has already been solved many years ago, so rather than re-inventing the wheel I can instead leverage existing, well-established web-standards to let me read arbitrary articles from the web in the comfy environment of my feed-reader, next to my subscriptions.&lt;/p&gt;

&lt;p&gt;If you subscribe via RSS/Atom to blogs, podcasts or Youtube channels, then RSS-Librarian is one more way for you to tunnel the Internet through your feed-reader.&lt;/p&gt;

&lt;h1 id=&quot;version-one&quot;&gt;Version One&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://github.com/thefranke/rss-librarian&quot;&gt;RSS-Librarian&lt;/a&gt; is a read-it-later service for RSS purists. You can store articles from the web in your own &lt;em&gt;personal RSS/Atom feed&lt;/em&gt; and use your favorite feed-reader software to read your stored articles later. RSS-Librarian uses no database and works without accounts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I am happy to announce that &lt;a href=&quot;https://www.github.com/thefranke/rss-librarian&quot;&gt;RSS-Librarian&lt;/a&gt; has now arrived at &lt;em&gt;Version One&lt;/em&gt; and is ready for production. Since &lt;a href=&quot;/log/2024/04/28/a-reader-service-for-rss-purists.html&quot;&gt;my first article&lt;/a&gt; a lot has happened.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/thefranke/rss-librarian/commit/48ebf96f3099b4426b67fbad3b96e48e357c187c#diff-ee8c822add044a72b0a9d632d12b30bf1d329400b1e6d369e930741d94ee6ca2&quot;&gt;original RSS-Librarian&lt;/a&gt; was kept extremely simple at just 211 lines of code in a single file. RSS-Librarian is written in PHP to make it as easy as possible to drop it on a VPS (virtually zero hosters come without PHP support these days). The script receives a URL as input, runs it through &lt;a href=&quot;https://www.fivefilters.org/&quot;&gt;a readability service&lt;/a&gt; to extract the raw content, and then attaches it to a randomly generated feed file, no database required. The random feed can be revisited to add more articles to it. Once subscribed to it via a feed-reader, the reader software does the rest. I personally am using readers like &lt;a href=&quot;https://github.com/seazon/FeedMe&quot;&gt;FeedMe&lt;/a&gt; which download the full content of a feed, images included, to my phone, making all stored articles available offline for later-reading during for instance flights.&lt;/p&gt;

&lt;p&gt;Since then, I have addressed several things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Bugs&lt;/strong&gt;: The code structure was mixed up with HTML quite badly. After some iterations it got better and easier to maintain. Still not perfect, but much easier to read. Separating code from structure also got rid of a lot of state-tracking bugs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;External Configuration&lt;/strong&gt;: RSS-Librarian now reads its configuration from a JSON file rather than needing source modifications. The &lt;strong&gt;Instance Info&lt;/strong&gt; section at the footer of the webpage provides an overview of the current configuration.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Feed Validation&lt;/strong&gt;: The original RSS 2.0 output created invalid feeds. This has been fixed.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Local Readability&lt;/strong&gt;: Rather than relying on a centralized external readability service, one can now download an extra library and run the extraction of article content locally.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Disabling Content Extraction&lt;/strong&gt;: Content extraction can now be turned &lt;strong&gt;off&lt;/strong&gt;. Whilst this seems strange, some feed-readers like &lt;a href=&quot;https://www.feedflow.dev/&quot;&gt;FeedFlow&lt;/a&gt; do not read RSS item content anymore and instead rely on a built-in reader-view &lt;a href=&quot;https://support.mozilla.org/en-US/kb/firefox-reader-view-clutter-free-web-pages&quot;&gt;similar to the ones in browsers&lt;/a&gt; to pull content from the URL directly. To save redundant space in these cases this option will skip adding any content to the feed.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Atom Support&lt;/strong&gt;: The &lt;a href=&quot;https://www.ietf.org/rfc/rfc4287.txt&quot;&gt;Atom feed format&lt;/a&gt; is slighlty more convenient when it comes to storing an author without an email address, and some other small things. I decided to leave the format RSS-Librarian uses up to the user. One can easily switch between both and RSS-Librarian will automatically convert between formats for existing feeds once a user adds a new article.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;User Onboarding&lt;/strong&gt;: On the original test-instance I noticed many feeds were repeatedly created with just one article in them, suggesting users forgot to bookmark their randomly created feed ID. Now users are onboarded with a warning before adding their first article.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Mobile CSS&lt;/strong&gt;: RSS-Librarian now has a beautiful and easy to tap-on user interface for mobile.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;iOS Share integration&lt;/strong&gt;: With iOS Shortcuts I was able to &lt;a href=&quot;https://www.icloud.com/shortcuts/d047b96550114317beb45bb57466a88f&quot;&gt;create a share-integration&lt;/a&gt; that allows any app to share a URL with your bookmarked RSS-Librarian feed.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Stylization&lt;/strong&gt;: Both custom CSS for RSS-Librarian and XSLT for the feeds can now be configured. When opening the feed&amp;rsquo;s XML file one now gets a full preview of all stored articles in a nicely formatted webpage rather than just raw XML. If no XSLT is present the code will default to &lt;a href=&quot;https://feedreader.xyz&quot;&gt;feedreader.xyz&lt;/a&gt; for a quick preview of the feed. Both the logo and the FavIcon can be configured as well.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Administration&lt;/strong&gt;: All users of an RSS-Librarian instance are pseudonymous, meaning they have an ID but no other identifier. To inform users of the instance about downtimes, changes or any other admin related notices, a special ID is pre-generated on first start up which lets an admin attach a message to all existing user feeds. Furthermore, the admin interface allows cleaning up abandoned feed files. The configuration also contains a field to add contact information for the instance administrator, which defaults to the &lt;a href=&quot;https://github.com/thefranke/rss-librarian/issues&quot;&gt;Github issues of RSS-Librarian&lt;/a&gt;. The administrator ID has its own feed that keeps track of any administrator operations and URLs added by any feed managed on the instance.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are also two contributions I want to mention:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/thefranke/rss-librarian/pull/2&quot;&gt;Alex Faxå cleaned up the code&lt;/a&gt; and added more robust cURL parameters to make RSS-Librarian appear more like a regular browser.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/thefranke/rss-librarian/pull/3&quot;&gt;Abe Ramseyer added&lt;/a&gt; the ability to see the list of stored items and optionally remove them again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To summarize, the code is now much easier to deploy, much easier to modify, has less bugs and creates valid RSS and Atom feeds. The user interface is visually better and easier to use on a phone.&lt;/p&gt;

&lt;div class=&quot;figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2025_11_08_rsslib_fennec.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2025_11_08_rsslib_fennec.png&quot; alt=&quot;RSS-Librarian running via Fennec on a LineageOS phone&quot; title=&quot;RSS-Librarian running via Fennec on a LineageOS phone&quot;&gt;
    
    &lt;/a&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;RSS-Librarian running via Fennec on a LineageOS phone&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;A full documentation on how to use RSS-Librarian, how to self-host and configure it and an FAQ is now &lt;a href=&quot;https://github.com/thefranke/rss-librarian/wiki&quot;&gt;availble on the Github Wiki&lt;/a&gt; of the project.&lt;/p&gt;

&lt;h1 id=&quot;store-it-well&quot;&gt;Store it well&lt;/h1&gt;

&lt;p&gt;Most importantly though, I want to announce the official RSS-Librarian instance hosted at&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.rsslibrarian.ch/librarian.php&quot;&gt;https://www.rsslibrarian.ch/librarian.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The old test instance hosted on hstn.me had several issues that caused subscription errors, particularily with online readers like &lt;a href=&quot;https://freshrss.org/&quot;&gt;FreshRSS&lt;/a&gt;, since it&amp;rsquo;s HTTPS certificate did not work correctly and regularily rerouted linked XML files to error pages. Additionally hstn.me would often attach extra URL parameters that needed to be filtered out from that particular instance. W3C feed validation for the same reasons would regularily fail. For &lt;em&gt;Version One&lt;/em&gt; therefore I wanted to unveil a stable main instance everyone can use for free. If you have any bugs to report, feature requests or PRs, do not hesitate to &lt;a href=&quot;https://github.com/thefranke/rss-librarian/issues&quot;&gt;open a Github issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Join the great crusade against the enshitification of the web. We do not need to burn it all down and re-create it from the ashes, the foundational standards for a better web are already here.&lt;/p&gt;

                
                
                
                
            </description>
            <pubDate>Sat, 08 Nov 2025 00:00:00 +0100</pubDate>
            <link>https://www.tobias-franke.eu/log/2025/11/08/the-rss-librarian.html</link>
            <guid>LS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS0KCmlRRk9CQUFCQ0FBNUZpRUUxVWxUbHFPVUY1a3NFZCszT0RETU5qa2l2RThGQW1vcHpwSWJGSUFBQUFBQUJBQU8KYldGdWRUSXNNaTQxS3pFdU1USXNNQ3d6QUFvSkVEZ3d6RFk1SXJ4UFhsa0grTFZ0WWhuZnFFckY1dlA1bUtSWgpaTjYveTFFajB0eUUraFlZdFlBeWpaN3pVZm85dDRLUlNUKzNSWFBIY2dVTituOWpMTHZEc3Zud0o4SWJMMWRMCmNwdnl2MUlDclhCSm5kTDZncUVueGdDQ1djVitJdjl6Wm1UeGorUngxclJVMTlNcHB6cER6YndMYUVudHJrT1cKbVlKV2RYYkJWVEZxMng0ekQwNFEzVnpzc0F1eWpuWWYvRm1qbkN5dmZsSTJyRWwyMnVLWTZmeU5PRVRqc3JFSApqTk1UZUowa2t2VFhYOHhDZGtzckR6SDg5RFNibnI3enl1Rlo4dDBHcXNjSVUwNjdrZFRCUjJKS3V5bDdaN29mCjFyOTVldUFaV0JVQ3kxT2IyTTQxT0lDT21idnhydFE5Ri9rR3dvV0E1RS9wZk4rZ3p0citiNUU5T2ZMeWF6TGoKaXc9PQo9bHR3WAotLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS0K</guid>
            <category>RSS</category><category>Deshitifaction</category>
            <comments>https://graphics.social/@thefranke</comments>
        </item>
        
        <item>
            <title>Next-gen challenges for mobile game rendering</title>
            <description>
                
                
                &lt;img src=&quot;https://www.tobias-franke.eu/publications/franke25dublinml/franke25dublinml.jpg&quot; alt=&quot;&quot;&gt;&lt;br/&gt;&lt;br/&gt;
                Authors: Tobias Alexander Franke&lt;br/&gt;
                In: Machine Learning Meetup Dublin&lt;br/&gt;
                
                &lt;p&gt;Mobile hardware vendors are facing a growing number of game studios that seek to push AAA content on mobile platforms.&lt;/p&gt;

&lt;p&gt;In this talk I present an overview of the requirements for next-gen games on mobile: AAA asset sizes, raytracing, volumetric lighting, transparency for foliage, global illumination, high-detail characters, elaborate postprocessing and supersampling, leveraging on-chip CPUs, GPUs and NPUs.&lt;/p&gt;

&lt;p&gt;Each one of these categories needs to overcome certain hardware limitations before being feasible on mobile platforms. The goal is to focus on research of highly adaptive and scalable alternatives to algorithms currently in use in AAA games.&lt;/p&gt;

                
                
                &lt;p&gt;Links:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.adaptcentre.ie/news-and-events/machine-learning-meetup-at-sandbox-vr-for-dublin-tech-week/&quot;&gt;ADAPT Centere article&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://web.archive.org/web/20250609091021/https://www.adaptcentre.ie/news-and-events/machine-learning-meetup-at-sandbox-vr-for-dublin-tech-week/&quot;&gt;Wayback Machine Archive&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
                
            </description>
            <pubDate>Mon, 09 Jun 2025 00:00:00 +0200</pubDate>
            <link>https://www.tobias-franke.eu/publications/franke25dublinml/index.html</link>
            <guid>LS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS0KCmlRRlBCQUFCQ0FBNUZpRUUxVWxUbHFPVUY1a3NFZCszT0RETU5qa2l2RThGQW1vcHpwTWJGSUFBQUFBQUJBQU8KYldGdWRUSXNNaTQxS3pFdU1USXNNQ3d6QUFvSkVEZ3d6RFk1SXJ4UFNob0gvMlpQc0Fnem0wZFdpOGFnL3JLcgpVRE02RzRUQjUyV2xJSkV5LzlkbEtBR0pJQmZjcUZrNzZoN1VxYVg4c1BnNkNhbEM0ZWVnLy9zSWV4TzNNa1R1Ck1ndHc0RVNKWnNQclQ2MXF4TUViTHM1TGM4bVZLaVBPL1NSOWlEajR2UjNzQThUYlhBQUZRZ242RDFTdmFEcUEKRFp2VXNTK2taVzVUOGJSQk4vVy94Uno3aGsxWUw2UEtNcjkxQU95b2FuRUVXQkZycjRWTVJ2bVZ1YlEzSlpNMQphVElvTXpSUU51WWFHb050eGtDUnJCTFEzVHdlellHOFd0ajkxYTVxTzdrODQ5MytncEgxYnBHVWtvSUVmL3U5CmoyZWhNRWNYOXdOS2JBaHFkOGQxVm9kOGJmVzhWWHpVeVB3c1Y3UVY5Q1RBYTFQazRpODJLQ2lybTh4WlhwVTcKVWlVPQo9NnBETQotLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS0K</guid>
            <category>Talk</category><category>Publication</category>
            <comments>https://graphics.social/@thefranke</comments>
        </item>
        
        <item>
            <title>Traditional and Neural Order-Independent Transparency</title>
            <description>
                
                
                &lt;img src=&quot;https://www.tobias-franke.eu/publications/tsopouridis25tnoit/tsopouridis25tnoit.jpg&quot; alt=&quot;&quot;&gt;&lt;br/&gt;&lt;br/&gt;
                Authors: Grigoris Tsopouridis et al. and Tobias Alexander Franke&lt;br/&gt;
                In: Proceedings of Eurographics 2025&lt;br/&gt;
                
                &lt;p&gt;Order independent transparency (OIT) is a technique in computer graphics that allows for accurate rendering of transparent objects without the need to sort them in a specific order based on their depth. Traditional transparency methods often suffer from artifacts and inaccuracies due to this sorting process, especially in complex scenes with many overlapping transparent surfaces. OIT is important because it provides a more visually correct representation of transparent materials, ensuring that colors mix accurately and that all elements are rendered consistently, regardless of their draw order. This enhances realism in applications such as video games, simulations, and visual effects in films. The tutorial will provide an overview of traditional (exact, approximate and hybrid) and deep learning approaches to OIT and examine their scope, performance and accuracy.&lt;/p&gt;

                
                
                &lt;p&gt;Links:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://diglib.eg.org/bitstream/handle/10.2312/egt20251001/tut1002.pdf&quot;&gt;Content&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://diglib.eg.org/bitstream/handle/10.2312/egt20251001/transparency_tutorial_eg2025.pptx&quot;&gt;Slides&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://diglib.eg.org/handle/10.2312/egt20251001&quot;&gt;EG DigLib&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://eg25.cs.ucl.ac.uk&quot;&gt;EG 2025&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
                
            </description>
            <pubDate>Mon, 12 May 2025 00:00:00 +0200</pubDate>
            <link>https://www.tobias-franke.eu/publications/tsopouridis25tnoit/index.html</link>
            <guid>LS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS0KCmlRRlBCQUFCQ0FBNUZpRUUxVWxUbHFPVUY1a3NFZCszT0RETU5qa2l2RThGQW1vcHpwTWJGSUFBQUFBQUJBQU8KYldGdWRUSXNNaTQxS3pFdU1USXNNQ3d6QUFvSkVEZ3d6RFk1SXJ4UGFhMEgvMHlCNGdkaG1iUDZjWnZIbk82RApVNHkzZXdabEpsR0ZJMVFhUlU3U0JrRGNJVVR0ZGttMGR2RU5ZbW1HRVkwRWdDN0dXbm1CcTRnRUF6bFAraFRwCmFIaWFucjBNSDcrc3gwb1NZRnJJM0Y5bGhKRlNzQWhIVjBRZDJPOCtkYzgxTm9pTnFZdXdhRmtLaXJFcG44L3EKU3dkOWhBbWhEUWFxdDBqZXVMeTg5Qm1HZVRselFUbCthbUIyb0tPd3M3dFc3SWdTMTBNL012TDEyUDA0eHpsbwpFekJ1UXFxZzgwV0JvOU9HWE1kQWxFYVZzazVqN25QbGpmRzQycldIWng2T0ViRjJKK0V3YkdKc1JieVoxNFlSCndGSmViMEZlaVZLOFd1ZjVTN1pIYU9SdGpJM2J1MmUrcWxYVWlWYytPS1VuQU44TmRYNWl1cHRkTUY5MWxZbWwKM05vPQo9cDRiYQotLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS0K</guid>
            <category>Conference</category><category>Publication</category><category>Tutorial</category>
            <comments>https://graphics.social/@thefranke</comments>
        </item>
        
        <item>
            <title>A read-it-later service for RSS purists</title>
            <description>
                
                
&lt;div class=&quot;figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_05_robo-readable.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_05_robo-readable.png&quot; alt=&quot;&quot; title=&quot;&quot;&gt;
    
    &lt;/a&gt;
    
    
&lt;/div&gt;

&lt;h1 id=&quot;leave-no-stylesheet-alive&quot;&gt;Leave no stylesheet alive&lt;/h1&gt;

&lt;p&gt;I read a lot. I read a lot of papers for instance. They are easy to read, because they have a common format, by which I mean their appearance. This is achieved in the academic community largely by separating markup, the language that describes what - in an article - constitues a headline, a paragraph and so on, and the formatting of thosed marked up properties, through &lt;a href=&quot;https://www.siggraph.org/preparing-your-content/author-instructions/&quot;&gt;a common Latex template such as the SIGGRAPH acmtog&lt;/a&gt; or using common formatters for simpler markup languages such as Markdown. This has one tremendous benefit: Reading a lot of articles is easy, because they all look the same.&lt;/p&gt;

&lt;p&gt;I have rambled about &lt;a href=&quot;/log/2019/08/07/in-praise-of-syndication.html&quot;&gt;the terrible state of the web&lt;/a&gt; before, &lt;a href=&quot;/log/2024/04/13/redirect-everything.html&quot;&gt;twice actually&lt;/a&gt;. Most webpages have become &lt;strong&gt;unreadable&lt;/strong&gt;, which describes a state where it is hard or impossible to find the actual content of a webpage amongst ads, banners, popups or other annoyances. This is especially disappointing if one opened said webpage with the expecation to just read some piece of text matching a headline.&lt;/p&gt;

&lt;p&gt;It is ironic that HTML - being &lt;strong&gt;the&lt;/strong&gt; markup language to differntiate content from style - has been abused by designers so much that a bunch of tools emerged to extract the content and throw away the style. Over a decade ago, when this problem became apparent, it was addressed with &lt;a href=&quot;https://en.wikipedia.org/wiki/Readability_(service)&quot;&gt;a bookmarklet called Readability&lt;/a&gt; reformatting webpages to reduce clutter, followed by browser implementations which added so called &lt;strong&gt;reader views&lt;/strong&gt;, for instance in &lt;a href=&quot;https://support.mozilla.org/en-US/kb/firefox-reader-view-clutter-free-web-pages&quot;&gt;Firefox&lt;/a&gt; and &lt;a href=&quot;https://support.brave.com/hc/en-us/articles/360045031392-What-is-Speedreader&quot;&gt;Brave&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now &lt;strong&gt;Read-it-later&lt;/strong&gt; services solve two problems at once and make articles offline-readable: &lt;a href=&quot;https://www.instapaper.com/&quot;&gt;Instapaper&lt;/a&gt;, &lt;a href=&quot;https://getpocket.com&quot;&gt;Pocket&lt;/a&gt; and the FOSS self-hostable &lt;a href=&quot;https://wallabag.org/&quot;&gt;Wallabag&lt;/a&gt; download and store bookmarked articles after running them through a readability tool, so that eventually all articles have the same layout and same formatting.&lt;/p&gt;

&lt;h1 id=&quot;motivation&quot;&gt;Motivation&lt;/h1&gt;

&lt;p&gt;As someone who reads a lot for research (papers, blog posts, news articles etc.) I wanted to have the simplest possible version of such a read-it-later service: All my bookmarked articles look the same and are readable offline  .&lt;/p&gt;

&lt;h3 id=&quot;failure-1&quot;&gt;Failure 1&lt;/h3&gt;

&lt;p&gt;My first experiment was to manually store PDFs of the reader view in Firefox. That&amp;rsquo;s a lot of manual labor though, and syncing across devices requires to store all PDFs on some cloud service. It&amp;rsquo;s also not easy to use on the go.&lt;/p&gt;

&lt;h3 id=&quot;failure-2&quot;&gt;Failure 2&lt;/h3&gt;

&lt;p&gt;My second experiment was trying out &lt;a href=&quot;https://archivebox.io/&quot;&gt;Archivebox&lt;/a&gt;, a neat FOSS project that streamlines archiving. Archivebox will not just download a copy of a webpage, but run it through several polishing tools. One of them is a readability library, which stores a PDF and HTML version alongside the original article. However, I found Archivebox hard to deploy on a webhost with just PHP on it (basically the things you get for free) and it produces quite bit of extra data I don&amp;rsquo;t need, but otherwise ticks almost all the boxes.&lt;/p&gt;

&lt;h3 id=&quot;failure-3&quot;&gt;Failure 3&lt;/h3&gt;

&lt;p&gt;My third experiment ended in &lt;a href=&quot;https://github.com/Ranchero-Software/NetNewsWire/issues/3023&quot;&gt;a personal frustration of mine&lt;/a&gt;: My workflow for reading anything I am interested in is by adding a star in my RSS reader to an article, which necessitates that anything I want to read is somehow a subscription or feed I can add to my RSS reader, and that isn&amp;rsquo;t true for individual articles I stumble across. Sometimes this isn&amp;rsquo;t even true for articles on blogs, where the blog is either too huge to subscribe to for just this one article, or (a sad occurunce these days) it doesn&amp;rsquo;t have RSS at all, even though that is trivial to add.&lt;/p&gt;

&lt;div class=&quot;figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_05_reeder-instpaper.jpg&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_05_reeder-instpaper.jpg&quot; alt=&quot;&quot; title=&quot;&quot;&gt;
    
    &lt;/a&gt;
    
    
&lt;/div&gt;

&lt;p&gt;Before switching to the most excellent &lt;a href=&quot;https://netnewswire.com/&quot;&gt;NetNewsWire&lt;/a&gt; reader, I was fond of &lt;a href=&quot;https://reederapp.com/&quot;&gt;Reeder&lt;/a&gt;, which includes the functionality to add read-it-later services like Instapaper as accounts, showing the same interface for both RSS and read-it-later subscriptions. This, essentially, made Reeder my one-stop reading application, indifferent to whether the article came from a single URL I wanted to read or from any of my RSS feeds. Whenever there was an article I wanted to read, I&amp;rsquo;d simply push it to an Instapaper or Pocket account and then fetch it via Reeder. However, this had one major downside: I&amp;rsquo;d use a centralized service that gradually became a huge data dump, as more and more articles ended up in those accounts.&lt;/p&gt;

&lt;h3 id=&quot;failure-4&quot;&gt;Failure 4&lt;/h3&gt;

&lt;p&gt;My fourth experiment involved RSS itself. NetNewsWire, like most RSS readers, is a minimalist, pure RSS reader, which means anything you want to &lt;strong&gt;star&lt;/strong&gt; inside the application has to come from an RSS feed you can subscribe to.&lt;/p&gt;

&lt;p&gt;Both &lt;a href=&quot;https://getpocket.com&quot;&gt;Pocket&lt;/a&gt; and &lt;a href=&quot;https://wallabag.org/&quot;&gt;Wallabag&lt;/a&gt; allow you to subscribe to your personal collection of articles via RSS, essentially treating your article collection as some kind of blog with new posts appearing whenever you add an article. However, this approach had the same downside as when I was using Reeder: The read-it-later accounts become a huge dump after a while, because I was not &lt;em&gt;really&lt;/em&gt; interessted in either Pocket or Wallabag, only in redirecting saved articles to my RSS reader. This solution however was extremly close to optimal, with just the exception of me logging into my accounts in regular intervals to delete everything.&lt;/p&gt;

&lt;h1 id=&quot;requirements&quot;&gt;Requirements&lt;/h1&gt;

&lt;p&gt;From the four failures I learned that what I wanted was actually this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Send a URL to a script, which runs it through readability and attaches the resulting content directly to an RSS feed. Ideally, I can create different feeds for different collections, for example one for work and for personal interests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Specifically, I want to:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Store single articles in a RSS reader application&lt;/li&gt;
  &lt;li&gt;Avoid third-party read-it-later services such as &lt;a href=&quot;https://getpocket.com&quot;&gt;Pocket&lt;/a&gt;, &lt;a href=&quot;https://www.instapaper.com&quot;&gt;Instapaper&lt;/a&gt; or &lt;a href=&quot;https://wallabag.org/&quot;&gt;Wallabag&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Minimize the amount of necessary apps for reading articles&lt;/li&gt;
  &lt;li&gt;Get rid of accounts and not sign up to anything&lt;/li&gt;
  &lt;li&gt;Read articles (offline) in a readable format, but not categorize or store them indefinitely&lt;/li&gt;
  &lt;li&gt;Synchronize stored articles to multiple devices&lt;/li&gt;
  &lt;li&gt;Optionally be able to self-host the whole architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The outcome of this is a project I call &lt;a href=&quot;https://github.com/thefranke/rss-librarian&quot;&gt;RSS-Librarian&lt;/a&gt;, a read-it-later service for RSS purists. RSS-Librarian solves all of these bullet points with a single, self-hostable PHP file that extracts content from URLs using &lt;a href=&quot;https://www.fivefilters.org/&quot;&gt;a readability service&lt;/a&gt; and adds what remains as new entries into a personal RSS feed, without requiring special libraries, a database or user accounts.&lt;/p&gt;

&lt;h1 id=&quot;how-it-works&quot;&gt;How it works&lt;/h1&gt;

&lt;p&gt;You can drop &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;librarian.php&lt;/code&gt; onto any host that supports PHP with no other requirements. RSS-Librarian has two parameters: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;librarian.php?id=HASH&amp;amp;url=SOMEPAGE&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; is a random ID for a personal feed. If this parameter is not supplied, RSS-Librarian will generate a new one and add a feed file corresponding to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; into the subfolder &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feeds/&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;url&lt;/code&gt; is a URL you submit to RSS-Librarian, whose content will be extracted and added to the feed coressponding to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;url&lt;/code&gt; posted to RSS-Librarian, the extracted content will be added to a RSS file derived from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; - if it exists in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feeds/&lt;/code&gt; folder. If it does not exist, it will be generated and written. The RSS file will store a maximum of 100 entries before removing the oldest one and adding the new one.&lt;/p&gt;

&lt;h1 id=&quot;an-example-use-case&quot;&gt;An example use-case&lt;/h1&gt;

&lt;p&gt;I will be using the &lt;a href=&quot;http://alternator.hstn.me/librarian.php&quot;&gt;this demo instance of RSS-Librarian&lt;/a&gt; in combination with &lt;a href=&quot;https://netnewswire.com/&quot;&gt;NetNewsWire&lt;/a&gt; on MacOS (&lt;a href=&quot;https://netnewswire.com/NetNewsWire.zip&quot;&gt;MacOS App&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I can recommend &lt;a href=&quot;https://netnewswire.com/&quot;&gt;NetNewsWire&lt;/a&gt; on iOS (&lt;a href=&quot;https://apps.apple.com/us/app/netnewswire-rss-reader/id1480640210&quot;&gt;App Store Link&lt;/a&gt;), &lt;a href=&quot;https://github.com/seazon/FeedMe&quot;&gt;FeedMe&lt;/a&gt; on Android (&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.seazon.feedme&quot;&gt;Play Store Link&lt;/a&gt;, &lt;a href=&quot;https://github.com/seazon/FeedMe/releases&quot;&gt;APK&lt;/a&gt;) and &lt;a href=&quot;https://nodetics.com/feedbro/&quot;&gt;FeedBro&lt;/a&gt; for various Browsers (&lt;a href=&quot;https://addons.mozilla.org/firefox/addon/feedbroreader/&quot;&gt;Firefox Addon&lt;/a&gt;, &lt;a href=&quot;https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa&quot;&gt;Brave/Chromium Addon&lt;/a&gt;). They all download offline copies of RSS feeds to your device ready for access even when disconnected completely.&lt;/p&gt;

&lt;h3 id=&quot;step-1-create-your-personal-feed&quot;&gt;Step 1: Create your personal feed&lt;/h3&gt;

&lt;p&gt;Assume we have &lt;a href=&quot;https://ohshitgit.com/&quot;&gt;the following article&lt;/a&gt; we want to store for later reading. First go to &lt;a href=&quot;http://alternator.hstn.me/librarian.php&quot;&gt;the RSS-Librarian instance&lt;/a&gt;. You&amp;rsquo;ll be greeted by the follwing interface.&lt;/p&gt;

&lt;div class=&quot;basic-figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_05_rss-librarian-step-1.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_05_rss-librarian-step-1.png&quot; alt=&quot;The view of RSS-Librarian on first visit&quot; title=&quot;The view of RSS-Librarian on first visit&quot;&gt;
    
    &lt;/a&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;The view of RSS-Librarian on first visit&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;This instance is currently hosting 8 other feeds. Paste the article URL &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://ohshitgit.com/&lt;/code&gt; into the empty field and press &lt;em&gt;Add to feed&lt;/em&gt;. Because you are a new user, RSS-Librarian will now generate a random, unique user ID for you.&lt;/p&gt;

&lt;div class=&quot;basic-figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_05_rss-librarian-step-2.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_05_rss-librarian-step-2.png&quot; alt=&quot;RSS-Librarian after adding the first link&quot; title=&quot;RSS-Librarian after adding the first link&quot;&gt;
    
    &lt;/a&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;RSS-Librarian after adding the first link&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;On the next page you will see &lt;strong&gt;two important things&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Your &lt;em&gt;personal URL&lt;/em&gt;: Store this URL in your bookmarks! This allows you to add more articles to your &lt;em&gt;personal RSS feed&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;Your &lt;em&gt;personal RSS feed&lt;/em&gt;: This is your feed that you can subscribe to with your RSS reader application. It is unique and can only be managed with the &lt;em&gt;personal URL&lt;/em&gt; above.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;It is important&lt;/strong&gt; - after adding your first link - to bookmark your &lt;em&gt;personal URL&lt;/em&gt; somewhere so you can keep adding links to your feed (instead of creating a new one accidentally)!&lt;/p&gt;

&lt;h3 id=&quot;step-2-open-your-personal-url&quot;&gt;Step 2: Open your personal URL&lt;/h3&gt;

&lt;p&gt;For this demo, the &lt;em&gt;personal URL&lt;/em&gt; now links to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://alternator.hstn.me/librarian.php?id=ff4d8b605c2cffacd19639af2a1d3ff8712021d66431de86f425e0279a1e768a&lt;/code&gt;. RSS-Librarian, on first use, will create a random hash (in our case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ff4d...768a&lt;/code&gt;). This hash will be used to store new URLs to your &lt;em&gt;personal RSS feed&lt;/em&gt;, which is derived from that hash. You can think of this as your user ID.&lt;/p&gt;

&lt;p&gt;Open the &lt;em&gt;personal URL&lt;/em&gt; with a browser again.&lt;/p&gt;

&lt;div class=&quot;basic-figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_05_rss-librarian-step-3.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_05_rss-librarian-step-3.png&quot; alt=&quot;RSS-Librarian personal URL&quot; title=&quot;RSS-Librarian personal URL&quot;&gt;
    
    &lt;/a&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;RSS-Librarian personal URL&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;With this page you can add more articles to your &lt;em&gt;personal RSS feed&lt;/em&gt;. Let&amp;rsquo;s &lt;a href=&quot;https://plus.maths.org/content/godel-and-limits-logic&quot;&gt;add another article&lt;/a&gt; to the feed we just created.&lt;/p&gt;

&lt;div class=&quot;basic-figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_05_rss-librarian-step-4.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_05_rss-librarian-step-4.png&quot; alt=&quot;Adding another article to the personal RSS feed&quot; title=&quot;Adding another article to the personal RSS feed&quot;&gt;
    
    &lt;/a&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;Adding another article to the personal RSS feed&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;You can now point your RSS reader to your &lt;em&gt;personal RSS feed&lt;/em&gt; and check out the result. At the bottom of the page you can find additional tools:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Feed bookmarklet&lt;/em&gt;: A bookmarklet you can use instead of the &lt;em&gt;personal URL&lt;/em&gt;. This will add the currently open page to your &lt;em&gt;personal RSS feed&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Feed preview&lt;/em&gt;: If you have no RSS viewer at hand you can preview your &lt;em&gt;personal RSS feed&lt;/em&gt; with this page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;step-3-add-it-to-an-rss-reader-of-your-choice&quot;&gt;Step 3: Add it to an RSS reader of your choice&lt;/h3&gt;

&lt;p&gt;Let&amp;rsquo;s try this out with NetNewsWire on MacOS. Copy the link &lt;em&gt;personal RSS feed&lt;/em&gt;, press the + symbol in NetNewsWire and add the URL.&lt;/p&gt;

&lt;div class=&quot;basic-figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_05_rss-librarian-step-5.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_05_rss-librarian-step-5.png&quot; alt=&quot;Adding the personal RSS feed to NetNewsWire&quot; title=&quot;Adding the personal RSS feed to NetNewsWire&quot;&gt;
    
    &lt;/a&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;Adding the personal RSS feed to NetNewsWire&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;After adding the feed, your stored articles appear in your RSS reader. Most RSS readers will automatically download all articles in a feed, meaning you also have a stored copy offline on the go, for instance when reading on a plane without WiFi.&lt;/p&gt;

&lt;div class=&quot;basic-figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_05_rss-librarian-step-6.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_05_rss-librarian-step-6.png&quot; alt=&quot;A view of the stored articles&quot; title=&quot;A view of the stored articles&quot;&gt;
    
    &lt;/a&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;A view of the stored articles&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;If you click the title of the article, you will end up on the original URL (in the above sample our &lt;a href=&quot;https://ohshitgit.com/&quot;&gt;first article&lt;/a&gt;). If you open the feed itself - &lt;em&gt;RSS-Librarian (ff4d)&lt;/em&gt; - you will jump back to your &lt;em&gt;personal URL&lt;/em&gt; where you can add more articles to the feed.&lt;/p&gt;

&lt;p&gt;If you do not have an RSS reader around, you can also click on &lt;em&gt;Feed preview&lt;/em&gt; in the tools section of your &lt;em&gt;personal URL&lt;/em&gt; to get a quick web-based view of the feed.&lt;/p&gt;

&lt;div class=&quot;basic-figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_05_rss-librarian-step-7.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_05_rss-librarian-step-7.png&quot; alt=&quot;Viewing a feed preview using feedreader.xyz&quot; title=&quot;Viewing a feed preview using feedreader.xyz&quot;&gt;
    
    &lt;/a&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;Viewing a feed preview using feedreader.xyz&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;And that is pretty much it. By visiting your &lt;em&gt;personal URL&lt;/em&gt; on a RSS-Librarian instance, you can add more  articles to your &lt;em&gt;personal RSS feed&lt;/em&gt; and they will become easy to read and storable offline by your reader software. You can subscribe to your &lt;em&gt;personal RSS feed&lt;/em&gt; with as many readers as you want and even share that feed with others (which however will let them add articles to it too).&lt;/p&gt;

&lt;h1 id=&quot;deploying-your-own-instance&quot;&gt;Deploying your own instance&lt;/h1&gt;

&lt;p&gt;Simply get a copy of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;librarian.php&lt;/code&gt; from the &lt;a href=&quot;https://github.com/thefranke/rss-librarian&quot;&gt;Github repository&lt;/a&gt; and put it on any VPS, Raspberry Pi or other webserver that has PHP. There are no other requirements. Create a directory called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feeds&lt;/code&gt; right next to the file and give the webserver write access to that folder.&lt;/p&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;librarian.php&lt;/code&gt; you can configure some globals:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$g_max_items&lt;/code&gt; is the number of articles stored per feed. Adding a new article beyond that number will remove the last one in the feed. Reduce this number if you&amp;rsquo;re tight on space.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$g_dir_feeds&lt;/code&gt; is the directory where user feeds are stored. Rename this if you want to use a different directory on your instance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;future-work&quot;&gt;Future work&lt;/h1&gt;

&lt;p&gt;There are a bunch of things I want to implement.&lt;/p&gt;

&lt;h3 id=&quot;protected-shareable-feeds&quot;&gt;Protected shareable feeds&lt;/h3&gt;

&lt;p&gt;Currently the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; given to RSS-Librarian is used to create a feed in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feeds/&lt;/code&gt; directory that has the same name. This means that sharing this feed with others will allow them to access the &lt;em&gt;personal URL&lt;/em&gt; as well and add articles. I&amp;rsquo;d like to change this behavior so that the feed&amp;rsquo;s file name is generated by hashing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; again. This way, the user id and the feed id are separated but correlated in one direction: The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; can be used to generate the corresponding feed file name, but not vice versa. This comes at the cost of usability: The feed currently links back to it&amp;rsquo;s &lt;em&gt;personal URL&lt;/em&gt;, which means you can quickly jump to the place where you can add more articles. This would need to be removed to not give away that page.&lt;/p&gt;

&lt;h3 id=&quot;remove-fivefilters-dependency&quot;&gt;Remove FiveFilters dependency&lt;/h3&gt;

&lt;p&gt;RSS Librarian sends articles to FiveFilters, which extracts them into single RSS entries and then appends that entry to the locally hosted feed. This is not optimal, because the feed generation is essentially centralized and not independent. Instead, RSS-Librarian needs to do the extraction locally via the FiveFilters &lt;a href=&quot;https://github.com/fivefilters/readability.php&quot;&gt;Readbility library for PHP&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;maintenance&quot;&gt;Maintenance&lt;/h3&gt;

&lt;p&gt;Every user can add a new feed at any time and as many as they want. An instance can quickly create a huge dump inside the feeds folder full of garbage feeds that are abandoned because users forgot to bookmark their &lt;em&gt;personal URL&lt;/em&gt;, or because they lost the link somehow later.&lt;/p&gt;

&lt;p&gt;On every new article added RSS-Librarian could run through the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feeds/&lt;/code&gt; folder and simply delete feeds that are too old, for instance those that had no new items added to them for more than 6 months. This is not an issue if the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; gets re-used again: RSS-Librarian recreates files for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;s when another article URL is added.&lt;/p&gt;

&lt;h3 id=&quot;share-feature-for-mobile&quot;&gt;Share-feature for mobile&lt;/h3&gt;

&lt;p&gt;App based read-it-later services tie into the OS and let you share an article with the app, which is the default way to add an article quickly to the read-it-later list. RSS-Librarian however has no app and uses a webpage instead, which makes quickly adding an article to a &lt;em&gt;personal RSS feed&lt;/em&gt; tedious.&lt;/p&gt;

&lt;p&gt;Currently, one can get to the &lt;em&gt;personal URL&lt;/em&gt; by simply opening the &lt;em&gt;personal RSS feed&lt;/em&gt; URL. Ideally however I&amp;rsquo;d like a simple share button that adds some URL being shared from any app.&lt;/p&gt;

&lt;h3 id=&quot;main-instance&quot;&gt;Main instance&lt;/h3&gt;

&lt;p&gt;An RSS-Librarian demo instance for testing &lt;a href=&quot;https://alternator.hstn.me/library.php&quot;&gt;is currently hosted here&lt;/a&gt;. The instance has a self-signed certificate and therefore many RSS readers will run into issues. Using HTTP instead is a no-go though. I want to eventually host a reliable main instance.&lt;/p&gt;

&lt;h1 id=&quot;the-great-crusade-against-web-enshitification&quot;&gt;The Great Crusade against Web-Enshitification&lt;/h1&gt;

&lt;p&gt;I&amp;rsquo;ve been using RSS-Librarian just for myself for about half a year now and it&amp;rsquo;s been a delight so far, given how compact the code is. I created two feeds for myself: One for articles I&amp;rsquo;m interested in personally, and one for stuff I need for work. I subscribe to my work feed using my personal devices and with &lt;a href=&quot;https://nodetics.com/feedbro/&quot;&gt;FeedBro&lt;/a&gt; on my work machine, which means I can easily add and read articles I find for work everywhere, whereas the feed containing articles I&amp;rsquo;m just interested in personally is only in my feed readers on my own devices. It&amp;rsquo;s all neatly separated and easy to manage.&lt;/p&gt;

&lt;p&gt;Of course I wrote this for myself initially, so if you find it weird, confusing, chaotic, or you&amp;rsquo;d like to see a feature, please &lt;a href=&quot;https://github.com/thefranke/rss-librarian/issues&quot;&gt;open an issue&lt;/a&gt; or let me know otherwise.&lt;/p&gt;

                
                
                
                
            </description>
            <pubDate>Sun, 28 Apr 2024 00:00:00 +0200</pubDate>
            <link>https://www.tobias-franke.eu/log/2024/04/28/a-reader-service-for-rss-purists.html</link>
            <guid>LS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS0KCmlRRlBCQUFCQ0FBNUZpRUUxVWxUbHFPVUY1a3NFZCszT0RETU5qa2l2RThGQW1vcHpwRWJGSUFBQUFBQUJBQU8KYldGdWRUSXNNaTQxS3pFdU1USXNNQ3d6QUFvSkVEZ3d6RFk1SXJ4UG5Dd0gvMG84OXBGSHo3NzNZYk03b2hnWQpnOTlIRXdHakF4MlA0NmpKUFJTNW53V3BYVTRjYkhHRWx2NDVsQ3NoMVpWZGdTbUwzSUZBYTRNWmMwTC9jMnQwClZ6MU5LMXpiOFlqNm5Hd0tlSXptY0JGWVNZYllCOXhFM3gvWkNzNDBqdU8rVDFZZ0FaS0tLa2Z0RC9uVm9rRk8KemYyczF2UlliTzdkbWQrWWNaOVAyeW11aUlMRlA1V09vK2Z4RFJTd09tblYrUlBweWZEUS92SFFKaVZ2QW4ySQorSHNmdTVmcDJ4dGJvRHVwTVI1ek5rdXZoemx6NE1ETmovQXRNU2NNY1lOYzJRRlZWdkcvTjkyODdDbmE0VTN6CjZFU1ZyNExmKzJXaTh0cjZFUFVEOEJFRWJmR2hpKy9qUGduOHJOLzFOLytzdXZxVkZLd05rWXozWmlXOWZOdG8KU2RRPQo9dDlVUwotLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS0K</guid>
            <category>RSS</category><category>Notes</category>
            <comments>https://graphics.social/@thefranke</comments>
        </item>
        
        <item>
            <title>Redirect everything</title>
            <description>
                
                
&lt;div class=&quot;figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_04_web-intro-lol.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_04_web-intro-lol.png&quot; alt=&quot;&quot; title=&quot;&quot;&gt;
    
    &lt;/a&gt;
    
    
&lt;/div&gt;

&lt;h1 id=&quot;the-web-is-terrible-redux&quot;&gt;The web is terrible: Redux&lt;/h1&gt;

&lt;p&gt;The screams are getting louder, but the solutions keep getting dumber.&lt;/p&gt;

&lt;p&gt;I know that my background as a graphics programmer is adding heavy bias to my point of view, but I really do not send data-oriented design lectures from Mike Acton to random Electron-app developers because I think their code is slow. Nevertheless, corporate web programmers have collectively given up on the notion that there is something called &lt;em&gt;efficiency&lt;/em&gt;. (No time for a good rant? Go to the &lt;a href=&quot;#tldr&quot;&gt;TL;DR&lt;/a&gt;!)&lt;/p&gt;

&lt;p&gt;Case in point: Most news websites have tiny irrelevant content that hides between full-screen ads, newsletter popups, cookie warnings, autoplaying videos, and Javascript yanking mouse control away from you to add custom scrolling behavior. Look, for instance, at the following &lt;a href=&quot;https://twitter.com/karpathy/status/1435827240286109702&quot;&gt;capture from Andrej Karpathy&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;figure&quot;&gt;
    
    &lt;video class=&quot;video&quot; style=&quot;width:720px;&quot; controls=&quot;&quot;&gt;
      &lt;source style=&quot;width:720px;&quot; src=&quot;/layout/logcache/2024_04_web-nonsense-sample-1.mp4&quot; type=&quot;video/mp4&quot;&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;Browsing the web, 2021&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;Of course this browser-violation is just the tip of the iceberg, as there is more code that is running in the background, streaming ever more useless stuff to the machine. Here is a sample &lt;a href=&quot;https://twitter.com/lunasorcery/status/1277690244725460994&quot;&gt;captured by Lunasorcery&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;figure&quot;&gt;
    
    &lt;video class=&quot;video&quot; style=&quot;width:720px;&quot; controls=&quot;&quot;&gt;
      &lt;source style=&quot;width:720px;&quot; src=&quot;/layout/logcache/2024_04_web-nonsense-sample-2.mp4&quot; type=&quot;video/mp4&quot;&gt;
      Your browser does not support the video tag.
    &lt;/video&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;Imagine getting paid for writing software like this!&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;Think about just how much all of this, per tab, eats into your CPU time for no good reason, heats up the machine and drains battery of your mobile device, even if you do absolutely nothing on that page. Soon mobile 4G won&amp;rsquo;t provide adequate bandwidth to load a simple news article anymore. Part of the problem here is that modern web design is a lazy pieced together conglomerate of hilarious libraries such as the &lt;a href=&quot;https://www.npmjs.com/package/is-odd?activeTab=code&quot;&gt;is-odd node module&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_04_web-is-odd-node.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_04_web-is-odd-node.png&quot; alt=&quot;A modulo operation, web-style!&quot; title=&quot;A modulo operation, web-style!&quot;&gt;
    
    &lt;/a&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;A modulo operation, web-style!&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;The pyramid-scheme continues with an even greater absurdity, also known as the &lt;a href=&quot;https://www.npmjs.com/package/is-even?activeTab=code&quot;&gt;is-even node module&lt;/a&gt;, which relies on - you guessed it - is-odd. To put that into perspective: is-even loads 254 bytes in addition to is-odd loading another 543 bytes for essentially this operation &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(n % 2) == 0&lt;/code&gt; (12 bytes), or &lt;strong&gt;6641% of the necessary bytes to load&lt;/strong&gt;. You can imagine how the rest of your average page looks like. If you believe this is just an elaborate prank or a contrived example, I present to you a sample of this &lt;a href=&quot;https://github.com/micromatch/nanomatch/pull/7/commits/dba3131bcafbfc6c6d009e7d1591427ef7d71d82&quot;&gt;being used in a glob matcher/parser&lt;/a&gt; before someone had the decency to remove it, make a mistake in the process and &lt;a href=&quot;https://github.com/micromatch/nanomatch/pull/7/commits/573338f1f118775f0c16370d989e6079aa2a6c68&quot;&gt;summon the comment from hell&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So here we are, with your browser wading through mountains of garbage, to display a simple piece of text to you. One way to deal with this is to fully embrace Stockholm-syndrome and &lt;a href=&quot;https://noyb.eu/en/pay-or-okay-explained-why-more-and-more-websites-make-you-pay-your-privacy&quot;&gt;just pay the ransom&lt;/a&gt; some of these sites demand from you to make the pain go away. Then there are a variety of tools that have been introduced to combat these issues, ranging from &lt;a href=&quot;https://support.mozilla.org/en-US/kb/firefox-reader-view-clutter-free-web-pages&quot;&gt;reader-views built into browsers&lt;/a&gt;, using &lt;strong&gt;Readability services&lt;/strong&gt; (just read that out loud) such as &lt;a href=&quot;https://wallabag.org/&quot;&gt;Wallabag&lt;/a&gt;, &lt;a href=&quot;https://getpocket.com/saves&quot;&gt;Pocket&lt;/a&gt; or &lt;a href=&quot;https://www.instapaper.com/&quot;&gt;Instapaper&lt;/a&gt;, or simply adding dozens of plugins into your browser to make pages look sane such as &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/&quot;&gt;user scripts like GreaseMonkey&lt;/a&gt; and &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/styl-us/&quot;&gt;user stylesheets like Stylus&lt;/a&gt; to, and I quote:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Redesign your favorite websites&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, all of this trickery will still not get the major problem under control: Browsers need ever more powerful machinery to display the most mundane content to you.&lt;/p&gt;

&lt;p&gt;Never fear, the age of cloud is here! So why not put the browser on &lt;em&gt;a different machine&lt;/em&gt; that is 10x more powerful and &lt;strong&gt;STREAM&lt;/strong&gt; what you see to your machine, that extra-layer will solve it!&lt;/p&gt;

&lt;div class=&quot;figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_04_mighty-browser-alive.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_04_mighty-browser-alive.png&quot; alt=&quot;Architecture Astronaut hard at work&quot; title=&quot;Architecture Astronaut hard at work&quot;&gt;
    
    &lt;/a&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;Architecture Astronaut hard at work&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;Turns out that this idea is still a tad bit too ludicrous to be implemented these days, so lucky for us this project is dead.&lt;/p&gt;

&lt;div class=&quot;figure&quot;&gt;
    
    &lt;a href=&quot;/layout/logcache/2024_04_mighty-browser-dead.png&quot;&gt;
    
        &lt;img src=&quot;/layout/logcache/2024_04_mighty-browser-dead.png&quot; alt=&quot;RIP ... and may you stay dead forver&quot; title=&quot;RIP ... and may you stay dead forver&quot;&gt;
    
    &lt;/a&gt;
    
    
    &lt;span class=&quot;caption&quot;&gt;RIP ... and may you stay dead forver&lt;/span&gt;
    
&lt;/div&gt;

&lt;p&gt;&lt;a id=&quot;tldr&quot;&gt;&lt;/a&gt;
Let&amp;rsquo;s recap where we are right now:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Your browser is as complex as an operating system&lt;/li&gt;
  &lt;li&gt;Browser complexity is the source of many security issues&lt;/li&gt;
  &lt;li&gt;(Corporate) websites abuse browser complexity at every opportunity&lt;/li&gt;
  &lt;li&gt;Even if you counter the abuse with layers and layers of tools, the issue is getting worse over time&lt;/li&gt;
  &lt;li&gt;There is no incentive for cooperations to reverse course&lt;/li&gt;
  &lt;li&gt;New programmers are born into habits of being as inefficient as possible&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;the-alternative&quot;&gt;The alternative&lt;/h1&gt;

&lt;p&gt;My number one suggestion is to leave most of the web entirely and &lt;a href=&quot;/log/2019/08/07/in-praise-of-syndication.html&quot;&gt;pipe as much content as you can through RSS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;However&lt;/em&gt;, that doesn&amp;rsquo;t take care of individual URLs and sites that simply don&amp;rsquo;t have an RSS feed. Every day someone sends me a link to one of the various horrible social media sites that I need to then open in a browser.&lt;/p&gt;

&lt;p&gt;This is where a new open-source trend comes to the rescue: The &lt;a href=&quot;https://github.com/topics/alternative-frontends&quot;&gt;Alternative Frontend&lt;/a&gt; movement. Alternative frontends, as the name suggest, are websites that provide a different frontend to an existing webpage.&lt;/p&gt;

&lt;p&gt;For instance, &lt;a href=&quot;http://invidio.us/&quot;&gt;Invidious&lt;/a&gt; is a popular alternative frontend for Youtube. It provides access to anything on Youtube, however in a much cleaner interface with much less clutter, no ads, zero tracking and directly downloadable MP4s in place of the video streaming.&lt;/p&gt;

&lt;p&gt;Right now there are many frontends to popular websites that extract their content and present it in a much saner way. These include Youtube, Twitter, Reddit, Tiktok, Imgur, Reuters, Quora, IMDB, Medium, Google, Stack Overflow, Fandom Wiki, Snopes&amp;hellip; the list keeps growing, because most of the original pages are terrible.&lt;/p&gt;

&lt;p&gt;A lot of these alternatives will use either public APIs or scraping to fetch the content of the original page and display it to you, but those details are not important.&lt;/p&gt;

&lt;h1 id=&quot;this-way-please&quot;&gt;This way please&lt;/h1&gt;

&lt;p&gt;One key question for me is how to use these frontends in a manner that is not intrusive, easy to use and allows me to open random links and get the best possible result.&lt;/p&gt;

&lt;p&gt;The easiest way I have found is to use a &lt;strong&gt;redirector plugin&lt;/strong&gt; for your browser. These plugins will detect a URL you enter into the browser, match it with a regular expression, and if a match has been found, replace it with another.&lt;/p&gt;

&lt;p&gt;For instance, you might detect the pattern &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://www.youtube.com/*&lt;/code&gt; and replace it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://invidious.fdn.fr/$1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A URL to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw&lt;/code&gt; (the 3Blue1Brown channel) will therefore be redirected to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://invidious.fdn.fr/channel/UCYO_jab_esuFRV4b17AJtAw&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These plugins allow you to add multiple patterns for multiple webpages. I have created a configuration you can simply import without the need to add all these patterns by hand, &lt;a href=&quot;https://gist.github.com/thefranke/d6a8137e7bf8c837b64621a3a1f9b9b9&quot;&gt;which is available here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;redirector-plugins-for-firefoxlibrewolftorbrowser&quot;&gt;Redirector plugins for Firefox/Librewolf/Torbrowser&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/redirector&quot;&gt;Redirector&lt;/a&gt; - General purpose redirector you can customize yourself&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/libredirect&quot;&gt;LibRedirect&lt;/a&gt; - Redirector to popular alterantive frontends, highly configurable&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/privacy-redirect&quot;&gt;Privacy Redirect&lt;/a&gt; - Redirector to popular alternative frontends, but limited in scope&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;redirector-plugins-for-safari&quot;&gt;Redirector plugins for Safari&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://apps.apple.com/de/app/redirect-web-for-safari/id1571283503&quot;&gt;Redirect Web for Safari&lt;/a&gt; - General purpose redirector you can customize yourself (can load exported Firefox Redirector JSON)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://apps.apple.com/de/app/privacy-redirect/id1578144015&quot;&gt;Privacy Redirect&lt;/a&gt; - Redirector to popular alternative frontends, but limited in scope&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;load-balancing&quot;&gt;Load-Balancing&lt;/h1&gt;

&lt;p&gt;Many of the alternatives are hosted privately by volunteers and are called &lt;em&gt;instances&lt;/em&gt;, which means there isn&amp;rsquo;t one URL for one alternative, but many. This decentralization acts as a safety net: Banning one doesn&amp;rsquo;t bring down the service itself and distributes the load.&lt;/p&gt;

&lt;p&gt;As an example, there are many &lt;a href=&quot;https://api.invidious.io/&quot;&gt;Invidious instances available&lt;/a&gt;. However, using one of them exclusively has two downsides:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;It might stop working, get banned, rate-limited or shut down&lt;/li&gt;
  &lt;li&gt;If many users pile on one instance, it will be overloaded and most likely rate-limited more quickly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To solve this issue, ideally recognizing a pattern should redirect to a &lt;em&gt;random instance&lt;/em&gt; instead of a fixed one, so that the load distributes equally across all hosted instances and prevents cases of rate-limiting or banning of an instance. This would need two things: For each alternative frontend a list of all available instances that is up to date, and the ability to forward to  randomly selected URLs. The forwarding though is the problem.&lt;/p&gt;

&lt;p&gt;A solution to this comes in the form of redirecting-gateways such as &lt;a href=&quot;https://github.com/benbusby/farside&quot;&gt;Farside&lt;/a&gt;. It&amp;rsquo;s &lt;a href=&quot;https://farside.link/&quot;&gt;a simple page&lt;/a&gt; that lists frontends and their instances. For each frontend, there is one link at the top of each section which will forward you randomly to one of the instances. You can configure your redirector to forward your URL to use those as targets instead.&lt;/p&gt;

&lt;p&gt;As an example, instead of redirecting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://www.youtube.com/*&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://invidious.fdn.fr/$1&lt;/code&gt;, you may want to redirect it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://farside.link/invidious/$1&lt;/code&gt;, which will select a random Invidious instance and attach your $1 parameters to it.&lt;/p&gt;

&lt;p&gt;I provide a simplified, self-hostable redirector-gateway &lt;a href=&quot;http://github.com/thefranke/tzeentch&quot;&gt;called Tzeentch&lt;/a&gt; which is heavily inspired by Farside, but simpler to deploy. You can &lt;a href=&quot;https://alternator.hstn.me&quot;&gt;check out a demo instance here&lt;/a&gt;. It also adds a feature absent in Farside, which is &lt;a href=&quot;https://alternator.hstn.me/?_redirector_config&quot;&gt;to create a ready-made configuration for your redirector&lt;/a&gt; for all the frontends you select.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/libredirect&quot;&gt;LibRedirect&lt;/a&gt; on the other hand maintains an internal list of instances where you can select one or more favorite ones that it chooses from randomly, but this is connected to a bit of manual labor every now and then.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;The battle for a cleaner, saner and less intrusive web is lost.&lt;/p&gt;

&lt;p&gt;Alternative frontends remove the need for a barrage of plugins to sanitize the web, are privacy-friendly and just generally provide a better experience. Redirector plugins using gateways which distribute the requests among frontend-instances will ensure decentralization and reduce the likelyhood of instances getting rate-limited or banned.&lt;/p&gt;

&lt;p&gt;I highly encourage you to &lt;strong&gt;redirect everything&lt;/strong&gt; and check out this way of browsing! Just take a look at &lt;a href=&quot;https://libreddit.eu.org/&quot;&gt;LibReddit&lt;/a&gt; and compare it with Reddit itself, for instance on a phone. Ask yourself: Is this better than the original?&lt;/p&gt;

                
                
                
                
            </description>
            <pubDate>Sat, 13 Apr 2024 00:00:00 +0200</pubDate>
            <link>https://www.tobias-franke.eu/log/2024/04/13/redirect-everything.html</link>
            <guid>LS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS0KCmlRRlBCQUFCQ0FBNUZpRUUxVWxUbHFPVUY1a3NFZCszT0RETU5qa2l2RThGQW1vcHpwRWJGSUFBQUFBQUJBQU8KYldGdWRUSXNNaTQxS3pFdU1USXNNQ3d6QUFvSkVEZ3d6RFk1SXJ4UFZSRUgvalNRVUpGRStoSWVzMzF2NTdyaQpwc0xya0cvTVYzdDN4WG9tT21DUjI2Qi9sYmlURU9ScUlsTCtjcm5jeFhYQjBOZkxzdXhOeGsyZVpvK2ZQOER0CktUVzVMK09uV1NyYlFIZlU2NW1WZWVwTXZ1VjJCamdTaENVNFZ0MjFncHZydXRsUXVUQzBPTGVKV2poQ0s5SGYKY0xmWERuZXd4RS9RQ1JLMHJtdWl0TWlkc21YOUJMTDU0Qk04UjZ3ZmU3RGtuYkhnaCtLM1dQcm9RQkdNU0pJawphR0R5bUVSZUIvcVRSQnpVbndPWEJUcUtOY1BsbjJRdFc1V2t1eTB1bzJBTlRQTFBwNzJxTTFVWmQ2V2JNdGd4CndvaCtreDlWcFhZY0hjMnkrQnRPSkEwVDFRWlFUSHcvNElJaXkrUDIrWE52ODdYZmtPaUEvZEZFMjZkNG54UjkKTGJ3PQo9bnB4QgotLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS0K</guid>
            <category>Commentary</category><category>RSS</category><category>Notes</category>
            <comments>https://graphics.social/@thefranke</comments>
        </item>
        
        <item>
            <title>Engine Design as a Holistic Adventure</title>
            <description>
                
                
                &lt;img src=&quot;https://www.tobias-franke.eu/publications/franke22edaaha/franke22edaaha.jpg&quot; alt=&quot;&quot;&gt;&lt;br/&gt;&lt;br/&gt;
                Authors: Tobias Alexander Franke and Royal O&apos;Brien&lt;br/&gt;
                In: Tales from the 3rd Dimension Podcast&lt;br/&gt;
                
                &lt;p&gt;Most listeners know that Huawei isn’t just a mobile phone company. But many might not realize that it runs a global network of research institutes that are working on optimizing engine development. Tobias Alexander Franke, principal game engine architect at Huawei, talks with Royal about why the company prefers working with open source 3D engines over commercial 3D engines. Highlighting the benefits of different perspectives, he believes open source collaboration results in a more well-rounded engine. Tobias and Royal reflect on other hot topics like the early days of the Open 3D Engine (O3DE) in GitHub, the need for standardization for greater interoperability and the development of the Metaverse.&lt;/p&gt;

&lt;p&gt;Tobias Alexander Franke, principal game engine architect at Huawei, is an experienced graphics engineer with a background in both graphics research and the gaming industry. He holds a PhD from TU-Darmstadt focused on augmented reality, relighting and global illumination.&lt;/p&gt;

                
                
                &lt;p&gt;Links:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://podcasts.apple.com/us/podcast/engine-design-as-a-holistic-adventure/id1646520722?i=1000583075509&quot;&gt;Apple Podcasts&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://open.spotify.com/show/1zUZ2crUiWSm7J2P88QMw1&quot;&gt;Spotify Podcasts&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.deezer.com/us/show/5213737&quot;&gt;Deezer Podcast&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
                
            </description>
            <pubDate>Tue, 18 Oct 2022 00:00:00 +0200</pubDate>
            <link>https://www.tobias-franke.eu/publications/franke22edaaha/index.html</link>
            <guid>LS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS0KCmlRRlBCQUFCQ0FBNUZpRUUxVWxUbHFPVUY1a3NFZCszT0RETU5qa2l2RThGQW1vcHpwTWJGSUFBQUFBQUJBQU8KYldGdWRUSXNNaTQxS3pFdU1USXNNQ3d6QUFvSkVEZ3d6RFk1SXJ4UHVZY0lBS0VwWE00QXdSTVJUYVpVcVAveApLK1ZrWW0xWXluMElBSnFnbHFHNXRDWkpxdloyWGhrNjlycVRqSE9yTTV0elYrNFN4aUpOckF5Zzc5TlZJMVgrCllNbW5UcVhGUnhPUDlHVzN5ZHJwcmU0bVlzaDBKb2ZDeGREeTlsVW14NURsNVEyOHVhL3ZkN3htZ0tDZSt1bE4KanNKTlZYSnpQcTFWQjg1b2tLY2hNSjJqTzBVM29IRzlvV2NoN204dWhGbGlFbmFGMjZVclRSS0JFajkzQTh3TQpxcGVjT0JkVHFGODQwVDBRampsNVE1RkNEbno5WnlGckQ5OE9CeXpoVHdWRHRPbEhzZmpEOW0yc2pDa2VrLzJFCk56ZHBTb05XaWRraHN3ZTFxL0ZhMWFqdk9BWFkrdDhFYnU4OXVLN1ovR0RVQXNmY2VOTE1kYlRYQWdHTTVUOWgKd2drPQo9bUd3cgotLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS0K</guid>
            <category>O3DE</category><category>Architecture</category><category>Open Source</category><category>Publication</category>
            <comments>https://graphics.social/@thefranke</comments>
        </item>
        
        <item>
            <title>Life in the metaverse will be revolutionary</title>
            <description>
                
                
                &lt;img src=&quot;https://www.tobias-franke.eu/publications/franke22irishtimes/franke22irishtimes.jpg&quot; alt=&quot;&quot;&gt;&lt;br/&gt;&lt;br/&gt;
                Authors: Tobias Alexander Franke and Sandra O&apos;Connell&lt;br/&gt;
                In: Irish Times&lt;br/&gt;
                
                &lt;p&gt;I have been interviewed by the Irish Times on the topic of the Metaverse, what it is, why one would be interested in it, the big problem it is supposed to solve and how it will affect our lives. In this interview, I give a sober analysis of the Metaverse, and my goal is to cut out the hype.&lt;/p&gt;

                
                
                &lt;p&gt;Links:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.irishtimes.com/special-reports/2022/07/21/life-in-the-metaverse-will-be-revolutionary/&quot;&gt;Irish Times Article&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://web.archive.org/web/20220724204315/https://www.irishtimes.com/special-reports/2022/07/21/life-in-the-metaverse-will-be-revolutionary/&quot;&gt;Wayback Machine Archive&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
                
            </description>
            <pubDate>Thu, 21 Jul 2022 00:00:00 +0200</pubDate>
            <link>https://www.tobias-franke.eu/publications/franke22irishtimes/index.html</link>
            <guid>LS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS0KCmlRRlBCQUFCQ0FBNUZpRUUxVWxUbHFPVUY1a3NFZCszT0RETU5qa2l2RThGQW1vcHpwTWJGSUFBQUFBQUJBQU8KYldGdWRUSXNNaTQxS3pFdU1USXNNQ3d6QUFvSkVEZ3d6RFk1SXJ4UFF3b0gvM3l5WXVnSWdnV05xQTJrcGsvZQo0NWtTV2w4UDVTMXB5Q2ZKM3FTdGFqamtyczZGMXlXS1V6RHh0TzRIcHl3a1B5Q2hYTllwQkxDeFF3aTRIa3BpClBrL2V1YWUrenlWTU95M0kxcmlRSHkweTB1ZnNrUm9tRzV1S1hJblg4Z09tVTJCS1NMRHZ6UzRLdzFLaENwWE8KLzJBbUpRM1h2TDkxc3VCOXN1WDRpWVZjcHNaS01jRkpwTEJJTytrL2lROERHK010RFI5bFFTSTQxdDZNbkVPdwpyN0wreTNxWXozOFVGQnI2Q3o3Vm1oaWdubzl5NCtGWHpvai93VFh2SHYxVVZEeUJLeUNtTzk0YUZBODdBY0pMCnNoeFVBYTdUdmFMb3Vocy84N1ZzUG5MeDFZQjBEZDhsdG00T1lGOXhEcVc0bVBvNk9ZeThtaS9nOXgzZnVzOFYKU0M0PQo9dU1mVQotLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS0K</guid>
            <category>Interview</category><category>Metaverse</category><category>Cryptocurrency</category><category>Publication</category>
            <comments>https://graphics.social/@thefranke</comments>
        </item>
        
        <item>
            <title>A New Gaming Eco-system for Huawei</title>
            <description>
                
                
                &lt;img src=&quot;https://www.tobias-franke.eu/publications/franke22angefh/franke22angefh.jpg&quot; alt=&quot;&quot;&gt;&lt;br/&gt;&lt;br/&gt;
                Authors: Tobias Alexander Franke&lt;br/&gt;
                In: Huawei GSTS 2022&lt;br/&gt;
                
                &lt;p&gt;Cloud-based gaming services have seen varying success in practice. Where-as multiplayer games feature a wide variety of aspects which are hosted remotely, most games today run exclusively on one device and require the customer to adapt to new hardware requirements in regular cycles, usually tied to the current console generation. Streaming-based gaming services have in contrast failed so far, underestimating technological challenges as well as selling to the wrong market. Edge computing architectures are designed to distribute computational tasks to other nodes in a network or cloud. Huawei&amp;rsquo;s involvement in the Open 3D Engine presents a unique opportunity to build a more resilient architecture for game engines that scale from one device to an arbitrary number. In doing so, a new gaming eco-system evolves as a business opportunity for Huawei.&lt;/p&gt;

                
                
                &lt;p&gt;Links:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://huawei-events.de/en/gsts22-tobias-alexander-franke.htm&quot;&gt;Huawei GSTS 2022&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
                
            </description>
            <pubDate>Thu, 07 Jul 2022 00:00:00 +0200</pubDate>
            <link>https://www.tobias-franke.eu/publications/franke22angefh/index.html</link>
            <guid>LS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS0KCmlRRlBCQUFCQ0FBNUZpRUUxVWxUbHFPVUY1a3NFZCszT0RETU5qa2l2RThGQW1vcHpwTWJGSUFBQUFBQUJBQU8KYldGdWRUSXNNaTQxS3pFdU1USXNNQ3d6QUFvSkVEZ3d6RFk1SXJ4UGNBSUlBSXp4eFhzYktJdlpyeG84bE04WQptSEE0clhxWjhNTjkrZXVWSnFTQi9zT1VKWWR0a3VWL0pEOC81UENQcHRiTy95OWo1aFlmSTRpbkx5cmVrbEtmCjh4TTZBZXQvNGRISHRWSFZVUDRpOGM0cG5BK1ErODk5RkFPSi9Gc3RjRVNJS1pUK1kyeXhUVlN0cnU3NUNpVFIKenZkM3lCUHVldENGSmtCSHgvY2I4bEczWGJrUUgvQkxiZUw1OHN0NnN3VUxVVHNybThkZWtjUGFRWnk4R2hBWQpQNlJrUEZTMHpJcWk0bVlmVTVYTTNhYkI2VWpwMCtObmZYaUdKU3FkUE9VbTRCQ205Q2lrY3dPUWpVWHh3TkFOCkVmZjkrK05KbVpFL2hQMHk2cnhTR0oxWFF4Z3d0Q2RmL0R1bXEwTDAvQmV1eEFLWUNKQmx2SVd4Z2FmRHkyNU8KK3lnPQo9M3FoUwotLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS0K</guid>
            <category>Conference</category><category>Publication</category>
            <comments>https://graphics.social/@thefranke</comments>
        </item>
        
        <item>
            <title>Scalable Global Illumination for O3DE</title>
            <description>
                
                
                &lt;img src=&quot;https://www.tobias-franke.eu/publications/franke21sgifo/franke21sgifo.jpg&quot; alt=&quot;&quot;&gt;&lt;br/&gt;&lt;br/&gt;
                Authors: Tobias Alexander Franke&lt;br/&gt;
                In: O3DECon 2021&lt;br/&gt;
                
                &lt;p&gt;Implementing global illumination for games at scale (on mobile devices and up to high end PCs) is a hard problem. O3DE currently uses DDGI as a solution to provide both baked and real-time global illumination. However several key issues need to be addressed before this system is ready for production. In this talk I want to address those issues:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Scalability and streaming&lt;/li&gt;
  &lt;li&gt;Authoring with small or thin objects&lt;/li&gt;
  &lt;li&gt;Probe aliasing&lt;/li&gt;
  &lt;li&gt;Volume overlaps and blending&lt;/li&gt;
  &lt;li&gt;Baking large scenes&lt;/li&gt;
  &lt;li&gt;Strange mixtures of baked and real-time probe based volumes&lt;/li&gt;
  &lt;li&gt;Baked lighting variants for day-night cycles or similar setups&lt;/li&gt;
  &lt;li&gt;Re-using visibility buffers for other effects&lt;/li&gt;
  &lt;li&gt;Going beyond diffuse GI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea is to highlight each issue, present a small solution (not necessarily &lt;em&gt;the&lt;/em&gt; solution) based off experience from developing similar volume based GI systems in other engines, and make more people from the community - both artists and programmers - aware of these issues so we can collectively come up with a proper system that has an easy workflow and provides great results when computing GI for a project. The talk will close with an outlook to some experiments, novel workflows and how the same system could be used to provide GI for large open worlds, maybe even on mobile devices.&lt;/p&gt;

                
                
                &lt;p&gt;Links:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;franke21sgifo_slides.pdf&quot;&gt;Slides&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://o3decon2021.sched.com/event/mIcc/scalable-global-illumination-for-o3de-tobias-alexander-franke-huawei&quot;&gt;O3DECon 2021&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
                
            </description>
            <pubDate>Tue, 12 Oct 2021 00:00:00 +0200</pubDate>
            <link>https://www.tobias-franke.eu/publications/franke21sgifo/index.html</link>
            <guid>LS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS0KCmlRRlBCQUFCQ0FBNUZpRUUxVWxUbHFPVUY1a3NFZCszT0RETU5qa2l2RThGQW1vcHpwTWJGSUFBQUFBQUJBQU8KYldGdWRUSXNNaTQxS3pFdU1USXNNQ3d6QUFvSkVEZ3d6RFk1SXJ4UDNPMEgvamlZVlIxUnRDTHNMUVZNVmw1WQpCTUpXd2JXU3hKNWpOckUvUGNRTGl6dkJYZ3U3SW1DSWxpMG1IRTRTTS85VGZRT0hHWnE3MDdENDNqMUtZWWRrCkdzRS82RXZkUllwVWVWcjNNU2RlZ1hxZ2pKNEVhSmx5M2Z2ajg0bElraGh2V0ZMUFhEQmpLczdJNllpdkhSV1EKalBQbzFRUFN3WXN4eWJsYWRtQmpJU0IrbkF2R0dKaEs5UFBJQUU4SGI1YmZINXlvVERqbEdYaG1aVXltUUluOAo5aWRScnJmNDZyZklFYjQvN3pOb0RIKzNTTnNMQXhteU5JZjdDWis5RjczeU0rbVBIV29Uc0ZRejc5S2FjQkswCnk5aGl6VTdIRXpGVHQ5cUlxMlgrZmxwbTNHWFpsZXR5RnB1SG12bUlNT05PYnV4eFBacEVJamFXSEhJbXRsOHcKdGRnPQo9SDM5TAotLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS0K</guid>
            <category>Conference</category><category>Publication</category>
            <comments>https://graphics.social/@thefranke</comments>
        </item>
        
    </channel>
</rss>
<!-- LS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS0KCmlRRlBCQUFCQ0FBNUZpRUUxVWxUbHFPVUY1a3NFZCszT0RETU5qa2l2RThGQW1vcHpwZ2JGSUFBQUFBQUJBQU8KYldGdWRUSXNNaTQxS3pFdU1USXNNQ3d6QUFvSkVEZ3d6RFk1SXJ4UHRjb0grd1Jubnh1cWpnYkJ5ZTh0NHhjegpmSU5MR1V6Rk5tWml0MmNnTm9vUlRiYWkrUFdjd29zM1EwQzJDeEpxWnEvS0pBS3g0WG9tL2xjaVNNajlsL3M1CnFMcmFHNFZBTXlSemF2eUxmcFM1SWdOY3NFeWhwREtUbjBpemwvNkVoelFzYUt4dVN1a1dYdURReHA2T1liU2kKN1A0RHhaTENldEpKeW5wbFRObE9jZTZldFhPTmJwTGdOWDl1QlBYS0JZTjFweHYxYlFadWsrRWhlZjZiQXNLVwpBaHo2NExtY2xRZGNYaUlxaUNHVkMvY1owQXNza2NwaVdwbEpPZmx4eDBoT1JHdlVveVhtZ0pJK3pzanVlWFZuCkFkSmptellGaWdEcTRhVmdpSUN5bmpRaTN5c0dtZHl1VGtyemJEZlA0Z2VWSEdZNVlRR1JiQVkrYlo2MllaeVcKYXpjPQo9NjVuTQotLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS0K -->