<?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>Bluesky on LNA-DEV ~ Lukas Nagel</title>
    <link>https://lna-dev.net/en/tags/bluesky/</link>
    <description>Recent content in Bluesky 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.156.0</generator>
    <language>en-US</language>
    <managingEditor>me@lna-dev.net (Lukas Nagel)</managingEditor>
    <webMaster>me@lna-dev.net (Lukas Nagel)</webMaster>
    <copyright>🄯 CC-BY-ND</copyright>
    <lastBuildDate>Wed, 29 Jan 2025 18:50:00 +0200</lastBuildDate>
    <atom:link href="https://lna-dev.net/en/tags/bluesky/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>How to automate Bluesky posts</title>
      <link>https://lna-dev.net/en/posts/projects/bluesky-automation/</link>
      <pubDate>Wed, 29 Jan 2025 18:50:00 +0200</pubDate><author>me@lna-dev.net (Lukas Nagel)</author>
      <guid>https://lna-dev.net/en/posts/projects/bluesky-automation/</guid>
      <description>I recently created my Bluesky account and in this post, I’ll show you how I automate my posts to it.</description>
      <content:encoded><![CDATA[<p>I had Bluesky on my radar for quite a while but never liked the way the platform was built. <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> But recently, I decided to give it a try. With this post, I want to share my experience automating my posts to Bluesky with you. (You can find <a href="https://bsky.app/profile/lna-dev.net">my profile</a> here.)</p>
<p>I also wrote <a href="../pixelfed-automation/">a post</a> in which I explained how to automate posts to <a href="../../media/fediverse/#pixelfed">Pixelfed</a> (a <a href="../../media/fediverse/">Fediverse</a> platform), which is quite similar to this one. I might refer to that post from time to time because the process is quite similar.</p>
<blockquote>
<p>This post might be a bit technical if you have no previous experience with coding / tech.</p>
</blockquote>
<h2 id="posse">POSSE</h2>
<p>So, first of all, why do all this? For me, the answer is simple: I have been getting into the <a href="https://indieweb.org/">IndieWeb</a> bubble for a while. As part of it, you want to build your own website, which you control and have full power over. Your website is your little garden where you put all your stuff. But because most people won’t just visit your website directly, you need a way to get your content to them. For this, automation is key.</p>
<p>You host all your content on your page and then build scripts and little bots that take the content from your webpage and <strong>syndicate</strong> it wherever you want. Whether it&rsquo;s the walled gardens of big tech, the <a href="../../media/fediverse/">Fediverse</a> or Bluesky doesn’t matter. You just post to your website and your bots do the rest. This principle is called <em>Post On your Own Site Syndicate Elsewhere</em> or <strong>POSSE</strong> for short.</p>
<p>I might write a detailed post about the IndieWeb and <a href="https://www.citationneeded.news/posse/">POSSE</a> in the future, so stay tuned!</p>
<h2 id="my-data-source---rss-feed">My data source - RSS Feed</h2>
<p>As I already explained in my <a href="../pixelfed-automation/#rss-feed">Pixelfed Automation</a> post, I have an RSS feed that contains all the data I need for publishing my images. Specifically, this includes the image URL, description, alt text and hashtags.</p>
<p>Your data source may vary if you have an API or something similar to access the required data. Personally, I like the idea of having an RSS feed because users of my webpage can follow the same feed and use an RSS reader to consume my content.</p>
<h2 id="the-python-script">The Python script</h2>
<p>Now, onto the actual automation. I used Python because I already had my Pixelfed script, which I could reuse a lot from.</p>
<p>There are a couple of code blocks that I didn’t really touch. For example, the parts responsible for retrieving the image from my website, selecting which image to publish and notifying my API about the images that are now online.</p>
<p>Since I’ve covered these steps before, I’ll jump straight to the interesting part: how to publish to Bluesky. (If you want to know the details about the other steps, you can read them <a href="../pixelfed-automation/#the-pixelfed-script">here</a>.)</p>
<h3 id="publish-to-bluesky">Publish to Bluesky</h3>
<p>This is where it gets interesting because we’re doing the actual publishing. First of all, there is a good Python library for Bluesky / ATproto called <code>atproto</code>. You can install it with the following command:</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-sh" data-lang="sh"><span style="display:flex;"><span>pip install atproto
</span></span></code></pre></div><p>After installing the right package, we can look at the code itself. First, we need a client where we pass our email and PAT (I explain how to generate one in the next section). This is enough to authenticate with Bluesky.</p>
<p>Next, we prepare our post. For this, I use the <code>TextBuilder</code> provided by the package to format links and hashtags correctly. After preparing our text/caption, we can use the <code>send_image</code> or <code>send_post</code> method, respectively. And that’s pretty much it! Quite easy when using the library.</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> atproto <span style="color:#f92672">import</span> Client
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> atproto <span style="color:#f92672">import</span> client_utils
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>BLUESKY_PAT <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;BLUESKY_PAT&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>bsClient <span style="color:#f92672">=</span> Client()
</span></span><span style="display:flex;"><span>bsClient<span style="color:#f92672">.</span>login(
</span></span><span style="display:flex;"><span>    login<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;bluesky@lna-dev.net&#34;</span>,
</span></span><span style="display:flex;"><span>    password<span style="color:#f92672">=</span>BLUESKY_PAT,
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># [...]</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> client_utils<span style="color:#f92672">.</span>TextBuilder()
</span></span><span style="display:flex;"><span>    caption<span style="color:#f92672">.</span>text(<span style="color:#e6db74">&#34;More at &#34;</span>)
</span></span><span style="display:flex;"><span>    caption<span style="color:#f92672">.</span>link(text<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://lna-dev.net/en/gallery&#34;</span>, url<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://lna-dev.net/en/gallery&#34;</span>)
</span></span><span style="display:flex;"><span>    caption<span style="color:#f92672">.</span>text(<span style="color:#e6db74">&#34;</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>tag(<span style="color:#e6db74">&#34;#&#34;</span> <span style="color:#f92672">+</span> element<span style="color:#f92672">.</span>term, element<span style="color:#f92672">.</span>term)
</span></span><span style="display:flex;"><span>        caption<span style="color:#f92672">.</span>text(<span style="color:#e6db74">&#34; &#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    media_url <span style="color:#f92672">=</span> entry<span style="color:#f92672">.</span>media_content[<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#34;url&#34;</span>]
</span></span><span style="display:flex;"><span>    alt_text <span style="color:#f92672">=</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></span><span style="display:flex;"><span>    alt_text <span style="color:#f92672">=</span> alt_text<span style="color:#f92672">.</span>group(<span style="color:#ae81ff">1</span>) <span style="color:#66d9ef">if</span> alt_text <span style="color:#66d9ef">else</span> <span style="color:#e6db74">&#34;Alt not found&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    bsClient<span style="color:#f92672">.</span>send_image(
</span></span><span style="display:flex;"><span>        text<span style="color:#f92672">=</span>caption,
</span></span><span style="display:flex;"><span>        image<span style="color:#f92672">=</span>download_image(media_url),
</span></span><span style="display:flex;"><span>        image_alt<span style="color:#f92672">=</span>alt_text,
</span></span><span style="display:flex;"><span>    )
</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><h3 id="how-to-get-your-bluesky-pat">How to get your Bluesky PAT</h3>
<p>To generate a <em>Personal Access Token</em> (<strong>PAT</strong>), also called an <em>App password</em>, go to <code>Settings =&gt; Privacy and Security =&gt; App passwords</code> or click <a href="https://bsky.app/settings/privacy-and-security">here</a>.</p>
<p>Now you can just click <code>Add App Password</code> and copy the text. That’s it! You can now add this to your script or environment variables.</p>
<h3 id="full-code">Full code</h3>


<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 style="color:#f92672">from</span> atproto <span style="color:#f92672">import</span> Client
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> atproto <span style="color:#f92672">import</span> client_utils
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>BLUESKY_PAT <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;BLUESKY_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>bsClient <span style="color:#f92672">=</span> Client()
</span></span><span style="display:flex;"><span>bsClient<span style="color:#f92672">.</span>login(
</span></span><span style="display:flex;"><span>    login<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;bluesky@lna-dev.net&#34;</span>,
</span></span><span style="display:flex;"><span>    password<span style="color:#f92672">=</span>BLUESKY_PAT,
</span></span><span style="display:flex;"><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>    <span style="color:#75715e"># name_list.append(&#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/bluesky&#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/bluesky?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> client_utils<span style="color:#f92672">.</span>TextBuilder()
</span></span><span style="display:flex;"><span>    caption<span style="color:#f92672">.</span>text(<span style="color:#e6db74">&#34;More at &#34;</span>)
</span></span><span style="display:flex;"><span>    caption<span style="color:#f92672">.</span>link(text<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://lna-dev.net/en/gallery&#34;</span>, url<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://lna-dev.net/en/gallery&#34;</span>)
</span></span><span style="display:flex;"><span>    caption<span style="color:#f92672">.</span>text(<span style="color:#e6db74">&#34;</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>tag(<span style="color:#e6db74">&#34;#&#34;</span> <span style="color:#f92672">+</span> element<span style="color:#f92672">.</span>term, element<span style="color:#f92672">.</span>term)
</span></span><span style="display:flex;"><span>        caption<span style="color:#f92672">.</span>text(<span style="color:#e6db74">&#34; &#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    media_url <span style="color:#f92672">=</span> entry<span style="color:#f92672">.</span>media_content[<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#34;url&#34;</span>]
</span></span><span style="display:flex;"><span>    alt_text <span style="color:#f92672">=</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></span><span style="display:flex;"><span>    alt_text <span style="color:#f92672">=</span> alt_text<span style="color:#f92672">.</span>group(<span style="color:#ae81ff">1</span>) <span style="color:#66d9ef">if</span> alt_text <span style="color:#66d9ef">else</span> <span style="color:#e6db74">&#34;Alt not found&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    bsClient<span style="color:#f92672">.</span>send_image(
</span></span><span style="display:flex;"><span>        text<span style="color:#f92672">=</span>caption,
</span></span><span style="display:flex;"><span>        image<span style="color:#f92672">=</span>download_image(media_url),
</span></span><span style="display:flex;"><span>        image_alt<span style="color:#f92672">=</span>alt_text,
</span></span><span style="display:flex;"><span>    )
</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:#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>

<h2 id="schedule-the-script">Schedule the script</h2>
<p>After creating such a script, you need to trigger it somehow. If you are using <a href="../../../tags/kubernetes/">Kubernetes</a>, you may want to check out the <a href="../pixelfed-automation/#the-schedule">Pixelfed post</a>, which explains how to set up a Kubernetes CronJob.</p>
<p>For those who just have a simple server, I recommend setting up a Linux CronJob to trigger the script periodically.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I mainly have two big problems with Bluesky. The first is that there is already a good protocol for decentralized social networks. So why reinvent the wheel? And not only did they reinvent it, but they also made it worse. The AT protocol relies much more on centralized services controlled by Bluesky, which is completely different from <a href="../../../tags/activitypub/">ActivityPub</a>. The second issue is that Bluesky is a for-profit company instead of a nonprofit, which is the standard in the <a href="../../media/fediverse/">Fediverse</a>. What’s really alarming is that this company doesn’t yet know how it wants to make money. So, it’s realistic that the platform will get worse in the future to generate revenue.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
  </channel>
</rss>