parent
f853f10c69
commit
38ad7b1156
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue