Add rule34.us engine

pull/9/head
neobooru 2021-09-12 23:45:05 +02:00
parent 51653d7d72
commit 352ba505e1
6 changed files with 91 additions and 12 deletions

View File

@ -1,12 +1,13 @@
{ {
"name": "neo-scraper", "name": "neo-scraper",
"version": "0.4.5", "version": "0.4.6",
"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",
"scripts": { "scripts": {
"test": "jest", "test": "jest",
"build": "tsc", "build": "tsc",
"watch": "tsc -w",
"prepare": "npm run build" "prepare": "npm run build"
}, },
"author": "neobooru", "author": "neobooru",

View File

@ -17,6 +17,7 @@ export default class NeoScraper {
new engines.Moebooru(), new engines.Moebooru(),
new engines.Pixiv(), new engines.Pixiv(),
new engines.Reddit(), new engines.Reddit(),
new engines.Rule34us(),
new engines.SankakuComplex(), new engines.SankakuComplex(),
new engines.Shimmie2(), new engines.Shimmie2(),
new engines.Shuushuu(), new engines.Shuushuu(),

3
src/engines/Common.ts Normal file
View File

@ -0,0 +1,3 @@
import { TagCategory } from "../BooruTypes";
export type CategoryMap = Record<string, TagCategory>;

View File

@ -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 { CategoryMap } from "./Common";
import { guessContentType } from "../Utility"; import { guessContentType } from "../Utility";
// Small hack to avoid screwing with semver // Small hack to avoid screwing with semver
@ -8,11 +9,16 @@ enum Version {
v025, // 0.2.5 v025, // 0.2.5
} }
type CategoryMap = Record<string, TagCategory>;
export default class Gelbooru implements ScrapeEngine { export default class Gelbooru implements ScrapeEngine {
name = "gelbooru"; name = "gelbooru";
private readonly classNameToCategoryMap: CategoryMap = {
"tag-type-copyright": "copyright",
"tag-type-character": "character",
"tag-type-artist": "artist",
"tag-type-metadata": "meta",
};
canImport(url: Location): boolean { canImport(url: Location): boolean {
return ( return (
url.host == "safebooru.org" || url.host == "gelbooru.com" || url.host == "rule34.xxx" || url.host == "tbib.org" url.host == "safebooru.org" || url.host == "gelbooru.com" || url.host == "rule34.xxx" || url.host == "tbib.org"
@ -135,17 +141,10 @@ export default class Gelbooru implements ScrapeEngine {
let category: TagCategory | undefined; let category: TagCategory | undefined;
const classNameToCategoryMap: CategoryMap = {
"tag-type-copyright": "copyright",
"tag-type-character": "character",
"tag-type-artist": "artist",
"tag-type-metadata": "meta",
};
// Loop over all classes, because some websites use multiple. E.g. `class="tag tag-type-copyright"` // Loop over all classes, because some websites use multiple. E.g. `class="tag tag-type-copyright"`
for (const className of el.classList) { for (const className of el.classList) {
if (classNameToCategoryMap[className]) { if (this.classNameToCategoryMap[className]) {
category = classNameToCategoryMap[className]; category = this.classNameToCategoryMap[className];
break; break;
} }
} }

74
src/engines/Rule34us.ts Normal file
View File

@ -0,0 +1,74 @@
// Technically speaking rule34.us also uses gelbooru, albeit a (heavily?) modified one.
// Because pretty much no scraper code can be shared with other gelbooru-likes it makes more sense to create a new engine for rule34.us.
import { ScrapeEngine, ScrapeResult, ScrapedPost, ScrapedTag } from "../ScrapeEngine";
import { TagCategory } from "../BooruTypes";
import { CategoryMap } from "./Common";
import { guessContentType } from "../Utility";
export default class Gelbooru implements ScrapeEngine {
name = "rule34us";
private readonly classNameToCategoryMap: CategoryMap = {
"character-tag": "character",
"artist-tag": "character",
"copyright-tag": "copyright",
"metadata-tag": "meta",
};
canImport(url: Location): boolean {
return url.host == "rule34.us";
}
scrapeDocument(document: Document): ScrapeResult {
let result = new ScrapeResult(this.name);
let post = new ScrapedPost();
post.pageUrl = document.location.href;
// Set image url
const origImgEls = Array.from(document.querySelectorAll("ul > a"))
.map((x) => x as HTMLAnchorElement)
.filter((x) => x.innerText == "Original");
if (origImgEls.length > 0) {
post.contentUrl = origImgEls[0].href;
} else {
return result;
}
// Set content type
post.contentType = guessContentType(post.contentUrl);
// Patch contentUrl if contentType == "video"
// This is because the "Original" button does link to "302 Found" for videos. Idk why.
// Shit's fucked, there are multiple workarounds for that and this is one of them.
if (post.contentType == "video") {
post.contentUrl = post.contentUrl.replace("img2.rule34", "video.rule34");
}
// This is where we would set the rating and source,
// but rule34.us doesn't display those as far as I can see.
post.rating = "unsafe"; // Lucky guess.
// Set tags
const possibleTagEls = Array.from(document.querySelectorAll(".container > ul > li")).map((x) => x as HTMLLIElement);
for (const el of possibleTagEls) {
// Idk why booru sites can't have consistent a HTML DOM.
// Not all elements inside `possibleTagEls` are actually tags,
// only elements that have a `a` tag as a direct child are valid tags.
// E.g. the 'Edit' button also has the `general-tag` class, but it certainly is not a tag.
const anchor = el.firstElementChild as HTMLAnchorElement | null;
if (anchor?.tagName == "A") {
const name = anchor.innerText;
const category = this.classNameToCategoryMap[el.className] as TagCategory | undefined;
post.tags.push(new ScrapedTag(name, category));
}
}
result.tryAddPost(post);
return result;
}
}

View File

@ -8,6 +8,7 @@ export { default as Inkbunny } from "./Inkbunny";
export { default as Moebooru } from "./Moebooru"; export { default as Moebooru } from "./Moebooru";
export { default as Pixiv } from "./Pixiv"; export { default as Pixiv } from "./Pixiv";
export { default as Reddit } from "./Reddit"; export { default as Reddit } from "./Reddit";
export { default as Rule34us } from "./Rule34us";
export { default as SankakuComplex } from "./SankakuComplex"; export { default as SankakuComplex } from "./SankakuComplex";
export { default as Shimmie2 } from "./Shimmie2"; export { default as Shimmie2 } from "./Shimmie2";
export { default as Shuushuu } from "./Shuushuu"; export { default as Shuushuu } from "./Shuushuu";