<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://kniebes.com/assets/xsl/rss.xsl" type="text/xsl" media="screen"?>
<rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
    xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
    xmlns:media="http://search.yahoo.com/mrss/"
>

    <channel>
        <title>M. Kniebes. · API</title>
        <atom:link href="https://kniebes.com/tag/api.xml" rel="self" type="application/rss+xml" />
        <link>https://kniebes.com</link>
        <description>Beiträge zum Tag API</description>
        <lastBuildDate>Sat, 02 Aug 2025 17:25:40 +0200</lastBuildDate>
        <language>de-DE</language>
        <sy:updatePeriod>hourly</sy:updatePeriod>
        <sy:updateFrequency>1</sy:updateFrequency>
        <generator>IO</generator>
        <atom:link rel="hub" href="https://pubsubhubbub.appspot.com"/>
        <atom:link rel="hub" href="https://pubsubhubbub.superfeedr.com"/>
        <atom:link rel="hub" href="https://websubhub.com/hub"/>

                
            
            <item>
                <title><![CDATA[BlueSky API und Tags!]]></title>                
                <link>https://kniebes.com/2025/08/02/bluesky-api-und-tags.html</link>
                <dc:creator><![CDATA[Markus Kniebes]]></dc:creator>
                <pubDate>Sat, 02 Aug 2025 17:25:40 +0200</pubDate>
                <guid isPermaLink="true">https://knieb.es/61d8</guid>
                <description><![CDATA[<p>Im Februar hatte ich mir mal einen <a href="https://bsky.app/profile/grumpy-old-man.bsky.social">BlueSky-Account</a> angelegt und bis neulich gab es immerhin ganze drei Posts. Aber wenn ich schon Mastodon mit meinen Blogposts zuschütte, könnte ich das auch mit dem Bluesky-Account machen.</p>

<p>Ich habe es mir einfach gemacht und benutze dieses <a href="https://github.com/shahmal1yev/blueskysdk">BlueSky SDK</a> und das Einbinden war auch denkbar einfach. Allerdings habe ich mich gewundert, warum meine Tags in den Posts über die API nicht verlinkt werden. Tags sind ebensowenig unkomfortabel zu handhaben wie Links. Auch für Tags müssen <a href="https://docs.bsky.app/docs/advanced-guides/post-richtext#rich-text-facets">Facets zur Richtextaufbereitung</a> benutzt werden.</p>

<p>Ein Post für den Text <mark>#tags @ € £ #multibyte #test</mark> sieht dann in etwa so aus:</p>

<pre><code class="language-js">{
    "text": "#tags @ € £ #multibyte #test",
    "facets": [
        {
            "index": {
                "byteStart": 0,
                "byteEnd": 5
            },
            "features": [
                {
                    "$type": "app.bsky.richtext.facet#tag",
                    "tag": "tags"
                }
            ]
        },
        {
            "index": {
                "byteStart": 15,
                "byteEnd": 25
            },
            "features": [
                {
                    "$type": "app.bsky.richtext.facet#tag",
                    "tag": "multibyte"
                }
            ]
        },
        {
            "index": {
                "byteStart": 26,
                "byteEnd": 31
            },
            "features": [
                {
                    "$type": "app.bsky.richtext.facet#tag",
                    "tag": "test"
                }
            ]
        }
    ]
}
</code></pre>

<p>In der Tat ist es notwendig, Tags vor dem Posten eigenhändig zu suchen und deren Position zu bestimmen. Ich habe den <code>Atproto\Builders\Bluesky\RecordBuilder</code> aus dem og. SDK erweitert und darin die Methode <code>addText()</code> erweitert:</p>

<pre><code class="language-php">class RecordBuilderWithTags extends RecordBuilder
{
    public function addText($text): self
    {
        parent::addText($text);

        $tags = $this-&gt;extractTags($text);
        if (empty($tags)) {
            return $this;
        }

        if (!isset($this-&gt;record-&gt;facets)) {
            $this-&gt;record-&gt;facets = [];
        }

        foreach ($tags as $tag) {
            $offset = 0;
            while (($pos = strpos($text, $tag, $offset)) !== false) {
                $offset = $pos + 1;
                $this-&gt;record-&gt;facets[] = [
                    'index' =&gt; [
                        'byteStart' =&gt; $pos,
                        'byteEnd' =&gt; strlen($tag) + $pos,
                    ],
                    'features' =&gt; [
                        [
                            '$type' =&gt; 'app.bsky.richtext.facet#tag',
                            'tag' =&gt; substr($tag, 1), // without '#'
                        ]
                    ]
                ];
            }
        }

        return $this;
    }

    protected function extractTags(string $text): array
    {
        $pattern = '/#[A-Z0-9]+/i';
        preg_match_all($pattern, $text, $matches);
        $tags = $matches[0];

        return array_unique($tags);
    }
}
</code></pre>

<p>Es ist erforderlich, nicht die Multibyte-Funktionen <code>mb_strpos()</code> und <code>mb_strlen()</code> zu benutzen, da die API in den Werten <code>byteStart</code> und <code>byteEnd</code> wirklich die Position von Bytes und nicht von Zeichen erwartet.</p>

<p>Eine kleine Anmerkung, bevor jemand auf die schräge Idee kommt, das zu benutzen: Kombination von Tags wie #Proton und #ProtonAuthenticator führe zu Problemen, da strpos() mit <strong>#Proton</strong> auch die Position von <strong>#Proton</strong>Authenticator findet.</p>

<p>Ja, ich kann verstehen, dass man damit ein Höchstmaß an Flexibilität bei der Ausgestaltung der schlappen 300 Zeichen hat. Aber es ist auch absurd unkomfortabel.</p>
<hr>
<p><a href="mailto:m@kniebes.io?subject=BlueSky API und Tags! (knieb.es/61d8)">Per E-Mail antworten</a><br>
<a href="https://kniebes.com/frag-mich.html">Du hast Fragen an mich oder ein Thema für die FAQ?</a></p>]]></description>

                            
                    <category><![CDATA[Software]]></category>
                
                    <category><![CDATA[BlueSky]]></category>
                
                    <category><![CDATA[API]]></category>
                                
            </item>

                            
        
    </channel>
</rss>
