<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Mastodon on LNA-DEV ~ Lukas Nagel</title>
    <link>https://lna-dev.net/en/tags/mastodon/</link>
    <description>Recent content in Mastodon on LNA-DEV ~ Lukas Nagel</description>
    <image>
      <title>LNA-DEV ~ Lukas Nagel</title>
      <url>https://lna-dev.net/assets/Ping%C3%BCino/Ping%C3%BCino.png</url>
      <link>https://lna-dev.net/assets/Ping%C3%BCino/Ping%C3%BCino.png</link>
    </image>
    <generator>Hugo -- 0.160.1</generator>
    <language>en-US</language>
    <managingEditor>me@lna-dev.net (Lukas Nagel)</managingEditor>
    <webMaster>me@lna-dev.net (Lukas Nagel)</webMaster>
    <copyright>🄯 various licenses</copyright>
    <lastBuildDate>Sun, 05 May 2024 22:30:45 +0200</lastBuildDate>
    <atom:link href="https://lna-dev.net/en/tags/mastodon/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>How to automate Pixelfed posts</title>
      <link>https://lna-dev.net/en/posts/projects/pixelfed-automation/</link>
      <pubDate>Sun, 05 May 2024 22:30:45 +0200</pubDate><author>me@lna-dev.net (Lukas Nagel)</author>
      <guid>https://lna-dev.net/en/posts/projects/pixelfed-automation/</guid>
      <description>I needed to automate my posts to Pixelfed. In this post, I describe how I achieved that.</description>
      <content:encoded><![CDATA[<h2 id="my-problem-with-posting-to-pixelfed">My problem with posting to Pixelfed</h2>
<p>I started with Mastodon as my first account in the <a href="posts/media/fediverse/">Fediverse</a>. After a while, I found out about Pixelfed and created my account there. I kept using it for a while and even posted from time to time. My problem with posting to Pixelfed was the lack of a scheduler. I want to post regularly, but I do not want to login every day and make a post manually. This feature was even announced but never finished. Even today, this is not implemented. There is also a <a href="https://github.com/pixelfed/pixelfed/issues/2872">GitHub issue</a> discussing this if your interest goes deeper. In the following post by Pixelfed, you can see the announcement.


<iframe
  src="https://mastodon.social/@pixelfed/107574719894032457/embed"
  class="mastodon-embed"
  style="max-width: 100%; border: 0; margin-top: 15px; margin-bottom: 10px"
  width="100%"
  allowfullscreen="allowfullscreen"
></iframe>
<script src="https://mastodon.social/embed.js" async="async"></script>
</p>
<p>There are some post schedulers for Mastodon out there. I tried using them with Pixelfed, but nothing worked properly. I even tried the client-side automatic upload feature of <a href="https://fedilab.app/">Fedilab</a>, which posted immediately instead of at the given time. This surely was a bug I could have reported or fixed myself (because Fedilab is <a href="tags/open-source/">FOSS</a>). But I wanted to make things more automatic anyway.</p>
<p>I do not want to complain about this. It is totally fine. There are a lot of other things that need to be done in Pixelfed and the Fediverse as a whole. But for me, it meant I needed to implement something on my own.</p>
<h2 id="requirements">Requirements</h2>
<p>So to solve this, I needed something which can somehow talk with my Pixelfed server and make a post there. I also needed a way to know which posts I want to make. This information must include some metadata like the date taken and definitely an alt text and tags to display in Pixelfed. Another thing to keep in mind is that I needed something which &ldquo;remembers&rdquo; the state of my postings. Basically, a list in which each post gets added if it is uploaded successfully. And finally, I needed some sort of mechanism which can schedule the execution of the code.</p>
<h2 id="the-automation">The automation</h2>
<h3 id="rss-feed">RSS feed</h3>
<p>So first of all, I have a <a href="https://lna-dev.net/en/gallery">photography page</a> which has an RSS feed. I modified the feed of the page to contain an entry per image I have uploaded there. I am also using Hugo there. Therefore I wrote this little Hugo <code>rss.xml</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span>{{- $authorEmail := site.Params.author.email -}}
</span></span><span style="display:flex;"><span>{{- $authorName := site.Params.author.name }}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{{- $pctx := . -}}
</span></span><span style="display:flex;"><span>{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
</span></span><span style="display:flex;"><span>{{- $pages := slice -}}
</span></span><span style="display:flex;"><span>{{- if or $.IsHome $.IsSection -}}
</span></span><span style="display:flex;"><span>{{- $pages = where $pctx.RegularPages &#34;Params.private&#34; &#34;ne&#34; true }}
</span></span><span style="display:flex;"><span>{{- else -}}
</span></span><span style="display:flex;"><span>{{- $pages = where $pctx.Pages &#34;Params.private&#34; &#34;ne&#34; true }}
</span></span><span style="display:flex;"><span>{{- end -}}
</span></span><span style="display:flex;"><span>{{- $pages = where $pages &#34;Params.rss_ignore&#34; &#34;ne&#34; true -}}
</span></span><span style="display:flex;"><span>{{- $limit := .Site.Config.Services.RSS.Limit -}}
</span></span><span style="display:flex;"><span>{{- if ge $limit 1 -}}
</span></span><span style="display:flex;"><span>{{- $pages = $pages | first $limit -}}
</span></span><span style="display:flex;"><span>{{- end -}}
</span></span><span style="display:flex;"><span>{{- printf &#34;<span style="color:#75715e">&lt;?xml version=\&#34;1.0\&#34; encoding=\&#34;utf-8\&#34; standalone=\&#34;yes\&#34;?&gt;</span>&#34; | safeHTML }}
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;rss</span> <span style="color:#a6e22e">version=</span><span style="color:#e6db74">&#34;2.0&#34;</span> <span style="color:#a6e22e">xmlns:media=</span><span style="color:#e6db74">&#34;http://search.yahoo.com/mrss/&#34;</span> <span style="color:#a6e22e">xmlns:atom=</span><span style="color:#e6db74">&#34;http://www.w3.org/2005/Atom&#34;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;channel&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;title&gt;</span>{{ if eq  .Title  .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}<span style="color:#f92672">&lt;/title&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;link&gt;</span>{{ .Permalink }}<span style="color:#f92672">&lt;/link&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;description&gt;</span>Recent content {{ if ne  .Title  .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}<span style="color:#f92672">&lt;/description&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;generator&gt;</span>Hugo -- gohugo.io<span style="color:#f92672">&lt;/generator&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;language&gt;</span>{{ site.Language.LanguageCode }}<span style="color:#f92672">&lt;/language&gt;</span>{{ with site.Params.author.email }}
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;managingEditor&gt;</span>{{.}}{{ with $authorName }} ({{ . }}){{ end }}<span style="color:#f92672">&lt;/managingEditor&gt;</span>{{ end }}{{ with $authorEmail }}
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;webMaster&gt;</span>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}<span style="color:#f92672">&lt;/webMaster&gt;</span>{{ end }}{{ with .Site.Copyright }}
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;copyright&gt;</span>{{.}}<span style="color:#f92672">&lt;/copyright&gt;</span>{{end}}{{ if not .Date.IsZero }}
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;lastBuildDate&gt;</span>{{ (index $pages.ByLastmod.Reverse 0).Lastmod.Format &#34;Mon, 02 Jan 2006 15:04:05 -0700&#34; | safeHTML }}<span style="color:#f92672">&lt;/lastBuildDate&gt;</span>{{ end }}
</span></span><span style="display:flex;"><span>    {{- with .OutputFormats.Get &#34;RSS&#34; -}}
</span></span><span style="display:flex;"><span>    {{ printf &#34;<span style="color:#f92672">&lt;atom:link</span> <span style="color:#a6e22e">href=</span><span style="color:#e6db74">%q</span> <span style="color:#a6e22e">rel=</span><span style="color:#e6db74">\&#34;self\&#34;</span> <span style="color:#a6e22e">type=</span><span style="color:#e6db74">%q</span> <span style="color:#f92672">/&gt;</span>&#34; .Permalink .MediaType | safeHTML }}
</span></span><span style="display:flex;"><span>    {{- end -}}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    {{- range $pages }}
</span></span><span style="display:flex;"><span>    {{- if not (in .Path &#34;/archive/&#34;) -}}
</span></span><span style="display:flex;"><span>    {{ range .Resources.ByType &#34;image&#34; }}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;item&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;title&gt;</span>{{ .Name }}<span style="color:#f92672">&lt;/title&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;link&gt;</span>{{ .Permalink }}<span style="color:#f92672">&lt;/link&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;pubDate&gt;</span>{{ .Exif.Date.Format &#34;Mon, 02 Jan 2006 15:04:05 -0700&#34; | safeHTML }}<span style="color:#f92672">&lt;/pubDate&gt;</span>
</span></span><span style="display:flex;"><span>      {{- with $authorEmail }}<span style="color:#f92672">&lt;author&gt;</span>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}<span style="color:#f92672">&lt;/author&gt;</span>{{ end }}
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;guid&gt;</span>{{ .Permalink }}<span style="color:#f92672">&lt;/guid&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;media:content</span> <span style="color:#a6e22e">url=</span><span style="color:#e6db74">&#34;{{ .Permalink }}&#34;</span> <span style="color:#a6e22e">type=</span><span style="color:#e6db74">&#34;image/jpeg&#34;</span><span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;description&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;img</span> <span style="color:#a6e22e">src=</span><span style="color:#e6db74">&#34;{{ .Permalink }}&#34;</span> <span style="color:#a6e22e">alt=</span><span style="color:#e6db74">&#34;{{ .Params.Alt }}&#34;</span><span style="color:#f92672">/&gt;</span>          
</span></span><span style="display:flex;"><span>        {{- if ne .Title .Name -}}
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;p&gt;</span> {{ .Title }} <span style="color:#f92672">&lt;/p&gt;</span>
</span></span><span style="display:flex;"><span>        {{ end }}
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;/description&gt;</span>
</span></span><span style="display:flex;"><span>      {{ range .Params.Tags }}
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;category&gt;</span>{{ . }}<span style="color:#f92672">&lt;/category&gt;</span>        
</span></span><span style="display:flex;"><span>      {{- end }}
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;/item&gt;</span>
</span></span><span style="display:flex;"><span>    {{- end -}}
</span></span><span style="display:flex;"><span>    {{- end -}}
</span></span><span style="display:flex;"><span>    {{ end }}
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;/channel&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/rss&gt;</span>
</span></span></code></pre></div><p>That done, I managed the metadata of all my images in the Hugo config file and wrote an alt text and tags for all my images. (Must admit&hellip; an LLM helped me a bit with that. This made the process a lot faster, but the quality is also just okay.)</p>
<h3 id="my-personal-api">My personal API</h3>
<p>I even wanted to create an API for this page before this little project. So I created a new API written in <strong>go</strong>. There I implemented a few endpoints which can save and read a list of filenames from and to MongoDB. I am using MongoDB because of the easy use and their free cloud storage. I will probably change that to something self-hosted in the future, but for now, it does its job. (I am running a K8s cluster with broken local storage right now. Need to fix this first.)<br>
Oh, and I also made the <code>POST</code> endpoint protected by an API key so no one unauthorized can alter my data😉</p>
<p>Now having an API which knows which images have already been posted, I could continue to the script interacting with Pixelfed.</p>
<h3 id="the-pixelfed-script">The Pixelfed Script</h3>
<p>For writing the script, I went with Python. I am having mixed feelings about Python. On the one hand, I really dislike the way it is handling types (it doesn&rsquo;t lol) but on the other hand, you can quickly make basic things work. So I went the (for me) experimental way and chose Python as my way to go.</p>
<p>I created this basic script which is handling all the stuff needed to create the post and make all the API and RSS handling. I go into detail on some of the methods and bits of code I find most important but I have the full code provided below if you are interested.</p>


<p><details >
  <summary markdown="span">The full code</summary>
  <p>Alternatively to this code block, there is an up-to-date <a href="https://github.com/LNA-DEV/Autouploader">Repo</a> on GitHub.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> io <span style="color:#f92672">import</span> BytesIO
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> re
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> sys
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> feedparser
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> datetime <span style="color:#f92672">import</span> datetime
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> time
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> random
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> requests
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>PIXELFED_INSTANCE_URL <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;https://pixelfed.de&#39;</span>
</span></span><span style="display:flex;"><span>PAT <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;PIXELFED_PAT&#39;</span>)
</span></span><span style="display:flex;"><span>API_KEY <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;API_KEY&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Function to filter entries based on the name list</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">filter_entries</span>(entries, name_list):
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Temp skips (for example if this image does not fit currently)</span>
</span></span><span style="display:flex;"><span>    name_list<span style="color:#f92672">.</span>append(<span style="color:#e6db74">&#34;P1002496.JPG&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> [entry <span style="color:#66d9ef">for</span> entry <span style="color:#f92672">in</span> entries <span style="color:#66d9ef">if</span> entry<span style="color:#f92672">.</span>title <span style="color:#f92672">not</span> <span style="color:#f92672">in</span> name_list]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_already_uploaded_items</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>        response <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;https://api.lna-dev.net/autouploader/pixelfed&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> response<span style="color:#f92672">.</span>status_code <span style="color:#f92672">==</span> <span style="color:#ae81ff">200</span>:
</span></span><span style="display:flex;"><span>            string_list <span style="color:#f92672">=</span> response<span style="color:#f92672">.</span>json()
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> string_list
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Failed to fetch data from API. Status code: </span><span style="color:#e6db74">{</span>response<span style="color:#f92672">.</span>status_code<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>            sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">Exception</span> <span style="color:#66d9ef">as</span> e:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;An error occurred: </span><span style="color:#e6db74">{</span>e<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">published_entry</span>(entry_name):
</span></span><span style="display:flex;"><span>    requests<span style="color:#f92672">.</span>post(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;https://api.lna-dev.net/autouploader/pixelfed?item=</span><span style="color:#e6db74">{</span>entry_name<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>, headers<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#34;Authorization&#34;</span>: <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;ApiKey </span><span style="color:#e6db74">{</span>API_KEY<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">download_image</span>(image_url):
</span></span><span style="display:flex;"><span>    response <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(image_url)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> response<span style="color:#f92672">.</span>status_code <span style="color:#f92672">==</span> <span style="color:#ae81ff">200</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> BytesIO(response<span style="color:#f92672">.</span>content)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#34;Failed to download image!&#34;</span>)
</span></span><span style="display:flex;"><span>        sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">publish_entry</span>(entry):
</span></span><span style="display:flex;"><span>    caption <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;More at https://lna-dev.net/en/gallery</span><span style="color:#ae81ff">\n\n</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> element <span style="color:#f92672">in</span> entry<span style="color:#f92672">.</span>tags:
</span></span><span style="display:flex;"><span>        caption <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#39;#&#39;</span> <span style="color:#f92672">+</span> element<span style="color:#f92672">.</span>term <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; &#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    mediaResponse <span style="color:#f92672">=</span> upload_media(entry)
</span></span><span style="display:flex;"><span>    publish_post(caption, mediaResponse)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    published_entry(entry<span style="color:#f92672">.</span>title)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">upload_media</span>(entry):
</span></span><span style="display:flex;"><span>    media_url <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;</span><span style="color:#e6db74">{</span>PIXELFED_INSTANCE_URL<span style="color:#e6db74">}</span><span style="color:#e6db74">/api/v1/media&#39;</span>
</span></span><span style="display:flex;"><span>    headers <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;Authorization&#39;</span>: <span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;Bearer </span><span style="color:#e6db74">{</span>PAT<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;Accept&#39;</span>: <span style="color:#e6db74">&#39;application/json&#39;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    files <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;file&#39;</span>: download_image(entry<span style="color:#f92672">.</span>link)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    data <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;description&#39;</span>: re<span style="color:#f92672">.</span>search(<span style="color:#e6db74">&#39;alt=&#34;(.*?)&#34;&#39;</span>, entry<span style="color:#f92672">.</span>summary)<span style="color:#f92672">.</span>group(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    response <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>post(media_url, headers<span style="color:#f92672">=</span>headers, files<span style="color:#f92672">=</span>files, data<span style="color:#f92672">=</span>data)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> response<span style="color:#f92672">.</span>status_code <span style="color:#f92672">==</span> <span style="color:#ae81ff">200</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> response<span style="color:#f92672">.</span>json()[<span style="color:#e6db74">&#39;id&#39;</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#34;Failed to upload media.&#34;</span>)
</span></span><span style="display:flex;"><span>        sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">publish_post</span>(caption, media_id):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> caption<span style="color:#f92672">.</span>strip():
</span></span><span style="display:flex;"><span>        post_url <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;</span><span style="color:#e6db74">{</span>PIXELFED_INSTANCE_URL<span style="color:#e6db74">}</span><span style="color:#e6db74">/api/v1/statuses&#39;</span>
</span></span><span style="display:flex;"><span>        headers <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;Authorization&#39;</span>: <span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;Bearer </span><span style="color:#e6db74">{</span>PAT<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;Accept&#39;</span>: <span style="color:#e6db74">&#39;application/json&#39;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        data <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;status&#39;</span>: caption,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;media_ids[]&#39;</span>: media_id
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        response <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>post(post_url, headers<span style="color:#f92672">=</span>headers, data<span style="color:#f92672">=</span>data)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> response<span style="color:#f92672">.</span>status_code <span style="color:#f92672">==</span> <span style="color:#ae81ff">200</span>:
</span></span><span style="display:flex;"><span>            print(<span style="color:#e6db74">&#34;Post published successfully!&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            print(<span style="color:#e6db74">&#34;Failed to publish post!&#34;</span>)
</span></span><span style="display:flex;"><span>            sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#34;Caption cannot be empty.&#34;</span>)
</span></span><span style="display:flex;"><span>        sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Parse the RSS feed</span>
</span></span><span style="display:flex;"><span>feed_url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;https://lna-dev.net/en/gallery/index.xml&#39;</span>
</span></span><span style="display:flex;"><span>feed <span style="color:#f92672">=</span> feedparser<span style="color:#f92672">.</span>parse(feed_url)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Filter out entries with specific names</span>
</span></span><span style="display:flex;"><span>specific_names <span style="color:#f92672">=</span> get_already_uploaded_items()
</span></span><span style="display:flex;"><span>filtered_entries <span style="color:#f92672">=</span> filter_entries(feed<span style="color:#f92672">.</span>entries, specific_names)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> filtered_entries:
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;No entries available after filtering.&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Calculate time differences considering only month, day, hour, minute, and second</span>
</span></span><span style="display:flex;"><span>    current_time <span style="color:#f92672">=</span> datetime<span style="color:#f92672">.</span>now()
</span></span><span style="display:flex;"><span>    closest_entry <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>
</span></span><span style="display:flex;"><span>    skipped_entries <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>    min_difference <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> entry <span style="color:#f92672">in</span> filtered_entries:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> entry<span style="color:#f92672">.</span>published_parsed<span style="color:#f92672">.</span>tm_year <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">or</span> entry<span style="color:#f92672">.</span>published_parsed<span style="color:#f92672">.</span>tm_year <span style="color:#f92672">==</span> <span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>            skipped_entries<span style="color:#f92672">.</span>append(entry)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span>  <span style="color:#75715e"># Skip entries with invalid year</span>
</span></span><span style="display:flex;"><span>        temp <span style="color:#f92672">=</span> time<span style="color:#f92672">.</span>mktime(entry<span style="color:#f92672">.</span>published_parsed)
</span></span><span style="display:flex;"><span>        published_time <span style="color:#f92672">=</span> datetime<span style="color:#f92672">.</span>fromtimestamp(temp)
</span></span><span style="display:flex;"><span>        difference <span style="color:#f92672">=</span> abs(current_time<span style="color:#f92672">.</span>replace(year<span style="color:#f92672">=</span>published_time<span style="color:#f92672">.</span>year, tzinfo<span style="color:#f92672">=</span><span style="color:#66d9ef">None</span>) <span style="color:#f92672">-</span> published_time)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> min_difference <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span> <span style="color:#f92672">or</span> difference <span style="color:#f92672">&lt;</span> min_difference:
</span></span><span style="display:flex;"><span>            min_difference <span style="color:#f92672">=</span> difference
</span></span><span style="display:flex;"><span>            closest_entry <span style="color:#f92672">=</span> entry
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> closest_entry <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#34;No valid entries available after filtering.&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Get all entries published at the same time as the closest entry</span>
</span></span><span style="display:flex;"><span>        closest_entries <span style="color:#f92672">=</span> [entry <span style="color:#66d9ef">for</span> entry <span style="color:#f92672">in</span> filtered_entries <span style="color:#66d9ef">if</span> entry<span style="color:#f92672">.</span>published <span style="color:#f92672">==</span> closest_entry<span style="color:#f92672">.</span>published]
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> element <span style="color:#f92672">in</span> skipped_entries:
</span></span><span style="display:flex;"><span>            closest_entries<span style="color:#f92672">.</span>append(element)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Select a random entry from the closest entries</span>
</span></span><span style="display:flex;"><span>        random_entry <span style="color:#f92672">=</span> random<span style="color:#f92672">.</span>choice(closest_entries)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Print the selected entry</span>
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#34;Random entry closest to the current date/time (ignoring year):&#34;</span>)
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#34;Title:&#34;</span>, random_entry<span style="color:#f92672">.</span>title)
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#34;URL:&#34;</span>, random_entry<span style="color:#f92672">.</span>link)
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#34;Published Date:&#34;</span>, random_entry<span style="color:#f92672">.</span>published)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        publish_entry(random_entry)
</span></span></code></pre></div>
</details></p>

<p>So let&rsquo;s start with the retrieval of the RSS data. To simplify this for me, I used the <code>feedparser</code> library here. With that, I have access to all the data of the RSS feed in a better-structured way.</p>
<p>With this simple API call, I retrieve the already uploaded posts from my personal API I mentioned above. With this data, I can filter out any entries in the RSS feed which I don&rsquo;t need to upload anymore.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_already_uploaded_items</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>        response <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;https://api.lna-dev.net/autouploader/pixelfed&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> response<span style="color:#f92672">.</span>status_code <span style="color:#f92672">==</span> <span style="color:#ae81ff">200</span>:
</span></span><span style="display:flex;"><span>            string_list <span style="color:#f92672">=</span> response<span style="color:#f92672">.</span>json()
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> string_list
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Failed to fetch data from API. Status code: </span><span style="color:#e6db74">{</span>response<span style="color:#f92672">.</span>status_code<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>            sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">Exception</span> <span style="color:#66d9ef">as</span> e:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;An error occurred: </span><span style="color:#e6db74">{</span>e<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
</span></span></code></pre></div><p>After having a list of possible posts, I need the script to decide which post to post next. For that, I am calculating which posts are nearest to the current day and month without taking the year into the calculation.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> entry <span style="color:#f92672">in</span> filtered_entries:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> entry<span style="color:#f92672">.</span>published_parsed<span style="color:#f92672">.</span>tm_year <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">or</span> entry<span style="color:#f92672">.</span>published_parsed<span style="color:#f92672">.</span>tm_year <span style="color:#f92672">==</span> <span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>        skipped_entries<span style="color:#f92672">.</span>append(entry)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">continue</span>  <span style="color:#75715e"># Skip entries with invalid year</span>
</span></span><span style="display:flex;"><span>    temp <span style="color:#f92672">=</span> time<span style="color:#f92672">.</span>mktime(entry<span style="color:#f92672">.</span>published_parsed)
</span></span><span style="display:flex;"><span>    published_time <span style="color:#f92672">=</span> datetime<span style="color:#f92672">.</span>fromtimestamp(temp)
</span></span><span style="display:flex;"><span>    difference <span style="color:#f92672">=</span> abs(current_time<span style="color:#f92672">.</span>replace(year<span style="color:#f92672">=</span>published_time<span style="color:#f92672">.</span>year, tzinfo<span style="color:#f92672">=</span><span style="color:#66d9ef">None</span>) <span style="color:#f92672">-</span> published_time)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> min_difference <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span> <span style="color:#f92672">or</span> difference <span style="color:#f92672">&lt;</span> min_difference:
</span></span><span style="display:flex;"><span>        min_difference <span style="color:#f92672">=</span> difference
</span></span><span style="display:flex;"><span>        closest_entry <span style="color:#f92672">=</span> entry
</span></span></code></pre></div><p>Then I select one of the closest entries randomly. I need to do this because if I would have taken many photos on one day there could be multiple items close to the current date.</p>
<p>Now we are ready for creating the post. Therefore, I am now preparing the caption which is included in the post.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">publish_entry</span>(entry):
</span></span><span style="display:flex;"><span>    caption <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;More at https://lna-dev.net/en/gallery</span><span style="color:#ae81ff">\n\n</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> element <span style="color:#f92672">in</span> entry<span style="color:#f92672">.</span>tags:
</span></span><span style="display:flex;"><span>        caption <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#39;#&#39;</span> <span style="color:#f92672">+</span> element<span style="color:#f92672">.</span>term <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; &#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    mediaResponse <span style="color:#f92672">=</span> upload_media(entry)
</span></span><span style="display:flex;"><span>    publish_post(caption, mediaResponse)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    published_entry(entry<span style="color:#f92672">.</span>title)
</span></span></code></pre></div><p>Now to the trickiest bit: The actual posting. This was especially interesting because at the time I created this script there was a lack of documentation and I needed to somehow figure this out on my own. I also didn&rsquo;t find many examples searching the internet. The best I could do was look into the Mastodon documentation (because Pixelfeds API is similar) and figure it out on my own.</p>
<p>There are two important things here. First, you have to upload the media first before you can make the post which then simply contains the id of the media you uploaded beforehand. And second, you need to create a PAT (personal access token) and provide it via the bearer syntax to the API.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">upload_media</span>(entry):
</span></span><span style="display:flex;"><span>    media_url <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;</span><span style="color:#e6db74">{</span>PIXELFED_INSTANCE_URL<span style="color:#e6db74">}</span><span style="color:#e6db74">/api/v1/media&#39;</span>
</span></span><span style="display:flex;"><span>    headers <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;Authorization&#39;</span>: <span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;Bearer </span><span style="color:#e6db74">{</span>PAT<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;Accept&#39;</span>: <span style="color:#e6db74">&#39;application/json&#39;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    files <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;file&#39;</span>: download_image(entry<span style="color:#f92672">.</span>link)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    data <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;description&#39;</span>: re<span style="color:#f92672">.</span>search(<span style="color:#e6db74">&#39;alt=&#34;(.*?)&#34;&#39;</span>, entry<span style="color:#f92672">.</span>summary)<span style="color:#f92672">.</span>group(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    response <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>post(media_url, headers<span style="color:#f92672">=</span>headers, files<span style="color:#f92672">=</span>files, data<span style="color:#f92672">=</span>data)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> response<span style="color:#f92672">.</span>status_code <span style="color:#f92672">==</span> <span style="color:#ae81ff">200</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> response<span style="color:#f92672">.</span>json()[<span style="color:#e6db74">&#39;id&#39;</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#34;Failed to upload media.&#34;</span>)
</span></span><span style="display:flex;"><span>        sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">publish_post</span>(caption, media_id):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> caption<span style="color:#f92672">.</span>strip():
</span></span><span style="display:flex;"><span>        post_url <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;</span><span style="color:#e6db74">{</span>PIXELFED_INSTANCE_URL<span style="color:#e6db74">}</span><span style="color:#e6db74">/api/v1/statuses&#39;</span>
</span></span><span style="display:flex;"><span>        headers <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;Authorization&#39;</span>: <span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;Bearer </span><span style="color:#e6db74">{</span>PAT<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;Accept&#39;</span>: <span style="color:#e6db74">&#39;application/json&#39;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        data <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;status&#39;</span>: caption,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;media_ids[]&#39;</span>: media_id
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        response <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>post(post_url, headers<span style="color:#f92672">=</span>headers, data<span style="color:#f92672">=</span>data)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> response<span style="color:#f92672">.</span>status_code <span style="color:#f92672">==</span> <span style="color:#ae81ff">200</span>:
</span></span><span style="display:flex;"><span>            print(<span style="color:#e6db74">&#34;Post published successfully!&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            print(<span style="color:#e6db74">&#34;Failed to publish post!&#34;</span>)
</span></span><span style="display:flex;"><span>            sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#34;Caption cannot be empty.&#34;</span>)
</span></span><span style="display:flex;"><span>        sys<span style="color:#f92672">.</span>exit(<span style="color:#ae81ff">1</span>)
</span></span></code></pre></div><p>That done, I made a final API request to my personal API noting that I uploaded the image.</p>
<h3 id="the-schedule">The schedule</h3>
<p>This part was fairly easy because I was already using a <a href="/tags/Kubernetes">Kubernetes</a> cluster. I just needed to create a cronjob running each day and that&rsquo;s it.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">batch/v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">CronJob</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">pixelfed-autoupload</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">schedule</span>: <span style="color:#e6db74">&#34;0 16 * * *&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">jobTemplate</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">containers</span>:
</span></span><span style="display:flex;"><span>            - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">pixelfed-autoupload</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">image</span>: <span style="color:#ae81ff">lnadev/pixelfed-autoupload:{{ .Values.autoupload.pixelfed.version }}</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">imagePullPolicy</span>: <span style="color:#ae81ff">Always</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">resources</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">limits</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">memory</span>: <span style="color:#e6db74">&#34;128Mi&#34;</span> 
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">cpu</span>: <span style="color:#e6db74">&#34;500m&#34;</span> <span style="color:#75715e"># Don&#39;t use CPU limits in K8s but that is another topic...</span>
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>                - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">PIXELFED_PAT</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">valueFrom</span>:
</span></span><span style="display:flex;"><span>                    <span style="color:#f92672">secretKeyRef</span>:
</span></span><span style="display:flex;"><span>                      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">pixelfed</span>
</span></span><span style="display:flex;"><span>                      <span style="color:#f92672">key</span>: <span style="color:#ae81ff">pat</span>
</span></span><span style="display:flex;"><span>                - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">API_KEY</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#f92672">valueFrom</span>:
</span></span><span style="display:flex;"><span>                    <span style="color:#f92672">secretKeyRef</span>:
</span></span><span style="display:flex;"><span>                      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">personal-api-secret</span>
</span></span><span style="display:flex;"><span>                      <span style="color:#f92672">key</span>: <span style="color:#ae81ff">apikey</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">restartPolicy</span>: <span style="color:#ae81ff">Never</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">backoffLimit</span>: <span style="color:#ae81ff">0</span>
</span></span></code></pre></div><p>One thing I had to learn here is the following: Setting the <code>restartPolicy</code> to <code>never</code> will not prevent the cronjob from retrying. You need to also set the <code>backoffLimit</code> to <code>0</code> to achieve the desired behavior. (This is because those two settings change different retry mechanisms you both want to disable.)</p>
]]></content:encoded>
    </item>
  </channel>
</rss>