Update Bluesky.ts

Added support for Bluesky videos.
master
Nes370 2026-10-12 20:50:39 -07:00
parent f853f10c69
commit 38ad7b1156
1 changed files with 60 additions and 0 deletions

View File

@ -4,6 +4,20 @@ function getFullSizeImg(link: string) {
return link.replace("/feed_thumbnail/", "/feed_fullsize/"); return link.replace("/feed_thumbnail/", "/feed_fullsize/");
} }
/**
* Bluesky serves video via HLS in the player, but the underlying blob is a
* single .mp4 retrievable from the AT Protocol sync endpoint:
*
* https://bsky.social/xrpc/com.atproto.sync.getBlob?did=<DID>&cid=<CID>
*
* The DID and CID are URL-encoded in the <video> poster's path:
*
* https://video.bsky.app/watch/<DID>/<CID>/thumbnail.jpg
*
* We reconstruct the blob URL and mark the post as uploadMode="content"
* so SzuruChrome fetches it in the browser instead of asking Szurubooru's
* server to download it (which would fail on the blob endpoint).
*/
export default class Bluesky extends ScrapeEngineBase { export default class Bluesky extends ScrapeEngineBase {
name = "bluesky"; name = "bluesky";
features: ScrapeEngineFeature[] = ["content"]; features: ScrapeEngineFeature[] = ["content"];
@ -29,6 +43,52 @@ export default class Bluesky extends ScrapeEngineBase {
result.tryAddPost(post); result.tryAddPost(post);
} }
// Videos
const videoEls = Array.from<HTMLVideoElement>(
document.querySelectorAll('video[poster^="https://video.bsky.app/watch"]')
);
for (const v of videoEls) {
const poster = v.getAttribute("poster");
if (!poster) continue;
const blobUrl = this.buildBlobUrl(poster);
if (!blobUrl) {
this.log(`Could not parse poster URL: ${poster}`);
continue;
}
const post = new ScrapedPost();
post.pageUrl = document.location.href;
post.contentUrl = blobUrl;
post.contentType = "video";
result.tryAddPost(post);
}
return result; return result;
} }
/**
* Poster format:
* https://video.bsky.app/watch/<DID-urlencoded>/<CID>/thumbnail.jpg
* Returns the AT Protocol blob URL, or undefined if the poster doesn't
* match the expected shape.
*/
private buildBlobUrl(poster: string): string | undefined {
try {
const u = new URL(poster);
const parts = u.pathname.split("/").filter((p) => p.length > 0);
// Expected: ["watch", "<DID>", "<CID>", "thumbnail.jpg"]
if (parts.length < 3 || parts[0] !== "watch") return undefined;
const did = decodeURIComponent(parts[1]); // did:plc:...
const cid = parts[2]; // bafkrei...
return (
"https://bsky.social/xrpc/com.atproto.sync.getBlob" +
`?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}`
);
} catch {
return undefined;
}
}
} }