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

Posted under General

Song

Janitor

Bluesky serves two types of sample versions: feed_thumbnail and feed_fullsize. These are not the best public versions on the site. In order to access higher-quality versions, you need to reformat the URL to retrieve the original blob instead. Additionally, Bluesky uses a format for accounts and posts that breaks when usernames are changed. Solving these issues requires changing the URL to include the user's DID (decentralized identifier), but performing this process manually is tedious.

The two userscripts below will automatically get the best image quality and resolve to more stable links to Bluesky account and post pages:

Bluesky higher-quality image redirect: https://gist.github.com/Tarrgon/a58375cd3c1f15d8fd4238a2a7df35b5
Bluesky stable links redirect: https://gist.github.com/Tarrgon/c84f267a4f97314090b4870760b8fb8f

In order to use these, 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 scripts linked above into new scripts and save. Bluesky image links will now reload to higher-quality versions, and post and account links will be more stable for archiving purposes. The image quality improvement will vary from almost imperceptible to significantly less JPEG compression, but this version will always be superior.

Wayback Machine links to the above scripts in the event that the GitHub pages are unavailable in the future (these may be out of date):
https://web.archive.org/web/20250427005625/https://gist.github.com/Tarrgon/a58375cd3c1f15d8fd4238a2a7df35b5
https://web.archive.org/web/20250509034554/https://gist.github.com/Tarrgon/c84f267a4f97314090b4870760b8fb8f

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?

Song

Janitor

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>

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.

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

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

sorry to ask, but i genuinely do not know how to use the script

what am i supposed to do exactly for it to work?

Song

Janitor

istole50watermelons said:
sorry to ask, but i genuinely do not know how to use the script

what am i supposed to do exactly for it to work?

1) Download a userscript manager (in this case Tampermonkey, linked in the original post)
2) Go to your extensions manager page and open the options for Tampermonkey
3) Click the + icon to add a new script
4) Clear the template and copy-paste one of the scripts into it exactly
5) Hit File -> Save in the top left
6) Repeat steps 3-5 for the second script linked in the first post

That is it. Once installed, userscripts work automatically and can be completely forgotten about while you browse. Extensions like userscript managers will be disabled in private browser windows by default, but you can change this behavior if you want to from your browser's extensions menu.

I hope this explanation was clear for you! If you have any other questions, feel free to ask.

Same can be done for videos but with considerably more verbose steps.

Open dev tools, click on a post, then find the request to endpoint
/xrpc/app.bsky.unspecced.getPostThreadV2
You can find did in the response. Path:
thread[0].value.post.author.did
(did:plc: must be prepended to value)
and cid:
thread[0].value.post.embed.cid
Substitute parameters: https://bsky.social/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}. This is the same as the images.

As https://e621.net/wiki_pages/howto:sites_and_sources mentioned:
Videos are compressed to 1080p resolution (720p before July 2025) at 30 fps in MP4 format, regardless of its original dimensions(?), and are limited to 3 minutes at most as of March 2025 (before this, the limit was 60 seconds)

Updated

Well shit, i've posted multiple Bluesky images and this kept grinding my gears. I didn't realize that there was a solution.
Honestly i'll prefer to upload from FurAffinity where possible, i agree with the previous comment that Bluesky isn't meant as an art gallery at all

Original page: https://e621.net/forum_topics/56919