{"id":1141,"date":"2023-10-21T20:45:11","date_gmt":"2023-10-22T03:45:11","guid":{"rendered":"https:\/\/werebear.net\/main\/?p=1141"},"modified":"2023-10-21T21:19:46","modified_gmt":"2023-10-22T04:19:46","slug":"advanced-freebooting-technology","status":"publish","type":"post","link":"https:\/\/werebear.net\/main\/blog\/2023\/10\/21\/advanced-freebooting-technology\/","title":{"rendered":"Advanced Freebooting Technology"},"content":{"rendered":"\n<p>In my <a href=\"https:\/\/werebear.net\/main\/blog\/2023\/10\/19\/blue-sky-thinking\/\" target=\"_blank\" rel=\"noreferrer noopener\">last post<\/a>, I discussed how Bluesky\u2019s ATProtocol made automation with IFTTT and Zapier nearly impossible. Fortunately, at least in Zapier\u2019s case, \u201calmost\u201d was the operative word. I found a couple of extremely <a href=\"https:\/\/community.zapier.com\/code-webhooks-52\/sending-tweets-to-blue-sky-bsky-app-27950\" target=\"_blank\" rel=\"noreferrer noopener\">useful JavaScript chunks<\/a> on the Zapier forums that provided the bedrock for a subsequent solution. And now, viola! When I post to Mastodon, a matching post is made automatically to Bluesky\u2026 with some caveats. But let\u2019s start with the fundamentals of this Zap.<\/p>\n\n\n\n<p>Signing up for a new Zapier account is free, but unless you\u2019re willing to pay twenty bucks a month, you\u2019re limited to two-step Zaps. That made the original author\u2019s impelemtation problematic, because I\u2019m a miser and I spend enough on subscriptions as it stands. So if I wanted to achieve the same effect, I would need to combine both those chunks into a single script with an RSS trigger.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"966\" height=\"573\" src=\"https:\/\/i0.wp.com\/werebear.net\/main\/wp-content\/uploads\/2023\/10\/img_2735-1.jpg?resize=966%2C573&#038;ssl=1\" alt=\"\" class=\"wp-image-1140\" srcset=\"https:\/\/i0.wp.com\/werebear.net\/main\/wp-content\/uploads\/2023\/10\/img_2735-1.jpg?w=966&amp;ssl=1 966w, https:\/\/i0.wp.com\/werebear.net\/main\/wp-content\/uploads\/2023\/10\/img_2735-1.jpg?resize=300%2C178&amp;ssl=1 300w\" sizes=\"auto, (max-width: 966px) 100vw, 966px\" \/><figcaption class=\"wp-element-caption\">The Zapier diagram for this Zap.<\/figcaption><\/figure>\n\n\n\n<p>After a frustrating false start, I finally grafted both halves together into a unified, functioning script with all the variables appropriately named (\u201cskeet\u201d vs. \u201ctweet\u201d) and a new post length truncator. The truncation code was critical as content over 300 characters will cause the post to fail. Since Mastodon posts can be 500 characters or longer, I put in a conditional that cuts off the main post at 240 characters if needed. The remainder of the space is for a \u201c(More)\u201d note and the Mastodon URL at the end.<\/p>\n\n\n\n<p>This approach does have some problems. Media won\u2019t display across the ATProtocol\/ActivityPub divide due to the complexities of posting media in one format, then displaying it in the other. As an example, Bluesky doesn\u2019t support natively posting videos, so trying to show a native Mastodon video on a service without any video support at all is\u2026 rough.<\/p>\n\n\n\n<p>Four parameters must be supplied when creating a Zap with this script. These are specified in the \u201cCode by Zapier\u201d JavaScript step. Those parameters are: username, password, skeet_text, and skeet_url.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"853\" height=\"1024\" src=\"https:\/\/i0.wp.com\/werebear.net\/main\/wp-content\/uploads\/2023\/10\/img_2736-1.jpg?resize=853%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-1139\" srcset=\"https:\/\/i0.wp.com\/werebear.net\/main\/wp-content\/uploads\/2023\/10\/img_2736-1.jpg?resize=853%2C1024&amp;ssl=1 853w, https:\/\/i0.wp.com\/werebear.net\/main\/wp-content\/uploads\/2023\/10\/img_2736-1.jpg?resize=250%2C300&amp;ssl=1 250w, https:\/\/i0.wp.com\/werebear.net\/main\/wp-content\/uploads\/2023\/10\/img_2736-1.jpg?w=1086&amp;ssl=1 1086w\" sizes=\"auto, (max-width: 853px) 100vw, 853px\" \/><figcaption class=\"wp-element-caption\">The requisite Zapier fields.<\/figcaption><\/figure>\n\n\n\n<p>The \u201cusername\u201d and \u201cpassword\u201d fields are exactly what you\u2019d expect. Just be sure to use an <em>application<\/em> password from Bluesky rather than your <em>primary<\/em> password. The next two fields are also fairly self-explanatory. Use the \u201cskeet_text\u201d field for the post\u2019s content, and the \u201cskeet_url\u201d field for the Mastodon post\u2019s web address. <\/p>\n\n\n\n<p>But where can you find an RSS feed of your Mastodon account in the first place? That\u2019s the easy bit. Every Mastodon account has one built into it! For example, if your account is&#8230;<\/p>\n\n\n\n<p><code>https:\/\/weirdscience.org\/@technomancer<\/code><\/p>\n\n\n\n<p>&#8230;your RSS feed is&#8230;<\/p>\n\n\n\n<p><code>https:\/\/weirdscience.org\/@technomancer.rss<\/code><\/p>\n\n\n\n<p>&#8230;and updates every time you post. Simply point Zapier&#8217;s RSS trigger at your feed, and the rest is cake. <\/p>\n\n\n\n<p>Want to make your own crossposter? All the code you need is below!<\/p>\n\n\n\n<hr class=\"wp-block-separator has-text-color has-black-color has-alpha-channel-opacity has-black-background-color has-background is-style-default\"\/>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>\/\/ Stage 1: Get Authentication Token\n\n\nconst url = 'https:\/\/bsky.social\/xrpc\/com.atproto.server.createSession';\n\nconst data = {\n    identifier: inputData&#91;'username'], \/\/ Input from Zapier, assuming 'username' corresponds to 'BLUESKY_HANDLE'\n    password: inputData&#91;'password']    \/\/ Input from Zapier, corresponding to 'BLUESKY_APP_PASSWORD'\n};\n\n\nconst response = await fetch(url, {\n    method: 'POST',\n    body: JSON.stringify(data),  \/\/ Sending the data as a JSON string\n    headers: {\n        'Content-Type': 'application\/json' \/\/ Use JSON content type\n    },\n    redirect: 'manual'\n});\n\n\/\/ Check if the response status is OK\nif (!response.ok) {\n    console.error('Failed request', await response.text());\n    return null;\n}\n\nconst responseBody = await response.json(); \/\/ Parse the JSON response\n\nif (!responseBody.accessJwt) {\n    console.error('accessJwt not found in the response');\n    return null;\n}\n\n\/\/ Stage 2: Post to Bsky\n\n\/\/ Truncate the post if it's too long.\n\nvar RawSkeetText = inputData&#91;'skeet_text'];\nif (RawSkeetText.length &gt; 240) {\n\tvar TruncatedSkeet = RawSkeetText.substring(0, 240) + '... (More)';\n} else {\n\tvar TruncatedSkeet = RawSkeetText;\n}\n\n\/\/ Define the requisite constants.\n\nconst secondUrl = 'https:\/\/bsky.social\/xrpc\/com.atproto.repo.createRecord';\nconst authToken = responseBody.accessJwt;\n\nconst skeetText = TruncatedSkeet + \"\\r\\n\" + inputData&#91;'skeet_url'];\nconst skeetURL = inputData&#91;'skeet_url'];\n\n\/\/ Finding the start and end byte positions of the skeetURL within skeetText\nconst byteStart = Buffer.from(skeetText.slice(0, skeetText.indexOf(skeetURL))).length;\nconst byteEnd = byteStart + Buffer.from(skeetURL).length;\n\nconst recordData = {\n    \"$type\": \"app.bsky.feed.post\",\n    \"text\": skeetText,\n    \"createdAt\": new Date().toISOString(),\n    \"facets\": &#91;\n        {\n            \"index\": {\n                \"byteStart\": byteStart,\n                \"byteEnd\": byteEnd\n            },\n            \"features\": &#91;\n                {\n                    \"$type\": \"app.bsky.richtext.facet#link\",\n                    \"uri\": skeetURL\n                }\n            ]\n        }\n    ]\n};\n\nconst postData = {\n    repo: inputData&#91;'username'],\n    collection: 'app.bsky.feed.post',\n    record: recordData\n};\n\nconst postResponse = await fetch(secondUrl, {\n    method: 'POST',\n    body: JSON.stringify(postData),\n    headers: {\n        'Content-Type': 'application\/json',\n        'Authorization': `Bearer ${authToken}`\n    }\n});\n\nif (!postResponse.ok) {\n    console.error('Failed request', await postResponse.text());\n    output = {\n        success: false,\n        message: \"Failed to create record\"\n    };\n    return;\n}\n\nconst postResponseBody = await postResponse.json();\nif (!postResponseBody.success) {\n    console.error('Failed to create record', postResponseBody.message);\n    output = {\n        success: false,\n        message: postResponseBody.message\n    };\n    return;\n}\n\noutput = &#91;{\n    success: true\n}];\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>In my last post, I discussed how Bluesky\u2019s ATProtocol made automation with IFTTT and Zapier nearly impossible. Fortunately, at least in Zapier\u2019s case, \u201calmost\u201d was the operative word. I found a couple of extremely useful JavaScript chunks on the Zapier forums that provided the bedrock for a subsequent solution. And now, viola! When I post to Mastodon, a matching post is made automatically to Bluesky\u2026 with some caveats. But let\u2019s start with the fundamentals of this Zap.<\/p>\n","protected":false},"author":1,"featured_media":1138,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"In my last post, I said Bluesky made automation with IFTTT and Zapier almost impossible. Fortunately, for Zapier, \u201calmost\u201d was the operative word. In this post, I share all the details on my solution!","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[67,44,43],"tags":[68,70,69,74,47,71],"class_list":["post-1141","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-atprotocol","category-fediverse","category-technology","tag-activitypub","tag-atprotocol","tag-bluesky","tag-javascript","tag-mastodon","tag-social-media"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/werebear.net\/main\/wp-content\/uploads\/2023\/10\/img_2734-1.jpg?fit=2999%2C1687&ssl=1","jetpack_shortlink":"https:\/\/wp.me\/peoZy7-ip","jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/werebear.net\/main\/wp-json\/wp\/v2\/posts\/1141","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/werebear.net\/main\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/werebear.net\/main\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/werebear.net\/main\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/werebear.net\/main\/wp-json\/wp\/v2\/comments?post=1141"}],"version-history":[{"count":13,"href":"https:\/\/werebear.net\/main\/wp-json\/wp\/v2\/posts\/1141\/revisions"}],"predecessor-version":[{"id":1162,"href":"https:\/\/werebear.net\/main\/wp-json\/wp\/v2\/posts\/1141\/revisions\/1162"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/werebear.net\/main\/wp-json\/wp\/v2\/media\/1138"}],"wp:attachment":[{"href":"https:\/\/werebear.net\/main\/wp-json\/wp\/v2\/media?parent=1141"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/werebear.net\/main\/wp-json\/wp\/v2\/categories?post=1141"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/werebear.net\/main\/wp-json\/wp\/v2\/tags?post=1141"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}