Topic: PSA: Obtaining higher-quality images from Bluesky sources & adding Bluesky permalinks

Posted under General

Song

Janitor

By default, Bluesky will serve you two types of sample versions: feed_thumbnail and feed_fullsize. These are not the best publicly available versions on the site. In order to access a higher-quality version, you need to reformat the URL to retrieve the original blob version instead. This requires changing the URL to include the user's DID (decentralized identifier), but performing this process manually every time is awful. Instead, you should use a userscript linked to in the Bluesky entry of our Sites and Sources wiki pages:

Bluesky entry in the Sites and Sources pages: https://e621.net/wiki_pages/31895#bluesky
Bluesky redirect userscript: https://gist.github.com/Tarrgon/a58375cd3c1f15d8fd4238a2a7df35b5

In order to use userscripts, you will need a userscript manager. An example of a userscript manager is Tampermonkey: https://www.tampermonkey.net/
Once you install a userscript manager extension of your choice, copy-paste the script linked above into a new script and save. All Bluesky image links will now reload to the higher-quality blob version. The quality improvement will vary from virtually imperceptible to significantly less JPEG compression, but this version will always be superior.

2025-05-08 edit:

We now also have a userscript to convert Bluesky account and post pages to use more permanent DID numbers, available here:
https://gist.github.com/Tarrgon/c84f267a4f97314090b4870760b8fb8f

Currently, a username change can result in broken links due to Bluesky treating usernames as part of the post identifier. Compare the following:
https://bsky.app/profile/quantamagazine.bsky.social
https://bsky.app/profile/did:plc:vfktz6qe6vy7serr3wqutpzt

Both of these links lead to the same page, but only the second will remain valid if an individual or organization changes its social media name or associated domain. The DID link version is a more stable solution for including in source fields, avoiding but not eliminating the risk of link rot, while remaining a primary source (Wayback Machine is invaluable, but technically a third-party source).

Wayback Machine links to the above scripts in the event that the GitHub pages are unavailable in the future:
Sample-to-original-blob
Username-to-DID-links

Updated

I checked this a few days ago and smiled when I found out only like a 1/5th of bsky posts are sourced from that API endpoint.

Sometimes I wondered if it was possible to add a feature to the upload page to enter the URL of the post and it would extract the best possible image URL associated with the post automatically...

Donovan DMC

Former Staff

aninox said:
Sometimes I wondered if it was possible to add a feature to the upload page to enter the URL of the post and it would extract the best possible image URL associated with the post automatically...

Danbooru has this but multiple successive e6 devs have denied implementing it due to how complicated it can be to upkeep

Maybe the BlueSky developer should be approached about being able to conveniently retrieve the image file you want to download

nin10dope said:
Maybe the BlueSky developer should be approached about being able to conveniently retrieve the image file you want to download

Like its predecessor Twitter/X or any other social media site, Bluesky is not designed as an art gallery or a file-sharing service.
Everything is compressed to shit and more, and artists should instead be persuaded to post elsewhere if they want to retain their artwork quality.

The setup is quite a bit more complex than installing a simple userscript, but you can also use FoxTrove to scrape the best available versions from Bluesky (and many other sites), then automatically reverse-search each one to identify posts that could potentially be replaced.

thegreatwolfgang said:
Like its predecessor Twitter/X or any other social media site, Bluesky is not designed as an art gallery or a file-sharing service.
Everything is compressed to shit and more, and artists should instead be persuaded to post elsewhere if they want to retain their artwork quality.

Yeahhhh you made me remember Facebook utterly nuking everything posted there
At least Messenger doesn't commit war crimes on images sent through it

Just to make sure I have been doing this right, doing manually like this is still right or is there a new way?

https://bsky.social/xrpc/com.atproto.sync.getBlob?did=(stuff)&cid=(stuff)

For example, taking this link:

https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:3y2b7u5qnwzwibk6o32umgza/bafkreif66mhpe3y5hlbsx2poqka3hf42ygk4q5yasdjwrzunr6ue6nifja@jpeg

And using these parts for each "stuff"

"did:plc:3y2b7u5qnwzwibk6o32umgza"
"bafkreif66mhpe3y5hlbsx2poqka3hf42ygk4q5yasdjwrzunr6ue6nifja" (taking off the "@jpeg")

Sigh, uploading from blusky is really tiring but I really don't want to use tampermonkey or something lol.

notknow said:
Just to make sure I have been doing this right, doing manually like this is still right or is there a new way?

https://bsky.social/xrpc/com.atproto.sync.getBlob?did=(stuff)&cid=(stuff)

For example, taking this link:

https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:3y2b7u5qnwzwibk6o32umgza/bafkreif66mhpe3y5hlbsx2poqka3hf42ygk4q5yasdjwrzunr6ue6nifja@jpeg

And using these parts for each "stuff"

"did:plc:3y2b7u5qnwzwibk6o32umgza"
"bafkreif66mhpe3y5hlbsx2poqka3hf42ygk4q5yasdjwrzunr6ue6nifja" (taking off the "@jpeg")

Sigh, uploading from blusky is really tiring but I really don't want to use tampermonkey or something lol.

I tried that method, all I get is a white screen with "Source image is unreachable" error message.

schlob said:
I tried that method, all I get is a white screen with "Source image is unreachable" error message.

It should look like this:
https://bsky.social/xrpc/com.atproto.sync.getBlob?did=did:plc:3y2b7u5qnwzwibk6o32umgza&cid=bafkreif66mhpe3y5hlbsx2poqka3hf42ygk4q5yasdjwrzunr6ue6nifja
After entering the url it should turn into this:
https://morel.us-east.host.bsky.network/xrpc/com.atproto.sync.getBlob?did=did:plc:3y2b7u5qnwzwibk6o32umgza&cid=bafkreif66mhpe3y5hlbsx2poqka3hf42ygk4q5yasdjwrzunr6ue6nifja

I may have explained it a bit wrong but it works for me lol

Hi Song, I noticed some of the pictures I uploaded (from suitedwolfie) got replaced using this script. I understand that we want to host the higher quality version available, but at the same time my upload limit was reduced due to the replacements.
This is the first time I tried uploading images to e6 and I wasn't aware of this script's existence (I didn't check the forum). I was uploading those because I noticed that no one else was uploading this artist's recent comic pages.

I can't help but feel like I'm getting unfairly penalized through these replacements, even though I tried to help by uploading them here. Is there no way to replace images on e6 without punishing the previous uploader?

nyaaaaa said:
Hi Song, I noticed some of the pictures I uploaded (from suitedwolfie) got replaced using this script. I understand that we want to host the higher quality version available, but at the same time my upload limit was reduced due to the replacements.
This is the first time I tried uploading images to e6 and I wasn't aware of this script's existence (I didn't check the forum). I was uploading those because I noticed that no one else was uploading this artist's recent comic pages.

I can't help but feel like I'm getting unfairly penalized through these replacements, even though I tried to help by uploading them here. Is there no way to replace images on e6 without punishing the previous uploader?

The uploading limit penalty exists to ensure users post content that meets the Uploading Guidelines and that they do not upload sample versions, which can create more work for site staff and users by needing to replace them later. To encourage uploading the best versions, replacements by default penalize the user unless that user is replacing their own uploads, but they can be flipped to not penalize on a case-by-case basis if something is truly outside of that user's control. I would not worry too much about this number - we don't think any less of you, and it's not going to hurt your account in the long run to have some posts deleted or replaced. You can still upload 8 posts an hour, will see that rise back over time, and are now equipped with more information to avoid post replacements in the future. We also want to keep this system transparent, rather than relying on opaque posting limits or activity throttling like major websites, even though it can be discouraging seeing a number go down.

In the case of flags or replacements when a user uploads samples, we do bring up cases where a user's upload limit is slashed excessively for reasons outside their reasonable knowledge or control. We are not going to leave a user at 0 or some other very low number because they didn't happen to read the forum or site wiki about sources, as there is no reasonable expectation that they would be aware of these resources. We manually bump those up back to some recoverable default, such as 5 or 10, and users can also make requests to staff if we forget to do so. User contributions are appreciated and wanted, even if they miss something due to not knowing the ins-and-outs of every website, and we're not going to be draconian about it.

Also see https://danielmangum.com/posts/this-website-is-hosted-on-bluesky/ for details on how Bluesky content addressing works.

I whipped up a tiny webpage for converting cdn.bsky.app URLs to the correct https://bsky.social/xrpc/com.atproto.sync.getBlob?did=<User DID>&cid=<Content CID> format:

Source Code:

<!DOCTYPE html>
<input id="input"></input>
<button onclick="convert()">Convert</button><br>
<a id="output"></a>
<script>
let input = document.getElementById('input');
let output = document.getElementById('output');
function convert() {
    let src = input.value;
    let arr = src.split("/");
    for (i = 0; i < arr.length; i++) {
        if (arr[i].startsWith("did:")) {
            let did = arr[i + 0];
            let cid = arr[i + 1].split("@")[0];
            let fmt = "https://bsky.social/xrpc/com.atproto.sync.getBlob?did=" + did + "&cid=" + cid;
            output.innerHTML = fmt;
            output.href = fmt;
            return;            
        }
    }
}
</script>

(I also tried to upload it here but it seems that script execution is blocked on bsky blob origins, so you'll have to just save the html and open it locally instead)

Updated

Donovan DMC

Former Staff

0f8c4c9d05154171ae8 said:
I whipped up a tiny webpage for converting cdn.bsky.app URLs to the correct https://bsky.social/xrpc/com.atproto.sync.getBlob?did=<User DID>&cid=<Content CID> format:

What's the point of that when you can just use the userscript which will redirect you with the only needed input being opening the image in a new tab

istole50watermelons said:
how does the Github script work, exactly?

It grabs parts of the url and reconstructs them into a url which resolves to the highest quality image bluesky can provide

donovan_dmc said:
What's the point of that when you can just use the userscript which will redirect you with the only needed input being opening the image in a new tab

Friction matters.

Even though the instructions for setting up user scripts are provided and not really hard, it’s still a turn off for those who might otherwise have helped with a quick one-off edit but not if they have to go through the extra hassle. Having a quick webpage they could just open immensely reduces that friction. Or even just a short, explicit instruction for how to convert it manually without setup, is conducive to motivating such drive-by edits.

Crowdsourcing efforts generally should encourage good-faith drive-by contributions instead of selecting only for those intending to dedicate time (the pool of which may or may not include obsessive bad-faith actors).

Updated

I just tried using the redirect userscript on the following post, just to test it, and it did not work. https://bsky.app/profile/hijibbles.bsky.social/post/3logrgzpwjs2o
Not even doing the URL substitutions manually got me anything useful. (from https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:p7zf4b3t6dqrkm3l4z4ywuc5/bafkreieq2b6iqsi5ecxm5o5wbfpkf6s7jo7xqzxb2ulictygdqylv2rn44@jpeg to https://bsky.social/xrpc/com.atproto.sync.getBlob?did=did:plc:p7zf4b3t6dqrkm3l4z4ywuc5&cid=bafkreieq2b6iqsi5ecxm5o5wbfpkf6s7jo7xqzxb2ulictygdqylv2rn44)

I received the following JSON instead:

{"error":"InvalidRequest","message":"Error: Params must have the property \"cid\""}

The URL in my browser immediately throws out the "&cid=..." part. Maybe the Bluesky server is redirecting me?

ichhabs said:
I just tried using the redirect userscript on the following post, just to test it, and it did not work. https://bsky.app/profile/hijibbles.bsky.social/post/3logrgzpwjs2o
Not even doing the URL substitutions manually got me anything useful. (from https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:p7zf4b3t6dqrkm3l4z4ywuc5/bafkreieq2b6iqsi5ecxm5o5wbfpkf6s7jo7xqzxb2ulictygdqylv2rn44@jpeg to https://bsky.social/xrpc/com.atproto.sync.getBlob?did=did:plc:p7zf4b3t6dqrkm3l4z4ywuc5&cid=bafkreieq2b6iqsi5ecxm5o5wbfpkf6s7jo7xqzxb2ulictygdqylv2rn44)

I received the following JSON instead:

{"error":"InvalidRequest","message":"Error: Params must have the property \"cid\""}

The URL in my browser immediately throws out the "&cid=..." part. Maybe the Bluesky server is redirecting me?

Your link works fine on my end. Perhaps disable the userscript before testing the link?

By the way, there's a far simpler way of getting the user's DID from its handle: just go to https://<USER HANDLE>/.well-known/atproto-did

Updated

Donovan DMC

Former Staff

0f8c4c9d05154171ae8 said:
By the way, there's a far simpler way of getting the user's DID from its handle: just go to https://<USER HANDLE>/.well-known/atproto-did

This won't work for the many users that have custom domains as handles

0f8c4c9d05154171ae8 said:
By the way, there's a far simpler way of getting the user's DID from its handle: just go to https://<USER HANDLE>/.well-known/atproto-did

Sorry, could you write an example using that link? I couldn't make it work on my end.

Updated

Yep it worked, I forgot the "bsky.social" part welp

https://shiiiiiiiva.bsky.social/.well-known/atproto-did
did:plc:4lwjijzjv2tulzc22w4rvm3o

Edit: Also used the small webpage code and it worked for me, less work than doing what I was doing manually copying and pasting the two IDs and having to cut off the "@JPEG" every time lol, thanks.

No I don't want to use tampermonkey.

Updated

Can the userscripts be rewritten as simple bookmarklets? Especially the first script seems simple enough that adding the following as a bookmark should just work:

javascript:(async function () { 'use strict'; let url = window.location.pathname.split("/"); if (url.length < 6) return; let did = url[4]; let cid = url[5].slice(0, url[5].lastIndexOf("@")); if (did && cid) { window.location.href = `https://bsky.social/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`; } })();

0f8c4c9d05154171ae8 said:
Your link works fine on my end. Perhaps disable the userscript before testing the link?

I did, of course.

I did some testing and found the issue: I have a Firefox Add-on called Neat URL that is supposed to remove bullshit tracking data from URLs, but it gets triggered by the "cid" parameter. In the settings, it's simply marked as "Campaign tracking (others)". I have added it to the override list, which fixed the problem. If I ever encounter a cid parameter in the wild, I will have to add a domain-specific block rule.