Add direct video import and a lot more.
- Add support for importing direct video urls - Add contentType property (image/video) - Add referrer property (for use with HTTP 'Referer' header)pull/5/head
parent
6f6ba711ce
commit
e1df85b44a
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "neo-scraper",
|
"name": "neo-scraper",
|
||||||
"version": "0.3.1",
|
"version": "0.4.0",
|
||||||
"description": "Scrape a booru-like post object from the DOM.",
|
"description": "Scrape a booru-like post object from the DOM.",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.js",
|
"types": "dist/index.d.js",
|
||||||
|
|
|
||||||
|
|
@ -2,132 +2,142 @@ import { JSDOM } from "jsdom";
|
||||||
import NeoScraper from "./NeoScraper";
|
import NeoScraper from "./NeoScraper";
|
||||||
|
|
||||||
async function scrapeUrl(url: string) {
|
async function scrapeUrl(url: string) {
|
||||||
const scraper = new NeoScraper();
|
const scraper = new NeoScraper();
|
||||||
const dom = await JSDOM.fromURL(url);
|
const dom = await JSDOM.fromURL(url);
|
||||||
return scraper.scrapeDocument(dom.window.document);
|
return scraper.scrapeDocument(dom.window.document);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
// Expose JSDOM Element constructor
|
// Expose JSDOM Element constructor
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
global.Element = (new JSDOM()).window.Element;
|
global.Element = new JSDOM().window.Element;
|
||||||
// 'Implement' innerText in JSDOM: https://github.com/jsdom/jsdom/issues/1245
|
// 'Implement' innerText in JSDOM: https://github.com/jsdom/jsdom/issues/1245
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
Object.defineProperty(global.Element.prototype, 'innerText', {
|
Object.defineProperty(global.Element.prototype, "innerText", {
|
||||||
get() {
|
get() {
|
||||||
return this.textContent.replace(/(\r\n|\n|\r|^\s+|\s+$)/gm, "");;
|
return this.textContent.replace(/(\r\n|\n|\r|^\s+|\s+$)/gm, "");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create scraper with default engines", async () => {
|
test("create scraper with default engines", async () => {
|
||||||
const scraper = new NeoScraper();
|
const scraper = new NeoScraper();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("all default scrapers have unique names", () => {
|
test("all default scrapers have unique names", () => {
|
||||||
const scraper = new NeoScraper();
|
const scraper = new NeoScraper();
|
||||||
let alreadySeen: string[] = [];
|
let alreadySeen: string[] = [];
|
||||||
|
|
||||||
for (const engine of scraper.engines) {
|
for (const engine of scraper.engines) {
|
||||||
if (alreadySeen.indexOf(engine.name) != -1) {
|
if (alreadySeen.indexOf(engine.name) != -1) {
|
||||||
fail(engine.name + " is not a unique name!");
|
fail(engine.name + " is not a unique name!");
|
||||||
} else {
|
} else {
|
||||||
alreadySeen.push(engine.name);
|
alreadySeen.push(engine.name);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Danbooru
|
// Danbooru
|
||||||
// All links are SFW unless stated otherwise
|
// All links are SFW unless stated otherwise
|
||||||
|
|
||||||
test("get danbooru.donmai.us post 3555872", async () => {
|
test("get danbooru.donmai.us post 3555872", async () => {
|
||||||
var pageUrl = "https://danbooru.donmai.us/posts/3555872";
|
var pageUrl = "https://danbooru.donmai.us/posts/3555872";
|
||||||
const res = await scrapeUrl(pageUrl);
|
const res = await scrapeUrl(pageUrl);
|
||||||
expect(res.posts.length).toBe(1);
|
expect(res.posts.length).toBe(1);
|
||||||
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
||||||
expect(res.posts[0].imageUrl).toBe("https://danbooru.donmai.us/data/__flint_girls_und_panzer_drawn_by_omachi_slabco__90332299216db81b8793084e4cb7c15d.jpg");
|
expect(res.posts[0].contentUrl).toBe(
|
||||||
|
"https://danbooru.donmai.us/data/__flint_girls_und_panzer_drawn_by_omachi_slabco__90332299216db81b8793084e4cb7c15d.jpg"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gelbooru
|
// Gelbooru
|
||||||
|
|
||||||
test("get safebooru.org post 2871532", async () => {
|
test("get safebooru.org post 2871532", async () => {
|
||||||
var pageUrl = "https://safebooru.org/index.php?page=post&s=view&id=2871532";
|
var pageUrl = "https://safebooru.org/index.php?page=post&s=view&id=2871532";
|
||||||
const res = await scrapeUrl(pageUrl);
|
const res = await scrapeUrl(pageUrl);
|
||||||
expect(res.posts.length).toBe(1);
|
expect(res.posts.length).toBe(1);
|
||||||
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
||||||
expect(res.posts[0].imageUrl).toBe("https://safebooru.org//images/2757/5f19c7744db918c3d4c4c6035bc65dfa9e18866f.jpg");
|
expect(res.posts[0].contentUrl).toBe(
|
||||||
|
"https://safebooru.org//images/2757/5f19c7744db918c3d4c4c6035bc65dfa9e18866f.jpg"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("get gelbooru.com post 4925055", async () => {
|
test("get gelbooru.com post 4925055", async () => {
|
||||||
var pageUrl = "https://gelbooru.com/index.php?page=post&s=view&id=4925055&tags=kimetsu_no_yaiba";
|
var pageUrl = "https://gelbooru.com/index.php?page=post&s=view&id=4925055&tags=kimetsu_no_yaiba";
|
||||||
const res = await scrapeUrl(pageUrl);
|
const res = await scrapeUrl(pageUrl);
|
||||||
expect(res.posts.length).toBe(1);
|
expect(res.posts.length).toBe(1);
|
||||||
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
||||||
expect(res.posts[0].imageUrl).toBe("https://img2.gelbooru.com/images/3d/03/3d03f6858637805d6473d390a4b1d8c8.jpg");
|
expect(res.posts[0].contentUrl).toBe("https://img2.gelbooru.com/images/3d/03/3d03f6858637805d6473d390a4b1d8c8.jpg");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("get rule34.xxx post 2740994", async () => {
|
test("get rule34.xxx post 2740994", async () => {
|
||||||
var pageUrl = "https://rule34.xxx/index.php?page=post&s=view&id=2740994";
|
var pageUrl = "https://rule34.xxx/index.php?page=post&s=view&id=2740994";
|
||||||
const res = await scrapeUrl(pageUrl);
|
const res = await scrapeUrl(pageUrl);
|
||||||
expect(res.posts.length).toBe(1);
|
expect(res.posts.length).toBe(1);
|
||||||
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
||||||
expect(res.posts[0].imageUrl).toContain(".rule34.xxx//images/2472/35bf31c98f53a369c047baf9bae496fb.jpeg");
|
expect(res.posts[0].contentUrl).toContain(".rule34.xxx//images/2472/35bf31c98f53a369c047baf9bae496fb.jpeg");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Moebooru
|
// Moebooru
|
||||||
|
|
||||||
test("get yande.re post 438510", async () => {
|
test("get yande.re post 438510", async () => {
|
||||||
var pageUrl = "https://yande.re/post/show/438510";
|
var pageUrl = "https://yande.re/post/show/438510";
|
||||||
const res = await scrapeUrl(pageUrl);
|
const res = await scrapeUrl(pageUrl);
|
||||||
expect(res.posts.length).toBe(1);
|
expect(res.posts.length).toBe(1);
|
||||||
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
||||||
expect(res.posts[0].imageUrl).toBe("https://files.yande.re/image/49b79bc372716a579d8525dcaccb56e2/yande.re%20438510%20landscape%20link%20nintendo%20princess_zelda%20sword%20the_legend_of_zelda%20the_legend_of_zelda%3A_breath_of_the_wild.jpg");
|
expect(res.posts[0].contentUrl).toBe(
|
||||||
|
"https://files.yande.re/image/49b79bc372716a579d8525dcaccb56e2/yande.re%20438510%20landscape%20link%20nintendo%20princess_zelda%20sword%20the_legend_of_zelda%20the_legend_of_zelda%3A_breath_of_the_wild.jpg"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("get konachan.com post 289968", async () => {
|
test("get konachan.com post 289968", async () => {
|
||||||
var pageUrl = "https://konachan.com/post/show/289968/ahri_-league_of_legends-akali-animal_ears-blonde_h";
|
var pageUrl = "https://konachan.com/post/show/289968/ahri_-league_of_legends-akali-animal_ears-blonde_h";
|
||||||
const res = await scrapeUrl(pageUrl);
|
const res = await scrapeUrl(pageUrl);
|
||||||
expect(res.posts.length).toBe(1);
|
expect(res.posts.length).toBe(1);
|
||||||
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
||||||
expect(res.posts[0].imageUrl).toBe("https://konachan.com/image/e40b38fb704434e4da3eae41e26bca39/Konachan.com%20-%20289968%20akali%20blue_eyes%20breasts%20choker%20cleavage%20evelynn%20foxgirl%20gloves%20group%20hat%20kai%27sa%20long_hair%20microphone%20ponytail%20saphirya%20short_hair%20signed%20watermark.jpg");
|
expect(res.posts[0].contentUrl).toBe(
|
||||||
|
"https://konachan.com/image/e40b38fb704434e4da3eae41e26bca39/Konachan.com%20-%20289968%20akali%20blue_eyes%20breasts%20choker%20cleavage%20evelynn%20foxgirl%20gloves%20group%20hat%20kai%27sa%20long_hair%20microphone%20ponytail%20saphirya%20short_hair%20signed%20watermark.jpg"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reddit
|
// Reddit
|
||||||
|
|
||||||
test("get old.reddit.com post", async () => {
|
test("get old.reddit.com post", async () => {
|
||||||
var pageUrl = "https://old.reddit.com/r/IceCreamWaifu/comments/ce0wgh/rtx_2018_neo_luluchan92/";
|
var pageUrl = "https://old.reddit.com/r/IceCreamWaifu/comments/ce0wgh/rtx_2018_neo_luluchan92/";
|
||||||
const res = await scrapeUrl(pageUrl);
|
const res = await scrapeUrl(pageUrl);
|
||||||
expect(res.posts.length).toBe(1);
|
expect(res.posts.length).toBe(1);
|
||||||
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
||||||
expect(res.posts[0].imageUrl).toBe("https://i.redd.it/ln8ahz17kpa31.jpg");
|
expect(res.posts[0].contentUrl).toBe("https://i.redd.it/ln8ahz17kpa31.jpg");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("get new.reddit.com post", async () => {
|
test("get new.reddit.com post", async () => {
|
||||||
var pageUrl = "https://new.reddit.com/r/IceCreamWaifu/comments/ce0wgh/rtx_2018_neo_luluchan92/";
|
var pageUrl = "https://new.reddit.com/r/IceCreamWaifu/comments/ce0wgh/rtx_2018_neo_luluchan92/";
|
||||||
const res = await scrapeUrl(pageUrl);
|
const res = await scrapeUrl(pageUrl);
|
||||||
expect(res.posts.length).toBe(1);
|
expect(res.posts.length).toBe(1);
|
||||||
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
||||||
expect(res.posts[0].imageUrl).toBe("https://i.redd.it/ln8ahz17kpa31.jpg");
|
expect(res.posts[0].contentUrl).toBe("https://i.redd.it/ln8ahz17kpa31.jpg");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Shimmie2
|
// Shimmie2
|
||||||
|
|
||||||
test("get rule34.paheal.net post 2505982", async () => {
|
test("get rule34.paheal.net post 2505982", async () => {
|
||||||
var pageUrl = "https://rule34.paheal.net/post/view/2505982"; // Somewhat sketchy
|
var pageUrl = "https://rule34.paheal.net/post/view/2505982"; // Somewhat sketchy
|
||||||
const res = await scrapeUrl(pageUrl);
|
const res = await scrapeUrl(pageUrl);
|
||||||
expect(res.posts.length).toBe(1);
|
expect(res.posts.length).toBe(1);
|
||||||
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
expect(res.posts[0].pageUrl).toBe(pageUrl);
|
||||||
expect(res.posts[0].imageUrl).toContain(".paheal.net/_images/15427ab5ed2cddbeef947e30f5c9cab6/2505982%20-%20Kai%27Sa%20League_of_Legends%20lawzilla.png");
|
expect(res.posts[0].contentUrl).toContain(
|
||||||
|
".paheal.net/_images/15427ab5ed2cddbeef947e30f5c9cab6/2505982%20-%20Kai%27Sa%20League_of_Legends%20lawzilla.png"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Twitter
|
// Twitter
|
||||||
|
|
||||||
test("get twitter.com post", async () => {
|
test("get twitter.com post", async () => {
|
||||||
pending("JSDOM can't parse this page");
|
pending("JSDOM can't parse this page");
|
||||||
// TODO: JSDOM sucks
|
// TODO: JSDOM sucks
|
||||||
// var pageUrl = "https://twitter.com/Dishwasher1910/status/1171162651461746688/photo/1";
|
// var pageUrl = "https://twitter.com/Dishwasher1910/status/1171162651461746688/photo/1";
|
||||||
// const res = await scrapeUrl(pageUrl);
|
// const res = await scrapeUrl(pageUrl);
|
||||||
// expect(res.posts.length).toBe(1);
|
// expect(res.posts.length).toBe(1);
|
||||||
// expect(res.posts[0].pageUrl).toBe(pageUrl);
|
// expect(res.posts[0].pageUrl).toBe(pageUrl);
|
||||||
// expect(res.posts[0].imageUrl).toBe("https://pbs.twimg.com/media/EEDOOCdU0AAHX00?format=jpg&name=orig");
|
// expect(res.posts[0].contentUrl).toBe("https://pbs.twimg.com/media/EEDOOCdU0AAHX00?format=jpg&name=orig");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,56 @@
|
||||||
import { ScrapeEngine, ScrapeResults, } from './ScrapeEngine';
|
import { ScrapeEngine, ScrapeResults } from "./ScrapeEngine";
|
||||||
import * as engines from "./engines";
|
import * as engines from "./engines";
|
||||||
|
|
||||||
export default class NeoScraper {
|
export default class NeoScraper {
|
||||||
engines: ScrapeEngine[] = [];
|
engines: ScrapeEngine[] = [];
|
||||||
fallbackEngine: ScrapeEngine | null = null;
|
fallbackEngine: ScrapeEngine | null = null;
|
||||||
|
|
||||||
constructor(useDefaultEngines: boolean = true) {
|
constructor(useDefaultEngines: boolean = true) {
|
||||||
if (useDefaultEngines) {
|
if (useDefaultEngines) {
|
||||||
this.engines.push(
|
this.engines.push(
|
||||||
new engines.AnimePictures(),
|
new engines.AnimePictures(),
|
||||||
new engines.Danbooru(),
|
new engines.Danbooru(),
|
||||||
new engines.e621(),
|
new engines.e621(),
|
||||||
new engines.Gelbooru(),
|
new engines.Gelbooru(),
|
||||||
new engines.Moebooru(),
|
new engines.Moebooru(),
|
||||||
new engines.Pixiv(),
|
new engines.Pixiv(),
|
||||||
new engines.Reddit(),
|
new engines.Reddit(),
|
||||||
new engines.SankakuComplex(),
|
new engines.SankakuComplex(),
|
||||||
new engines.Shimmie2(),
|
new engines.Shimmie2(),
|
||||||
new engines.Shuushuu(),
|
new engines.Shuushuu(),
|
||||||
new engines.Twitter(),
|
new engines.Twitter(),
|
||||||
new engines.Zerochan(),
|
new engines.Zerochan()
|
||||||
);
|
);
|
||||||
this.fallbackEngine = new engines.Fallback();
|
this.fallbackEngine = new engines.Fallback();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scrapeDocument(
|
||||||
|
document: Document,
|
||||||
|
engines: string[] | null = null,
|
||||||
|
allowFallbackEngine: boolean = false,
|
||||||
|
ignoreCanImport: boolean = false
|
||||||
|
): ScrapeResults {
|
||||||
|
const res = new ScrapeResults();
|
||||||
|
|
||||||
|
for (const engine of this.engines) {
|
||||||
|
if (ignoreCanImport || engine.canImport(document.location)) {
|
||||||
|
console.log("Using engine: " + engine.name);
|
||||||
|
res.results.push(engine.scrapeDocument(document));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scrapeDocument(document: Document, engines: string[] | null = null, allowFallbackEngine: boolean = false, ignoreCanImport: boolean = false): ScrapeResults {
|
if (allowFallbackEngine) {
|
||||||
const res = new ScrapeResults();
|
if (!this.fallbackEngine) {
|
||||||
|
console.error("NeoScraper.fallbackEngine is unset!");
|
||||||
for (const engine of this.engines) {
|
} else {
|
||||||
if (ignoreCanImport || engine.canImport(document.location)) {
|
console.log("Using fallback engine");
|
||||||
console.log("Using engine: " + engine.name);
|
res.results.push(this.fallbackEngine.scrapeDocument(document));
|
||||||
res.results.push(engine.scrapeDocument(document));
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allowFallbackEngine) {
|
|
||||||
if (!this.fallbackEngine) {
|
|
||||||
console.error("NeoScraper.fallbackEngine is unset!");
|
|
||||||
} else {
|
|
||||||
console.log("Using fallback engine");
|
|
||||||
res.results.push(this.fallbackEngine.scrapeDocument(document));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.dir(res);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.dir(res);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import { TagCategory, SafetyRating } from "./BooruTypes";
|
import { TagCategory, SafetyRating } from "./BooruTypes";
|
||||||
|
|
||||||
|
export type ContentType = "image" | "video";
|
||||||
|
|
||||||
export class ScrapedPost {
|
export class ScrapedPost {
|
||||||
imageUrl: string = "";
|
contentUrl: string = "";
|
||||||
pageUrl: string = "";
|
pageUrl: string = "";
|
||||||
|
contentType: ContentType = "image";
|
||||||
rating: SafetyRating = "safe";
|
rating: SafetyRating = "safe";
|
||||||
tags: ScrapedTag[] = [];
|
tags: ScrapedTag[] = [];
|
||||||
source: string | undefined = undefined;
|
source: string | undefined = undefined;
|
||||||
|
referrer: string | undefined = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ScrapedTag {
|
export class ScrapedTag {
|
||||||
|
|
@ -28,10 +32,8 @@ export class ScrapeResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
tryAddPost(post: ScrapedPost) {
|
tryAddPost(post: ScrapedPost) {
|
||||||
if (!post.imageUrl) {
|
if (!post.contentUrl) {
|
||||||
console.log(
|
console.log(`[${this.engine}] Not adding post because imageUrl is unset!`);
|
||||||
`[${this.engine}] Not adding post because imageUrl is unset!`
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.posts.push(post);
|
this.posts.push(post);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { guessContentType } from "./Utility";
|
||||||
|
|
||||||
|
test("guessContentType", () => {
|
||||||
|
expect(guessContentType("video.mp4")).toBe("video");
|
||||||
|
expect(guessContentType("https://example.com/video.mp4")).toBe("video");
|
||||||
|
expect(guessContentType("https://example.com/video.mp4?q=r")).toBe("video");
|
||||||
|
expect(guessContentType("https://example.com/video.mp4?q=r&rrr=fss")).toBe("video");
|
||||||
|
expect(guessContentType("https://example.com/video.mp4?q=r&rrr=fss,ef./s.e")).toBe("video");
|
||||||
|
expect(guessContentType("file://example.com/video.mp4?q=r&rrr=fss,ef./s.e")).toBe("video");
|
||||||
|
expect(guessContentType("example.com/video.mp4?q=r&rrr=fss,ef./s.e")).toBe("video");
|
||||||
|
expect(guessContentType(".com/video.mp4?q=r&rrr=fss,ef./s.e")).toBe("video");
|
||||||
|
expect(guessContentType("c:\\my\\video.mp4?q=r&rrr=fss,ef./s.e")).toBe("video");
|
||||||
|
expect(guessContentType("")).toBe("image");
|
||||||
|
expect(guessContentType("08435n gf g =0324")).toBe("image");
|
||||||
|
expect(guessContentType("https://example.com/.mp4/image.png")).toBe("image");
|
||||||
|
expect(guessContentType("https://example.com/file.mp4.png")).toBe("image");
|
||||||
|
expect(guessContentType("https://example.com/.mp4.png")).toBe("image");
|
||||||
|
expect(guessContentType(undefined as any)).toBe("image");
|
||||||
|
expect(guessContentType(null as any)).toBe("image");
|
||||||
|
expect(() => guessContentType(984 as any)).toThrow();
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { ContentType } from "./ScrapeEngine";
|
||||||
|
|
||||||
|
// https://github.com/sindresorhus/video-extensions/blob/main/video-extensions.json
|
||||||
|
export const videoExtensions = [
|
||||||
|
"3g2",
|
||||||
|
"3gp",
|
||||||
|
"aaf",
|
||||||
|
"asf",
|
||||||
|
"avchd",
|
||||||
|
"avi",
|
||||||
|
"drc",
|
||||||
|
"flv",
|
||||||
|
"m2v",
|
||||||
|
"m4p",
|
||||||
|
"m4v",
|
||||||
|
"mkv",
|
||||||
|
"mng",
|
||||||
|
"mov",
|
||||||
|
"mp2",
|
||||||
|
"mp4",
|
||||||
|
"mpe",
|
||||||
|
"mpeg",
|
||||||
|
"mpg",
|
||||||
|
"mpv",
|
||||||
|
"mxf",
|
||||||
|
"nsv",
|
||||||
|
"ogg",
|
||||||
|
"ogv",
|
||||||
|
"qt",
|
||||||
|
"rm",
|
||||||
|
"rmvb",
|
||||||
|
"roq",
|
||||||
|
"svi",
|
||||||
|
"vob",
|
||||||
|
"webm",
|
||||||
|
"wmv",
|
||||||
|
"yuv",
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getUrlExtension(url: string) {
|
||||||
|
return url.split(/[#?]/)[0].split(".").pop()!.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function guessContentType(url: string): ContentType {
|
||||||
|
if (url != undefined) {
|
||||||
|
const ext = getUrlExtension(url);
|
||||||
|
if (videoExtensions.indexOf(ext) != -1) return "video";
|
||||||
|
}
|
||||||
|
return "image";
|
||||||
|
}
|
||||||
|
|
@ -1,56 +1,63 @@
|
||||||
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
||||||
import { TagCategory } from "../BooruTypes";
|
import { TagCategory } from "../BooruTypes";
|
||||||
|
import { guessContentType } from "../Utility";
|
||||||
|
|
||||||
export default class AnimePictures implements ScrapeEngine {
|
export default class AnimePictures implements ScrapeEngine {
|
||||||
name = "animepictures";
|
name = "animepictures";
|
||||||
|
|
||||||
canImport(url: Location): boolean {
|
canImport(url: Location): boolean {
|
||||||
return url.host == "anime-pictures.net";
|
return url.host == "anime-pictures.net";
|
||||||
|
}
|
||||||
|
|
||||||
|
scrapeDocument(document: Document): ScrapeResult {
|
||||||
|
let result = new ScrapeResult(this.name);
|
||||||
|
let post = new ScrapedPost();
|
||||||
|
post.source = document.location.href;
|
||||||
|
|
||||||
|
// Set image url
|
||||||
|
const originalImageElements = Array.from(document.getElementsByClassName("download_icon"));
|
||||||
|
|
||||||
|
if (originalImageElements.length > 0) {
|
||||||
|
post.contentUrl = (originalImageElements[0] as HTMLAnchorElement).href;
|
||||||
|
} else {
|
||||||
|
// No point in continuing when we don't have an image.
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrapeDocument(document: Document): ScrapeResult {
|
// Set content type
|
||||||
let result = new ScrapeResult(this.name);
|
post.contentType = guessContentType(post.contentUrl);
|
||||||
let post = new ScrapedPost();
|
|
||||||
post.source = document.location.href;
|
|
||||||
|
|
||||||
// Set image url
|
// Set rating
|
||||||
const originalImageElements = Array.from(document.getElementsByClassName("download_icon"));
|
post.rating = "safe"; // Probably
|
||||||
|
|
||||||
if (originalImageElements.length > 0) {
|
// Set tags
|
||||||
post.imageUrl = (originalImageElements[0] as HTMLAnchorElement).href;
|
const tagElements = Array.from(document.querySelectorAll("ul.tags > li > a")).map((x) => x as HTMLAnchorElement);
|
||||||
|
for (const tagElement of tagElements) {
|
||||||
|
let tagName = tagElement.innerText;
|
||||||
|
|
||||||
|
let category: TagCategory | undefined;
|
||||||
|
for (const className of tagElement.classList) {
|
||||||
|
switch (className) {
|
||||||
|
case "copyright":
|
||||||
|
category = "copyright";
|
||||||
|
break;
|
||||||
|
case "character":
|
||||||
|
category = "character";
|
||||||
|
break;
|
||||||
|
case "artist":
|
||||||
|
category = "artist";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set rating
|
if (tagName) {
|
||||||
post.rating = "safe"; // Probably
|
let tag = new ScrapedTag(tagName, category);
|
||||||
|
post.tags.push(tag);
|
||||||
// Set tags
|
}
|
||||||
const tagElements = Array.from(document.querySelectorAll("ul.tags > li > a")).map(x => x as HTMLAnchorElement);
|
|
||||||
for (const tagElement of tagElements) {
|
|
||||||
let tagName = tagElement.innerText;
|
|
||||||
|
|
||||||
let category: TagCategory | undefined;
|
|
||||||
for (const className of tagElement.classList) {
|
|
||||||
switch (className) {
|
|
||||||
case "copyright":
|
|
||||||
category = "copyright";
|
|
||||||
break;
|
|
||||||
case "character":
|
|
||||||
category = "character";
|
|
||||||
break;
|
|
||||||
case "artist":
|
|
||||||
category = "artist";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tagName) {
|
|
||||||
let tag = new ScrapedTag(tagName, category);
|
|
||||||
post.tags.push(tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.tryAddPost(post);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.tryAddPost(post);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
||||||
import { TagCategory } from "../BooruTypes";
|
import { TagCategory } from "../BooruTypes";
|
||||||
|
import { guessContentType } from "../Utility";
|
||||||
|
|
||||||
export default class Danbooru implements ScrapeEngine {
|
export default class Danbooru implements ScrapeEngine {
|
||||||
name = "danbooru";
|
name = "danbooru";
|
||||||
|
|
@ -19,7 +20,17 @@ export default class Danbooru implements ScrapeEngine {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (originalImageElements.length > 0) {
|
if (originalImageElements.length > 0) {
|
||||||
post.imageUrl = (originalImageElements[0] as HTMLAnchorElement).href;
|
post.contentUrl = (originalImageElements[0] as HTMLAnchorElement).href;
|
||||||
|
} else {
|
||||||
|
// No point in continuing when we don't have an image.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set content type
|
||||||
|
if (document.querySelector(".image-container > video") != null) {
|
||||||
|
post.contentType = "video";
|
||||||
|
} else {
|
||||||
|
post.contentType = guessContentType(post.contentUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set rating
|
// Set rating
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,12 @@
|
||||||
import { ScrapeEngine, ScrapeResult, ScrapedPost } from "../ScrapeEngine";
|
import { ScrapeEngine, ScrapeResult, ScrapedPost, ContentType } from "../ScrapeEngine";
|
||||||
|
|
||||||
|
class MediaElement {
|
||||||
|
constructor(
|
||||||
|
public readonly contentType: ContentType,
|
||||||
|
public readonly contentUrl: string,
|
||||||
|
public readonly size: number
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
export default class Fallback implements ScrapeEngine {
|
export default class Fallback implements ScrapeEngine {
|
||||||
name = "fallback";
|
name = "fallback";
|
||||||
|
|
@ -8,21 +16,30 @@ export default class Fallback implements ScrapeEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
scrapeDocument(document: Document): ScrapeResult {
|
scrapeDocument(document: Document): ScrapeResult {
|
||||||
function getSize(el: HTMLImageElement) {
|
// Get all img elements.
|
||||||
return el.width * el.height;
|
const largestImgElement = Array.from(document.getElementsByTagName("img")).map(
|
||||||
}
|
(x) => new MediaElement("image", x.src, x.width * x.height)
|
||||||
|
);
|
||||||
|
|
||||||
// Get largest img element on this page
|
// Get all video elements that have at least one source (child node).
|
||||||
const largestImgElement = Array.from(document.getElementsByTagName("img")).reduce((a, b) =>
|
const largestVideoElement = Array.from(document.getElementsByTagName("video")).flatMap((x) => {
|
||||||
getSize(a) > getSize(b) ? a : b
|
const source = x.querySelector("source")?.src;
|
||||||
|
if (source != undefined) return new MediaElement("video", source, x.videoWidth * x.videoHeight);
|
||||||
|
return []; // Skip this item because it does not have a source.
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the largest media element.
|
||||||
|
const largestMediaElement = [...largestImgElement, ...largestVideoElement].reduce((a, b) =>
|
||||||
|
a?.size > b?.size ? a : b
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = new ScrapeResult(this.name);
|
let result = new ScrapeResult(this.name);
|
||||||
|
|
||||||
if (largestImgElement) {
|
if (largestMediaElement) {
|
||||||
let post = new ScrapedPost();
|
let post = new ScrapedPost();
|
||||||
post.pageUrl = document.location.href;
|
post.pageUrl = document.location.href;
|
||||||
post.imageUrl = largestImgElement.src;
|
post.contentUrl = largestMediaElement.contentUrl;
|
||||||
|
post.contentType = largestMediaElement.contentType;
|
||||||
result.posts.push(post);
|
result.posts.push(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
||||||
import { TagCategory } from "../BooruTypes";
|
import { TagCategory } from "../BooruTypes";
|
||||||
|
import { guessContentType } from "../Utility";
|
||||||
|
|
||||||
// Small hack to avoid screwing with semver
|
// Small hack to avoid screwing with semver
|
||||||
enum Version {
|
enum Version {
|
||||||
|
|
@ -44,7 +45,18 @@ export default class Gelbooru implements ScrapeEngine {
|
||||||
Array.from(document.querySelectorAll("li > a"))
|
Array.from(document.querySelectorAll("li > a"))
|
||||||
.map((x) => x as HTMLAnchorElement)
|
.map((x) => x as HTMLAnchorElement)
|
||||||
.filter((x) => x && x.innerText == "Original image")
|
.filter((x) => x && x.innerText == "Original image")
|
||||||
.map((x) => (post.imageUrl = x.href));
|
.map((x) => (post.contentUrl = x.href));
|
||||||
|
|
||||||
|
if (post.contentUrl == undefined) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set content type
|
||||||
|
if (document.getElementById("gelcomVideoPlayer") != null) {
|
||||||
|
post.contentType = "video";
|
||||||
|
} else {
|
||||||
|
post.contentType = guessContentType(post.contentUrl);
|
||||||
|
}
|
||||||
|
|
||||||
// Set rating and source
|
// Set rating and source
|
||||||
const regex = new RegExp("(.*): (.*)");
|
const regex = new RegExp("(.*): (.*)");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
||||||
import { TagCategory } from "../BooruTypes";
|
import { TagCategory } from "../BooruTypes";
|
||||||
|
import { guessContentType } from "../Utility";
|
||||||
|
|
||||||
export default class Moebooru implements ScrapeEngine {
|
export default class Moebooru implements ScrapeEngine {
|
||||||
name = "moebooru";
|
name = "moebooru";
|
||||||
|
|
@ -16,9 +17,15 @@ export default class Moebooru implements ScrapeEngine {
|
||||||
// Set image url
|
// Set image url
|
||||||
const originalImageElement = document.querySelector("#highres") as HTMLAnchorElement;
|
const originalImageElement = document.querySelector("#highres") as HTMLAnchorElement;
|
||||||
if (originalImageElement) {
|
if (originalImageElement) {
|
||||||
post.imageUrl = originalImageElement.href;
|
post.contentUrl = originalImageElement.href;
|
||||||
|
} else {
|
||||||
|
// No point in continuing when we don't have an image.
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set content type
|
||||||
|
post.contentType = guessContentType(post.contentUrl);
|
||||||
|
|
||||||
// Set rating and source
|
// Set rating and source
|
||||||
// Same method as Gelbooru v025.
|
// Same method as Gelbooru v025.
|
||||||
const regex = new RegExp("(.*): (.*)");
|
const regex = new RegExp("(.*): (.*)");
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,30 @@
|
||||||
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
||||||
import { TagCategory } from "../BooruTypes";
|
import { TagCategory } from "../BooruTypes";
|
||||||
|
import { guessContentType } from "../Utility";
|
||||||
|
|
||||||
export default class Pixiv implements ScrapeEngine {
|
export default class Pixiv implements ScrapeEngine {
|
||||||
name = "pixiv";
|
name = "pixiv";
|
||||||
|
|
||||||
canImport(url: Location): boolean {
|
canImport(url: Location): boolean {
|
||||||
return (
|
return url.host == "pixiv.net" || url.host == "www.pixiv.net";
|
||||||
url.host == "pixiv.net" ||
|
}
|
||||||
url.host == "www.pixiv.net"
|
|
||||||
);
|
scrapeDocument(document: Document): ScrapeResult {
|
||||||
|
let result = new ScrapeResult(this.name);
|
||||||
|
|
||||||
|
const imgLinks = Array.from(document.querySelectorAll("div[role='presentation'] > a")).map(
|
||||||
|
(x) => x as HTMLAnchorElement
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const imgLink of imgLinks) {
|
||||||
|
let post = new ScrapedPost();
|
||||||
|
post.pageUrl = document.location.href;
|
||||||
|
post.contentUrl = imgLink.href;
|
||||||
|
post.contentType = guessContentType(post.contentUrl);
|
||||||
|
post.referrer = document.location.origin;
|
||||||
|
result.tryAddPost(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
scrapeDocument(document: Document): ScrapeResult {
|
return result;
|
||||||
let result = new ScrapeResult(this.name);
|
}
|
||||||
|
|
||||||
const imgLinks = Array.from(document.querySelectorAll("div[role='presentation'] > a")).map(x => x as HTMLAnchorElement);
|
|
||||||
|
|
||||||
for (const imgLink of imgLinks) {
|
|
||||||
let post = new ScrapedPost();
|
|
||||||
post.pageUrl = document.location.href;
|
|
||||||
post.imageUrl = imgLink.href;
|
|
||||||
result.tryAddPost(post);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,37 @@
|
||||||
import { ScrapeEngine, ScrapeResult, ScrapedPost } from "../ScrapeEngine";
|
import { ScrapeEngine, ScrapeResult, ScrapedPost } from "../ScrapeEngine";
|
||||||
|
import { guessContentType } from "../Utility";
|
||||||
|
|
||||||
export default class Reddit implements ScrapeEngine {
|
export default class Reddit implements ScrapeEngine {
|
||||||
name = "reddit";
|
name = "reddit";
|
||||||
|
|
||||||
canImport(url: Location): boolean {
|
canImport(url: Location): boolean {
|
||||||
return url.host.endsWith("reddit.com");
|
return url.host.endsWith("reddit.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
scrapeDocument(document: Document): ScrapeResult {
|
||||||
|
let result = new ScrapeResult(this.name);
|
||||||
|
let post = new ScrapedPost();
|
||||||
|
post.pageUrl = document.location.href;
|
||||||
|
|
||||||
|
// Set image url
|
||||||
|
// For old.reddit.com
|
||||||
|
const oldLinkElements = document.querySelectorAll("a.title");
|
||||||
|
if (oldLinkElements.length > 0) {
|
||||||
|
post.contentUrl = (oldLinkElements[0] as HTMLAnchorElement).href;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrapeDocument(document: Document): ScrapeResult {
|
// For new reddit
|
||||||
let result = new ScrapeResult(this.name);
|
const newLinkElements = document.querySelectorAll("div[data-test-id='post-content'] > div > a");
|
||||||
let post = new ScrapedPost();
|
if (newLinkElements.length > 0) {
|
||||||
post.pageUrl = document.location.href;
|
post.contentUrl = (newLinkElements[0] as HTMLAnchorElement).href;
|
||||||
|
|
||||||
// Set image url
|
|
||||||
// For old.reddit.com
|
|
||||||
const oldLinkElements = document.querySelectorAll("a.title");
|
|
||||||
if (oldLinkElements.length > 0) {
|
|
||||||
post.imageUrl = (oldLinkElements[0] as HTMLAnchorElement).href;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For new reddit
|
|
||||||
const newLinkElements = document.querySelectorAll("div[data-test-id='post-content'] > div > a");
|
|
||||||
if (newLinkElements.length > 0) {
|
|
||||||
post.imageUrl = (newLinkElements[0] as HTMLAnchorElement).href;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.tryAddPost(post);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (post.contentUrl != undefined) {
|
||||||
|
post.contentType = guessContentType(post.contentUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.tryAddPost(post);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
||||||
import { TagCategory } from "../BooruTypes";
|
import { TagCategory } from "../BooruTypes";
|
||||||
|
import { guessContentType } from "../Utility";
|
||||||
|
|
||||||
export default class SankakuComplex implements ScrapeEngine {
|
export default class SankakuComplex implements ScrapeEngine {
|
||||||
name = "sankakucomplex";
|
name = "sankakucomplex";
|
||||||
|
|
@ -17,9 +18,15 @@ export default class SankakuComplex implements ScrapeEngine {
|
||||||
const originalImageElement = document.getElementById("highres") as HTMLAnchorElement;
|
const originalImageElement = document.getElementById("highres") as HTMLAnchorElement;
|
||||||
|
|
||||||
if (originalImageElement) {
|
if (originalImageElement) {
|
||||||
post.imageUrl = originalImageElement.href;
|
post.contentUrl = originalImageElement.href;
|
||||||
|
} else {
|
||||||
|
// No point in continuing when we don't have an image.
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set content type
|
||||||
|
post.contentType = guessContentType(post.contentUrl);
|
||||||
|
|
||||||
// Set rating
|
// Set rating
|
||||||
const safetyExp = new RegExp("Rating: (.*)");
|
const safetyExp = new RegExp("Rating: (.*)");
|
||||||
const safetyElements = Array.from(document.querySelectorAll("#stats > ul > li"))
|
const safetyElements = Array.from(document.querySelectorAll("#stats > ul > li"))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
||||||
|
import { guessContentType } from "../Utility";
|
||||||
|
|
||||||
export default class Shimmie2 implements ScrapeEngine {
|
export default class Shimmie2 implements ScrapeEngine {
|
||||||
name = "shimmie2";
|
name = "shimmie2";
|
||||||
|
|
@ -18,14 +19,21 @@ export default class Shimmie2 implements ScrapeEngine {
|
||||||
.filter((x) => x.innerText == "Image Only" || x.innerText == "File Only");
|
.filter((x) => x.innerText == "Image Only" || x.innerText == "File Only");
|
||||||
|
|
||||||
if (originalImageElements.length > 0) {
|
if (originalImageElements.length > 0) {
|
||||||
post.imageUrl = originalImageElements[0].href;
|
post.contentUrl = originalImageElements[0].href;
|
||||||
} else {
|
} else {
|
||||||
const dowloadBtnEl = document.querySelector("a[download]") as HTMLAnchorElement;
|
const dowloadBtnEl = document.querySelector("a[download]") as HTMLAnchorElement;
|
||||||
if (dowloadBtnEl) {
|
if (dowloadBtnEl) {
|
||||||
post.imageUrl = dowloadBtnEl.href;
|
post.contentUrl = dowloadBtnEl.href;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (post.contentUrl == undefined) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set content type
|
||||||
|
post.contentType = guessContentType(post.contentUrl);
|
||||||
|
|
||||||
// Set safety
|
// Set safety
|
||||||
post.rating = "unsafe"; // Usually it is unsafe
|
post.rating = "unsafe"; // Usually it is unsafe
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,74 @@
|
||||||
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
||||||
import { TagCategory } from "../BooruTypes";
|
import { TagCategory } from "../BooruTypes";
|
||||||
|
import { guessContentType } from "../Utility";
|
||||||
|
|
||||||
export default class Shuushuu implements ScrapeEngine {
|
export default class Shuushuu implements ScrapeEngine {
|
||||||
name = "shuushuu";
|
name = "shuushuu";
|
||||||
|
|
||||||
canImport(url: Location): boolean {
|
canImport(url: Location): boolean {
|
||||||
return url.host == "e-shuushuu.net";
|
return url.host == "e-shuushuu.net";
|
||||||
|
}
|
||||||
|
|
||||||
|
scrapeDocument(document: Document): ScrapeResult {
|
||||||
|
let result = new ScrapeResult(this.name);
|
||||||
|
let post = new ScrapedPost();
|
||||||
|
post.pageUrl = document.location.href;
|
||||||
|
|
||||||
|
// Set image url
|
||||||
|
const imgElement = document.querySelector(".thumb_image") as HTMLAnchorElement;
|
||||||
|
if (imgElement) {
|
||||||
|
post.contentUrl = imgElement.href;
|
||||||
|
} else {
|
||||||
|
// No point in continuing when we don't have an image.
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrapeDocument(document: Document): ScrapeResult {
|
// Set content type
|
||||||
let result = new ScrapeResult(this.name);
|
post.contentType = guessContentType(post.contentUrl);
|
||||||
let post = new ScrapedPost();
|
|
||||||
post.pageUrl = document.location.href;
|
|
||||||
|
|
||||||
// Set image url
|
// Set rating
|
||||||
const imgElement = document.querySelector(".thumb_image") as HTMLAnchorElement;
|
post.rating = "safe";
|
||||||
if (imgElement) {
|
|
||||||
post.imageUrl = imgElement.href;
|
// Set tags
|
||||||
|
const exp = new RegExp(/(.*)_(\w+)/);
|
||||||
|
const tagElements = document.getElementsByClassName("quicktag");
|
||||||
|
|
||||||
|
for (const tagElement of tagElements) {
|
||||||
|
// id = quicktag1_1009628
|
||||||
|
// match[0] = quicktag1_1009628 (aka input str)
|
||||||
|
// match[1] = quicktag1
|
||||||
|
// match[2] = 1009628
|
||||||
|
const match = exp.exec(tagElement.id);
|
||||||
|
if (!match || match.length != 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tagCategory: TagCategory | undefined;
|
||||||
|
switch (match[1]) {
|
||||||
|
// Source, aka copyright
|
||||||
|
case "quicktag2":
|
||||||
|
tagCategory = "copyright";
|
||||||
|
break;
|
||||||
|
// Artist
|
||||||
|
case "quicktag3":
|
||||||
|
tagCategory = "artist";
|
||||||
|
break;
|
||||||
|
// Characters
|
||||||
|
case "quicktag4":
|
||||||
|
tagCategory = "character";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const el of tagElement.children) {
|
||||||
|
const tagName = (el?.firstElementChild as HTMLAnchorElement)?.innerText;
|
||||||
|
if (tagName) {
|
||||||
|
post.tags.push(new ScrapedTag(tagName, tagCategory));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Set rating
|
|
||||||
post.rating = "safe";
|
|
||||||
|
|
||||||
// Set tags
|
|
||||||
const exp = new RegExp(/(.*)_(\w+)/);
|
|
||||||
const tagElements = document.getElementsByClassName("quicktag");
|
|
||||||
|
|
||||||
for (const tagElement of tagElements) {
|
|
||||||
// id = quicktag1_1009628
|
|
||||||
// match[0] = quicktag1_1009628 (aka input str)
|
|
||||||
// match[1] = quicktag1
|
|
||||||
// match[2] = 1009628
|
|
||||||
const match = exp.exec(tagElement.id);
|
|
||||||
if (!match || match.length != 3) { continue; }
|
|
||||||
|
|
||||||
let tagCategory: TagCategory | undefined;
|
|
||||||
switch (match[1]) {
|
|
||||||
// Source, aka copyright
|
|
||||||
case "quicktag2":
|
|
||||||
tagCategory = "copyright";
|
|
||||||
break;
|
|
||||||
// Artist
|
|
||||||
case "quicktag3":
|
|
||||||
tagCategory = "artist";
|
|
||||||
break;
|
|
||||||
// Characters
|
|
||||||
case "quicktag4":
|
|
||||||
tagCategory = "character";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const el of tagElement.children) {
|
|
||||||
const tagName = (el?.firstElementChild as HTMLAnchorElement)?.innerText;
|
|
||||||
if (tagName) {
|
|
||||||
post.tags.push(new ScrapedTag(tagName, tagCategory));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.tryAddPost(post);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.tryAddPost(post);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,48 @@
|
||||||
import { ScrapeEngine, ScrapeResult, ScrapedPost } from "../ScrapeEngine";
|
import { ScrapeEngine, ScrapeResult, ScrapedPost } from "../ScrapeEngine";
|
||||||
|
import { guessContentType } from "../Utility";
|
||||||
|
|
||||||
export function getOriginalImageUrl(imageUrl: string) {
|
export function getOriginalImageUrl(imageUrl: string) {
|
||||||
const x = imageUrl.replace(/:large|:med|:small|:orig$/, ""); // Remove `:large`, `:small` etc from url
|
const x = imageUrl.replace(/:large|:med|:small|:orig$/, ""); // Remove `:large`, `:small` etc from url
|
||||||
return x + ":orig"; // Add :orig to url (to get original image)
|
return x + ":orig"; // Add :orig to url (to get original image)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Twitter implements ScrapeEngine {
|
export default class Twitter implements ScrapeEngine {
|
||||||
name = "twitter";
|
name = "twitter";
|
||||||
|
|
||||||
canImport(url: Location): boolean {
|
canImport(url: Location): boolean {
|
||||||
return url.host == "twitter.com";
|
return url.host == "twitter.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
scrapeDocument(document: Document): ScrapeResult {
|
||||||
|
let result = new ScrapeResult(this.name);
|
||||||
|
let post = new ScrapedPost();
|
||||||
|
post.pageUrl = document.location.href;
|
||||||
|
|
||||||
|
// Set image url
|
||||||
|
// When clicked on post (not fullscreen, with post text etc)
|
||||||
|
const overlayElement = document.querySelector("#permalink-overlay-dialog");
|
||||||
|
if (overlayElement) {
|
||||||
|
const cardImageElement = overlayElement.querySelector("div[data-element-context='platform_photo_card'] > img");
|
||||||
|
if (cardImageElement) {
|
||||||
|
post.contentUrl = getOriginalImageUrl((cardImageElement as HTMLImageElement).src);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scrapeDocument(document: Document): ScrapeResult {
|
// When clicked on image (fullscreen)
|
||||||
let result = new ScrapeResult(this.name);
|
const mediaImageElements = document.querySelectorAll("div.Gallery-media > img.media-image");
|
||||||
let post = new ScrapedPost();
|
if (mediaImageElements.length > 0) {
|
||||||
post.pageUrl = document.location.href;
|
post.contentUrl = getOriginalImageUrl((mediaImageElements[0] as HTMLImageElement).src);
|
||||||
|
|
||||||
// Set image url
|
|
||||||
// When clicked on post (not fullscreen, with post text etc)
|
|
||||||
const overlayElement = document.querySelector("#permalink-overlay-dialog");
|
|
||||||
if (overlayElement) {
|
|
||||||
const cardImageElement = overlayElement.querySelector("div[data-element-context='platform_photo_card'] > img");
|
|
||||||
if (cardImageElement) {
|
|
||||||
post.imageUrl = getOriginalImageUrl((cardImageElement as HTMLImageElement).src);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When clicked on image (fullscreen)
|
|
||||||
const mediaImageElements = document.querySelectorAll("div.Gallery-media > img.media-image");
|
|
||||||
if (mediaImageElements.length > 0) {
|
|
||||||
post.imageUrl = getOriginalImageUrl((mediaImageElements[0] as HTMLImageElement).src);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.tryAddPost(post);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (post.contentUrl == undefined) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set content type
|
||||||
|
post.contentType = guessContentType(post.contentUrl);
|
||||||
|
|
||||||
|
result.tryAddPost(post);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
|
||||||
import { TagCategory } from "../BooruTypes";
|
import { TagCategory } from "../BooruTypes";
|
||||||
|
import { guessContentType } from "../Utility";
|
||||||
|
|
||||||
export default class Zerochan implements ScrapeEngine {
|
export default class Zerochan implements ScrapeEngine {
|
||||||
name = "zerochan";
|
name = "zerochan";
|
||||||
|
|
@ -17,15 +18,23 @@ export default class Zerochan implements ScrapeEngine {
|
||||||
const previewElements = document.getElementsByClassName("preview");
|
const previewElements = document.getElementsByClassName("preview");
|
||||||
if (previewElements.length > 0) {
|
if (previewElements.length > 0) {
|
||||||
// The preview element only exists when there is a larger size available. Preview's img element contains a downscaled version.
|
// The preview element only exists when there is a larger size available. Preview's img element contains a downscaled version.
|
||||||
post.imageUrl = (previewElements[0] as HTMLAnchorElement).href;
|
post.contentUrl = (previewElements[0] as HTMLAnchorElement).href;
|
||||||
} else {
|
} else {
|
||||||
// When there is no larger version available just use the displayed version.
|
// When there is no larger version available just use the displayed version.
|
||||||
const imgElement = document.querySelector("img[title='No larger size available']") as HTMLImageElement;
|
const imgElement = document.querySelector("img[title='No larger size available']") as HTMLImageElement;
|
||||||
if (imgElement) {
|
if (imgElement) {
|
||||||
post.imageUrl = imgElement.src;
|
post.contentUrl = imgElement.src;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (post.contentUrl == undefined) {
|
||||||
|
// No point in continuing when we don't have an image.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set content type
|
||||||
|
post.contentType = guessContentType(post.contentUrl);
|
||||||
|
|
||||||
// Set rating
|
// Set rating
|
||||||
post.rating = "safe";
|
post.rating = "safe";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,16 @@ export default class e621 implements ScrapeEngine {
|
||||||
|
|
||||||
// Set image url
|
// Set image url
|
||||||
const downloadEl = document.querySelector("#image-download-link > a") as HTMLAnchorElement;
|
const downloadEl = document.querySelector("#image-download-link > a") as HTMLAnchorElement;
|
||||||
post.imageUrl = downloadEl?.href;
|
post.contentUrl = downloadEl?.href;
|
||||||
|
|
||||||
|
if (post.contentUrl == undefined) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set content type
|
||||||
|
if (document.querySelector("video#image") != null) {
|
||||||
|
post.contentType = "video";
|
||||||
|
}
|
||||||
|
|
||||||
// Set rating
|
// Set rating
|
||||||
const ratingEl = document.getElementById("post-rating-text") as HTMLSpanElement;
|
const ratingEl = document.getElementById("post-rating-text") as HTMLSpanElement;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": [
|
"lib": [
|
||||||
"es6",
|
"es2019",
|
||||||
"dom",
|
"dom",
|
||||||
"dom.iterable"
|
"dom.iterable"
|
||||||
],
|
],
|
||||||
|
|
@ -19,4 +19,4 @@
|
||||||
"include": [
|
"include": [
|
||||||
"src/index.ts"
|
"src/index.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue