parent
f853f10c69
commit
38ad7b1156
|
|
@ -4,6 +4,20 @@ function getFullSizeImg(link: string) {
|
|||
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 {
|
||||
name = "bluesky";
|
||||
features: ScrapeEngineFeature[] = ["content"];
|
||||
|
|
@ -29,6 +43,52 @@ export default class Bluesky extends ScrapeEngineBase {
|
|||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue