parent
a6a4308276
commit
8004d0a183
|
|
@ -1,77 +1,45 @@
|
||||||
import {
|
import { ScrapeEngineBase, ScrapeResult, ScrapedPost, ScrapeEngineFeature, ContentType} from "../ScrapeEngine.js";
|
||||||
ScrapeEngineBase,
|
|
||||||
ScrapeResult,
|
|
||||||
ScrapedPost,
|
|
||||||
ScrapeEngineFeature,
|
|
||||||
ContentType,
|
|
||||||
} from "../ScrapeEngine.js";
|
|
||||||
|
|
||||||
interface Attachment {
|
interface Attachment {
|
||||||
url: string; // original media URL, ?sensitive=true stripped
|
url: string; // original media URL
|
||||||
contentType: ContentType;
|
contentType: ContentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles Mastodon-compatible instances, including forks such as baraag.net.
|
|
||||||
*
|
|
||||||
* Mastodon renders media in three different shapes:
|
|
||||||
*
|
|
||||||
* 1. Images:
|
|
||||||
* <a class="media-gallery__item-thumbnail" href=".../original/....png">
|
|
||||||
* <img src=".../small/....png">
|
|
||||||
* </a>
|
|
||||||
* → read the anchor href.
|
|
||||||
*
|
|
||||||
* 2. GIFVs:
|
|
||||||
* <div class="media-gallery__gifv autoplay">
|
|
||||||
* <video class="media-gallery__item-gifv-thumbnail"
|
|
||||||
* src=".../original/....mp4" autoplay loop></video>
|
|
||||||
* </div>
|
|
||||||
* → read the <video> src (already the original .mp4).
|
|
||||||
*
|
|
||||||
* 3. Videos:
|
|
||||||
* <div role="menuitem" class="video-player">
|
|
||||||
* <video src=".../original/....mp4" poster=".../small/....png"></video>
|
|
||||||
* </div>
|
|
||||||
* → read the <video> src and use the poster attribute for extraContentUrl.
|
|
||||||
*/
|
|
||||||
export default class Mastodon extends ScrapeEngineBase {
|
export default class Mastodon extends ScrapeEngineBase {
|
||||||
name = "mastodon";
|
name = "mastodon";
|
||||||
features: ScrapeEngineFeature[] = ["content", "source"];
|
features: ScrapeEngineFeature[] = ["content"];
|
||||||
notes = [
|
notes = [];
|
||||||
"Rating is assumed to be safe."
|
supportedHosts = [
|
||||||
|
"aethy.com",
|
||||||
|
"baraag.net",
|
||||||
|
"mastodon.online",
|
||||||
|
"mastodon.social",
|
||||||
|
"mastodon.world",
|
||||||
|
"mstdn.jp",
|
||||||
|
"mstdn.social",
|
||||||
|
"pawoo.net"
|
||||||
];
|
];
|
||||||
|
/**
|
||||||
supportedHosts = ["baraag.net"];
|
* Enabled for any other instances matching the pattern: .../@user/<postID>
|
||||||
|
*/
|
||||||
canImport(url: Location): boolean {
|
canImport(url: Location): boolean {
|
||||||
if (this.supportedHosts.indexOf(url.host.replace("www.", "")) !== -1) {
|
return this.supportedHosts.indexOf(url.host.replace("www.", "")) !== -1
|
||||||
return true;
|
|| /^\/@[^/]+\/\d+\/?$/.test(url.pathname);
|
||||||
}
|
|
||||||
// Generic Mastodon status URL shape: /@user/<numeric id>
|
|
||||||
return /^\/@[^/]+\/\d+\/?$/.test(url.pathname);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scrapeDocument(document: Document): ScrapeResult {
|
scrapeDocument(document: Document): ScrapeResult {
|
||||||
const result = new ScrapeResult(this.name);
|
const result = new ScrapeResult(this.name);
|
||||||
|
|
||||||
const attachments = this.findAttachments(document);
|
const attachments = this.findAttachments(document);
|
||||||
this.log(`Found ${attachments.length} attachment(s).`);
|
|
||||||
|
|
||||||
for (const att of attachments) {
|
for (const att of attachments) {
|
||||||
const post = new ScrapedPost();
|
const post = new ScrapedPost();
|
||||||
post.pageUrl = document.location.href;
|
post.pageUrl = document.location.href;
|
||||||
post.contentUrl = att.url;
|
post.contentUrl = att.url;
|
||||||
post.contentType = att.contentType;
|
post.contentType = att.contentType;
|
||||||
post.referrer = document.location.origin;
|
post.referrer = document.location.origin;
|
||||||
|
|
||||||
result.tryAddPost(post);
|
result.tryAddPost(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attachments.length === 0) {
|
|
||||||
this.log("No media attachments found on this page.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +53,13 @@ export default class Mastodon extends ScrapeEngineBase {
|
||||||
out.push(att);
|
out.push(att);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. Images: anchor href points at the original.
|
/**
|
||||||
|
* Images -> a[href]
|
||||||
|
*
|
||||||
|
* <a class="media-gallery__item-thumbnail" href=".../original/....png">
|
||||||
|
* <img src=".../small/....png">
|
||||||
|
* </a>
|
||||||
|
*/
|
||||||
let anchors = Array.from(
|
let anchors = Array.from(
|
||||||
document.querySelectorAll<HTMLAnchorElement>("a.media-gallery__item-thumbnail[href]")
|
document.querySelectorAll<HTMLAnchorElement>("a.media-gallery__item-thumbnail[href]")
|
||||||
);
|
);
|
||||||
|
|
@ -100,7 +74,7 @@ export default class Mastodon extends ScrapeEngineBase {
|
||||||
|
|
||||||
const img = a.querySelector("img");
|
const img = a.querySelector("img");
|
||||||
|
|
||||||
const url = this.normalizeUrl(rawHref) || this.largestFromSrcset(img);
|
const url = this.resolveUrl(rawHref) || this.largestFromSrcset(img);
|
||||||
if (!url) continue;
|
if (!url) continue;
|
||||||
|
|
||||||
add({
|
add({
|
||||||
|
|
@ -109,13 +83,23 @@ export default class Mastodon extends ScrapeEngineBase {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2 & 3. Videos and GIFVs: <video> elements carry the original .mp4 in src.
|
/**
|
||||||
|
* GIFs and Videos -> video[src]
|
||||||
|
*
|
||||||
|
* <div class="media-gallery__gifv autoplay">
|
||||||
|
* <video class="media-gallery__item-gifv-thumbnail" src=".../original/....mp4" autoplay loop></video>
|
||||||
|
* </div>
|
||||||
|
*
|
||||||
|
* <div role="menuitem" class="video-player">
|
||||||
|
* <video src=".../original/....mp4" poster=".../small/....png"></video>
|
||||||
|
* </div>
|
||||||
|
*/
|
||||||
const videos = Array.from(document.querySelectorAll<HTMLVideoElement>("video[src]"));
|
const videos = Array.from(document.querySelectorAll<HTMLVideoElement>("video[src]"));
|
||||||
for (const v of videos) {
|
for (const v of videos) {
|
||||||
const rawSrc = v.getAttribute("src");
|
const rawSrc = v.getAttribute("src") || v.currentSrc;
|
||||||
if (!rawSrc) continue;
|
if (!rawSrc) continue;
|
||||||
|
|
||||||
const url = this.normalizeUrl(rawSrc);
|
const url = this.resolveUrl(rawSrc);
|
||||||
if (!url) continue;
|
if (!url) continue;
|
||||||
|
|
||||||
add({
|
add({
|
||||||
|
|
@ -128,15 +112,11 @@ export default class Mastodon extends ScrapeEngineBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strip Mastodon's ?sensitive=true marker. The marker is metadata, not
|
* Resolve possible relative media URLs against current page.
|
||||||
* part of the media URL, and leaving it in can confuse booru deduplication.
|
|
||||||
* Returns an empty string if the URL is unusable.
|
|
||||||
*/
|
*/
|
||||||
private normalizeUrl(href: string): string {
|
private resolveUrl(href: string): string {
|
||||||
try {
|
try {
|
||||||
const u = new URL(href, document.location.href);
|
return new URL(href, document.location.href).toString();
|
||||||
u.searchParams.delete("sensitive");
|
|
||||||
return u.toString();
|
|
||||||
} catch {
|
} catch {
|
||||||
return href;
|
return href;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue