Initial commit
このコミットが含まれているのは:
コミット
a1a8f85d9e
|
@ -0,0 +1 @@
|
|||
CF=cf_clearance=XZoRJIDGsHrMCuS3rFIH9jCLhoYmBHzUNs6dfVJwNqg-1654563641-0-150
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"env": {
|
||||
"es2021": true,
|
||||
"node": true,
|
||||
"commonjs": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"linebreak-style": 0,
|
||||
"quotes": [
|
||||
"error",
|
||||
"double"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-empty": "error",
|
||||
"no-func-assign": "error",
|
||||
"no-case-declarations": "off",
|
||||
"no-unreachable": "error",
|
||||
"no-eval": "error",
|
||||
"no-global-assign": "error",
|
||||
"@typescript-eslint/no-explicit-any": ["off"],
|
||||
"indent": [
|
||||
"error",
|
||||
2
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
/node_modules
|
||||
yarn.lock
|
||||
/build
|
||||
/playground
|
||||
p.ts
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 sinkaroid.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "jandapress-cookie",
|
||||
"version": "0.0.1",
|
||||
"description": "Experimental doujin API with gather in mind",
|
||||
"main": "build/src/index.js",
|
||||
"scripts": {
|
||||
"build": "rm -rf build && tsc",
|
||||
"start": "node build/src/index.js",
|
||||
"test": "ts-node test/test.ts",
|
||||
"test:cf": "ts-node test/nhentaiCookietest.ts",
|
||||
"start:prod": "npm run build && node build/src/index.js",
|
||||
"start:dev": "nodemon src/index.ts",
|
||||
"lint": "npx eslint . --ext .ts",
|
||||
"lint:fix": "npx eslint . --fix",
|
||||
"postinstall": "npm run build"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "sinkaroid",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cheerio": "^1.0.0-rc.11",
|
||||
"express": "^4.18.1",
|
||||
"express-rate-limit": "^6.4.0",
|
||||
"express-slow-down": "^1.4.0",
|
||||
"http-cookie-agent": "^4.0.1",
|
||||
"phin": "^3.6.1",
|
||||
"pino": "^7.11.0",
|
||||
"pino-pretty": "^8.0.0",
|
||||
"tough-cookie": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express-rate-limit": "^6.0.0",
|
||||
"@types/express-slow-down": "^1.3.2",
|
||||
"@types/node": "^14.14.37",
|
||||
"@types/tough-cookie": "^4.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||
"@typescript-eslint/parser": "^5.18.0",
|
||||
"dotenv": "^16.0.1",
|
||||
"eslint": "^7.32.0",
|
||||
"nhentai-api": "^3.4.3",
|
||||
"nodemon": "^2.0.15",
|
||||
"npx": "^10.2.2",
|
||||
"ts-node": "^10.8.1",
|
||||
"typescript": "4.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.9.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { scrapeContent } from "../../scraper/hentai2read/hentai2readGetController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
export async function getHentai2read(req: any, res: any) {
|
||||
try {
|
||||
const book = req.query.book || "";
|
||||
if (!book) throw Error("Parameter book is required");
|
||||
if (book.split("/").length !== 2) throw Error("Book must be in format 'book_example/chapter'. Example: 'fate_lewd_summoning/1'");
|
||||
const url = `${c.HENTAI2READ}/${book}/`;
|
||||
const data = await scrapeContent(url);
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
return res.json(data);
|
||||
} catch (err: any) {
|
||||
const example = {
|
||||
"success": false,
|
||||
"message": err.message
|
||||
};
|
||||
res.json(example);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { scrapeContent } from "../../scraper/hentai2read/hentai2readSearchController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
export async function searchHentai2read(req: any, res: any, next: any) {
|
||||
try {
|
||||
const key = req.query.key || "";
|
||||
if (!key) throw Error("Parameter book is required");
|
||||
const url = `${c.HENTAI2READ}/hentai-list/search/${key}`;
|
||||
const data = await scrapeContent(url);
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
return res.json(data);
|
||||
} catch (err: any) {
|
||||
next(Error(err.message));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { scrapeContent } from "../../scraper/hentaifox/hentaifoxGetController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
export async function getHentaifox(req: any, res: any, next: any) {
|
||||
try {
|
||||
const book = req.query.book || "";
|
||||
if (!book) throw Error("Parameter book is required");
|
||||
if (isNaN(book)) throw Error("Value must be number");
|
||||
const url = `${c.HENTAIFOX}/gallery/${book}/`;
|
||||
const data = await scrapeContent(url);
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
return res.json(data);
|
||||
} catch (err: any) {
|
||||
next(Error(err.message));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { scrapeContent } from "../../scraper/hentaifox/hentaifoxSearchController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
const sorting = ["latest", "popular"];
|
||||
|
||||
export async function searchHentaifox(req: any, res:any, next: any) {
|
||||
try {
|
||||
const key = req.query.key || "";
|
||||
const page = req.query.page || 1;
|
||||
const sort = req.query.sort || sorting[0];
|
||||
if (!key) throw Error("Parameter key is required");
|
||||
if (!sorting.includes(sort)) throw Error("Invalid short: " + sorting.join(", "));
|
||||
const url = `${c.HENTAIFOX}/search/?q=${key}&sort=${sort}&page=${page}`;
|
||||
const data = await scrapeContent(url);
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
return res.json(data);
|
||||
} catch (err: any) {
|
||||
next(Error(err.message));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { scrapeContent } from "../../scraper/nhentai/nhentaiGetController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
//import { mock } from "../../utils/modifier";
|
||||
|
||||
export async function getNhentai(req: any, res: any) {
|
||||
try {
|
||||
const book = req.query.book || "";
|
||||
if (!book) throw Error("Parameter book is required");
|
||||
if (isNaN(book)) throw Error("Value must be number");
|
||||
|
||||
const url = `${c.NHENTAI}/api/gallery/${book}`;
|
||||
const data = await scrapeContent(url);
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
return res.json(data);
|
||||
} catch (err: any) {
|
||||
const e = {
|
||||
"success": false,
|
||||
"message": err.message
|
||||
};
|
||||
res.json(e);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { scrapeContent } from "../../scraper/nhentai/nhentaiRelatedController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { mock } from "../../utils/modifier";
|
||||
|
||||
export async function relatedNhentai(req: any, res: any) {
|
||||
try {
|
||||
const book = req.query.book || "";
|
||||
if (!book) throw Error("Parameter book is required");
|
||||
if (isNaN(book)) throw Error("Value must be number");
|
||||
|
||||
let actualAPI;
|
||||
if (!await mock(c.NHENTAI)) actualAPI = c.NHENTAI_IP_2;
|
||||
|
||||
const url = `${actualAPI}/api/gallery/${book}/related`;
|
||||
const data = await scrapeContent(url);
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
return res.json(data);
|
||||
} catch (err: any) {
|
||||
const e = {
|
||||
"success": false,
|
||||
"message": err.message
|
||||
};
|
||||
res.json(e);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import { scrapeContent } from "../../scraper/nhentai/nhentaiSearchController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { mock } from "../../utils/modifier";
|
||||
const sorting = ["popular-today", "popular-week", "popular"];
|
||||
|
||||
export async function searchNhentai(req: any, res: any) {
|
||||
try {
|
||||
const key = req.query.key || "";
|
||||
const page = req.query.page || 1;
|
||||
const sort = req.query.sort || sorting[0];
|
||||
if (!key) throw Error("Parameter key is required");
|
||||
if (!sorting.includes(sort)) throw Error("Invalid short: " + sorting.join(", "));
|
||||
|
||||
let actualAPI;
|
||||
if (!await mock(c.NHENTAI)) actualAPI = c.NHENTAI_IP_2;
|
||||
|
||||
const url = `${actualAPI}/api/galleries/search?query=${key}&sort=${sort}&page=${page}`;
|
||||
const data = await scrapeContent(url);
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
return res.json(data);
|
||||
} catch (err: any) {
|
||||
const e = {
|
||||
"success": false,
|
||||
"message": err.message
|
||||
};
|
||||
res.json(e);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { scrapeContent } from "../../scraper/pururin/pururinGetController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
export async function getPururin(req: any, res: any, next: any) {
|
||||
try {
|
||||
const book = req.query.book || "";
|
||||
if (!book) throw Error("Parameter book is required");
|
||||
if (isNaN(book)) throw Error("Value must be number");
|
||||
const url = `${c.PURURIN}/gallery/${book}/janda`;
|
||||
const data = await scrapeContent(url);
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
return res.json(data);
|
||||
} catch (err: any) {
|
||||
next(Error(err.message));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { scrapeContent } from "../../scraper/pururin/pururinSearchController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
const sorting = ["newest", "most-popular", "highest-rated", "most-viewed", "title", "random"];
|
||||
|
||||
export async function searchPururin(req: any, res: any, next: any) {
|
||||
try {
|
||||
const key = req.query.key || "";
|
||||
const page = req.query.page || 1;
|
||||
const sort = req.query.sort || sorting[0];
|
||||
if (!key) throw Error("Parameter key is required");
|
||||
if (!sorting.includes(sort)) throw Error("Invalid short: " + sorting.join(", "));
|
||||
const url = `${c.PURURIN}/search/${sort}?q=${key}&page=${page}`;
|
||||
const data = await scrapeContent(url);
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
return res.json(data);
|
||||
} catch (err: any) {
|
||||
next(Error(err.message));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { scrapeContent } from "../../scraper/simply-hentai/simply-hentaiGetController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { mock } from "../../utils/modifier";
|
||||
|
||||
export async function getSimplyhentai(req: any, res: any) {
|
||||
try {
|
||||
const book = req.query.book || "";
|
||||
if (!book) throw Error("Parameter book is required");
|
||||
|
||||
let actualAPI;
|
||||
if (!await mock(c.SIMPLY_HENTAI)) actualAPI = c.SIMPLY_HENTAI_PROXIFIED;
|
||||
|
||||
const url = `${actualAPI}/${book}`;
|
||||
const data = await scrapeContent(url);
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
return res.json(data);
|
||||
} catch (err: any) {
|
||||
const example = {
|
||||
"success": false,
|
||||
"message": err.message
|
||||
};
|
||||
res.json(example);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import express from "express";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import scrapeRoutes from "./router/endpoint";
|
||||
import { slow, limiter } from "./utils/limit-options";
|
||||
import { logger } from "./utils/logger";
|
||||
import { isNumeric } from "./utils/modifier";
|
||||
import * as pkg from "../package.json";
|
||||
const app = express();
|
||||
|
||||
|
||||
app.get("/", slow, limiter, (req, res) => {
|
||||
res.send({
|
||||
success: true,
|
||||
message: "Hi, I'm alive!",
|
||||
endpoint: "https://github.com/sinkaroid/jandapress/blob/master/README.md#routing",
|
||||
date: new Date().toLocaleString(),
|
||||
version: `${pkg.version}`,
|
||||
});
|
||||
logger.info({
|
||||
path: req.path,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
});
|
||||
|
||||
app.use(scrapeRoutes());
|
||||
app.get("/g/:id", slow, limiter, (req, res) => {
|
||||
if (!isNumeric(req.params.id)) throw Error("This path need required number to work");
|
||||
res.redirect(301, `https://nhentai.net/g/${req.params.id}`);
|
||||
});
|
||||
|
||||
|
||||
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
res.status(404);
|
||||
next(Error(`The page not found in path ${req.url} and method ${req.method}`));
|
||||
logger.error({
|
||||
path: req.url,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
});
|
||||
|
||||
app.use((error: any, res: Response) => {
|
||||
res.status(500).json({
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(process.env.PORT || 3000, () => console.log(`${pkg.name} running in port 3000`));
|
|
@ -0,0 +1,30 @@
|
|||
import { Router } from "express";
|
||||
import { searchHentaifox } from "../controller/hentaifox/hentaifoxSearch";
|
||||
import { getHentaifox } from "../controller/hentaifox/hentaifoxGet";
|
||||
import { getPururin } from "../controller/pururin/pururinGet";
|
||||
import { searchPururin } from "../controller/pururin/pururinSearch";
|
||||
import { searchHentai2read } from "../controller/hentai2read/hentai2readSearch";
|
||||
import { getHentai2read } from "../controller/hentai2read/hentai2readGet";
|
||||
import { getSimplyhentai } from "../controller/simply-hentai/simply-hentaiGet";
|
||||
import { getNhentai } from "../controller/nhentai/nhentaiGet";
|
||||
import { searchNhentai } from "../controller/nhentai/nhentaiSearch";
|
||||
import { relatedNhentai } from "../controller/nhentai/nhentaiRelated";
|
||||
import { slow, limiter } from "../utils/limit-options";
|
||||
|
||||
function scrapeRoutes() {
|
||||
const router = Router();
|
||||
router.get("/hentaifox/search", slow, limiter, searchHentaifox);
|
||||
router.get("/hentaifox/get", slow, limiter, getHentaifox);
|
||||
router.get("/pururin/get", slow, limiter, getPururin);
|
||||
router.get("/pururin/search", slow, limiter, searchPururin);
|
||||
router.get("/hentai2read/search", slow, limiter, searchHentai2read);
|
||||
router.get("/hentai2read/get", slow, limiter, getHentai2read);
|
||||
router.get("/simply-hentai/get", slow, limiter, getSimplyhentai);
|
||||
router.get("/nhentai/get", slow, limiter, getNhentai);
|
||||
router.get("/nhentai/search", slow, limiter, searchNhentai);
|
||||
router.get("/nhentai/related", slow, limiter, relatedNhentai);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
export default scrapeRoutes;
|
|
@ -0,0 +1,49 @@
|
|||
import { load } from "cheerio";
|
||||
import p from "phin";
|
||||
import c from "../../utils/options";
|
||||
|
||||
interface IHentai2readGet {
|
||||
title: string;
|
||||
id: string;
|
||||
image: string[];
|
||||
}
|
||||
|
||||
interface IHentai2readGetPush {
|
||||
data: object;
|
||||
main_url: string;
|
||||
current_url: string;
|
||||
next_url?: string;
|
||||
previus_url?: string;
|
||||
|
||||
}
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await p(url);
|
||||
const $ = load(res.body as Buffer);
|
||||
const script = $("script").map((i, el) => $(el).text()).get();
|
||||
|
||||
//find 'var gData = {}' inside script
|
||||
const gData = script.find(el => el.includes("var gData"));
|
||||
const gDataClean: string = gData?.replace(/[\s\S]*var gData = /, "").replace(/;/g, "").replace(/'/g, "\"") || "";
|
||||
const gDataJson = JSON.parse(gDataClean);
|
||||
const images = gDataJson.images.map((el: any) => `https://cdn-ngocok-static.sinxdr.workers.dev/hentai${el}`);
|
||||
|
||||
const objectData: IHentai2readGet = {
|
||||
title: gDataJson.title,
|
||||
id: url.replace(c.HENTAI2READ, ""),
|
||||
image: images
|
||||
};
|
||||
|
||||
const data: IHentai2readGetPush = {
|
||||
data: objectData,
|
||||
main_url: gDataJson.mainURL,
|
||||
current_url: gDataJson.currentURL,
|
||||
next_url: gDataJson.nextURL,
|
||||
previus_url: gDataJson.previousURL
|
||||
};
|
||||
return data;
|
||||
} catch (err: any) {
|
||||
throw Error(err.message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import { load } from "cheerio";
|
||||
import p from "phin";
|
||||
import c from "../../utils/options";
|
||||
import { getId } from "../../utils/modifier";
|
||||
|
||||
interface IHentai2readSearch {
|
||||
title: string;
|
||||
cover: string;
|
||||
id: string;
|
||||
link: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await p(url);
|
||||
const $ = load(res.body as Buffer);
|
||||
const title = $(".title-text").map((i, el) => $(el).text()).get();
|
||||
const imgSrc = $("img").map((i, el) => $(el).attr("data-src")).get();
|
||||
const id = $(".overlay-title").map((i, el) => $(el).children("a").attr("href")).get();
|
||||
const idClean = id.map(el => getId(el));
|
||||
|
||||
const content = [];
|
||||
for (const abc of title) {
|
||||
const objectData: IHentai2readSearch = {
|
||||
title: title[title.indexOf(abc)],
|
||||
cover: `${c.HENTAI2READ}${imgSrc[title.indexOf(abc)]}`,
|
||||
id: idClean[title.indexOf(abc)],
|
||||
link: `${c.HENTAI2READ}${idClean[title.indexOf(abc)]}`,
|
||||
message: "Required chapter number is mandatory",
|
||||
};
|
||||
content.push(objectData);
|
||||
|
||||
}
|
||||
|
||||
const data = {
|
||||
data: content,
|
||||
source: url,
|
||||
};
|
||||
return data;
|
||||
} catch (err: any) {
|
||||
throw Error(err.message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import { load } from "cheerio";
|
||||
import p from "phin";
|
||||
import c from "../../utils/options";
|
||||
|
||||
interface IHentaiFoxGet {
|
||||
title: string;
|
||||
id: number;
|
||||
tags: string[];
|
||||
type: string;
|
||||
total: number;
|
||||
image: string[];
|
||||
}
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await p(url);
|
||||
const $ = load(res.body as Buffer);
|
||||
const id = parseInt($("a.g_button")?.attr("href")?.split("/")[2] || "");
|
||||
|
||||
const category = $("a.tag_btn").map((i, abc) => {
|
||||
return $(abc)?.text()?.replace(/[0-9]/g, "").trim();
|
||||
}).get();
|
||||
|
||||
const imgSrc = $("img").map((i, el) => $(el).attr("data-src")).get();
|
||||
const parameterImg = imgSrc[0].split("/").slice(0, imgSrc[0].split("/").length - 1).join("/");
|
||||
const extensionImg = `.${imgSrc[0].split(".").slice(-1)[0]}`;
|
||||
|
||||
const info = $("span.i_text.pages").map((i, abc) => {
|
||||
return $(abc).text();
|
||||
}).get();
|
||||
|
||||
const pageCount = parseInt(info[0].replace(/[^0-9]/g, ""));
|
||||
const image = [];
|
||||
for (let i = 0; i < Number(pageCount); i++) {
|
||||
image.push(`${parameterImg}/${i + 1}${extensionImg}`);
|
||||
}
|
||||
const titleInfo = $("div.info").children("h1").text();
|
||||
|
||||
const objectData: IHentaiFoxGet = {
|
||||
title: titleInfo,
|
||||
id: id,
|
||||
tags: category,
|
||||
type: extensionImg,
|
||||
total: pageCount,
|
||||
image: image,
|
||||
};
|
||||
|
||||
const data = {
|
||||
data: objectData,
|
||||
source: `${c.HENTAIFOX}/gallery/${id}/`,
|
||||
};
|
||||
return data;
|
||||
} catch (err: any) {
|
||||
throw Error(err.message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import { load } from "cheerio";
|
||||
import p from "phin";
|
||||
import c from "../../utils/options";
|
||||
|
||||
interface IHentaiFoxSearch {
|
||||
title: string;
|
||||
cover: string;
|
||||
id: number;
|
||||
category: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await p(url);
|
||||
const $ = load(res.body as Buffer);
|
||||
|
||||
const title = $("h2.g_title").map((i, abc) => {
|
||||
return $(abc).text();
|
||||
}).get();
|
||||
|
||||
const link = $("h2.g_title").map((i, abc) => {
|
||||
//return number only
|
||||
return $(abc)?.children("a")?.attr("href")?.split("/")[2];
|
||||
}).get();
|
||||
|
||||
const category = $("h3.g_cat").map((i, abc) => {
|
||||
return $(abc)?.children("a")?.attr("href")?.split("/")[2];
|
||||
}).get();
|
||||
|
||||
const imgSrc = $("img").map((i, el) => $(el).attr("data-cfsrc")).get();
|
||||
const imgSrcClean = imgSrc.slice(0, imgSrc.length - 1);
|
||||
|
||||
const content = [];
|
||||
for (const abc of title) {
|
||||
const objectData: IHentaiFoxSearch = {
|
||||
title: title[title.indexOf(abc)],
|
||||
cover: imgSrcClean[title.indexOf(abc)],
|
||||
id: parseInt(link[title.indexOf(abc)]),
|
||||
category: category[title.indexOf(abc)],
|
||||
link: `${c.HENTAIFOX}/gallery/${link[title.indexOf(abc)]}`,
|
||||
};
|
||||
content.push(objectData);
|
||||
|
||||
}
|
||||
|
||||
const data = {
|
||||
data: content.filter(con => con.category !== ""),
|
||||
page: Number(url.split("&page=")[1]),
|
||||
sort: url.split("&sort=")[1].split("&")[0],
|
||||
source: url,
|
||||
};
|
||||
return data;
|
||||
} catch (err: any) {
|
||||
throw Error(err.message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
import p from "phin";
|
||||
import c from "../../utils/options";
|
||||
import { getDate, timeAgo } from "../../utils/modifier";
|
||||
import * as pkg from "../../../package.json";
|
||||
import { CookieJar } from "tough-cookie";
|
||||
import { HttpsCookieAgent } from "http-cookie-agent/http";
|
||||
import { config } from "dotenv";
|
||||
config();
|
||||
|
||||
interface INhentaiGet {
|
||||
title: string;
|
||||
optional_title: object;
|
||||
id: number;
|
||||
language: string;
|
||||
tags: string[];
|
||||
total: number;
|
||||
image: string[];
|
||||
num_pages: number;
|
||||
num_favorites: number;
|
||||
artist: string[];
|
||||
group: string;
|
||||
parodies: string;
|
||||
characters: string[];
|
||||
upload_date: string;
|
||||
}
|
||||
|
||||
const jar = new CookieJar();
|
||||
jar.setCookie(process.env.CF as string, "https://nhentai.net/");
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await p({
|
||||
url: url,
|
||||
core: {
|
||||
agent: new HttpsCookieAgent({ cookies: { jar, }, }),
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": `${pkg.name}/${pkg.version} Node.js/16.9.1`
|
||||
},
|
||||
parse: "json",
|
||||
});
|
||||
console.log(res.statusCode);
|
||||
|
||||
const GALLERY = "https://i.nhentai.net/galleries";
|
||||
const TYPE: any = {
|
||||
j: "jpg",
|
||||
p: "png",
|
||||
g: "gif",
|
||||
};
|
||||
|
||||
const dataRaw: any = res.body;
|
||||
const imagesRaw = dataRaw.images.pages;
|
||||
|
||||
const images: string[] = Object.keys(imagesRaw)
|
||||
.map((key: string) => imagesRaw[key].t);
|
||||
|
||||
const imageList = [];
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
imageList.push(`${GALLERY}/${dataRaw.media_id}/${i + 1}.${TYPE[images[i]]}`);
|
||||
}
|
||||
|
||||
//get all tags.name
|
||||
const tagsRaw = dataRaw.tags;
|
||||
const tags: string[] = Object.keys(tagsRaw).map((key: string) => tagsRaw[key].name);
|
||||
|
||||
const artistRaw = tagsRaw.filter((tag: any) => tag.type === "artist");
|
||||
const artist: string[] = artistRaw.map((tag: any) => tag.name) || [];
|
||||
|
||||
//find "type": "language" in tagsRaw
|
||||
const languageRaw = tagsRaw.find((tag: any) => tag.type === "language");
|
||||
const language = languageRaw ? languageRaw.name : null;
|
||||
|
||||
const parodiesRaw = tagsRaw.find((tag: any) => tag.type === "parody");
|
||||
const parodies = parodiesRaw ? parodiesRaw.name : null;
|
||||
|
||||
const groupRaw = tagsRaw.find((tag: any) => tag.type === "group");
|
||||
const group = groupRaw ? groupRaw.name : null;
|
||||
|
||||
//get all "type": "character" in tagsRaw
|
||||
const charactersRaw = tagsRaw.filter((tag: any) => tag.type === "character");
|
||||
const characters: string[] = charactersRaw.map((tag: any) => tag.name) || [];
|
||||
|
||||
const time = new Date(dataRaw.upload_date * 1000);
|
||||
|
||||
const objectData: INhentaiGet = {
|
||||
title: dataRaw.title.pretty,
|
||||
optional_title: {
|
||||
english: dataRaw.title.english,
|
||||
japanese: dataRaw.title.japanese,
|
||||
pretty: dataRaw.title.pretty,
|
||||
},
|
||||
id: dataRaw.id,
|
||||
language: language,
|
||||
tags: tags,
|
||||
total: imageList.length,
|
||||
image: imageList,
|
||||
num_pages: dataRaw.num_pages,
|
||||
num_favorites: dataRaw.num_favorites,
|
||||
artist: artist,
|
||||
group: group,
|
||||
parodies: parodies,
|
||||
characters: characters,
|
||||
upload_date: `${getDate(time)} (${timeAgo(time)})`,
|
||||
};
|
||||
|
||||
const data = {
|
||||
data: objectData,
|
||||
source: `${c.NHENTAI}/g/${dataRaw.id}`,
|
||||
};
|
||||
return data;
|
||||
} catch (err: any) {
|
||||
throw Error(err.message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import p from "phin";
|
||||
import c from "../../utils/options";
|
||||
import { getDate, timeAgo } from "../../utils/modifier";
|
||||
|
||||
interface INhentaiRelated {
|
||||
title: string;
|
||||
id: number;
|
||||
upload_date: string;
|
||||
total: number;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await p({ url: url, parse: "json" });
|
||||
const rawData: any = res.body;
|
||||
|
||||
const content = [];
|
||||
for (let i = 0; i < rawData.result.length; i++) {
|
||||
const time = new Date(rawData.result[i].upload_date * 1000);
|
||||
const objectData: INhentaiRelated = {
|
||||
title: rawData.result[i].title,
|
||||
id: rawData.result[i].id,
|
||||
upload_date: `${getDate(time)} (${timeAgo(time)})`,
|
||||
total: rawData.result[i].num_pages,
|
||||
tags: rawData.result[i].tags.map((tag: any) => tag.name),
|
||||
};
|
||||
content.push(objectData);
|
||||
}
|
||||
|
||||
const data = {
|
||||
data: content,
|
||||
source: url.replace(c.NHENTAI_IP, c.NHENTAI),
|
||||
};
|
||||
return data;
|
||||
|
||||
} catch (err: any) {
|
||||
throw Error(err.message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import p from "phin";
|
||||
import c from "../../utils/options";
|
||||
import { getDate, timeAgo } from "../../utils/modifier";
|
||||
|
||||
interface INhentaiSearch {
|
||||
title: string;
|
||||
id: number;
|
||||
language: string;
|
||||
upload_date: string;
|
||||
total: number;
|
||||
cover: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await p({ url: url, parse: "json" });
|
||||
const rawData: any = res.body;
|
||||
|
||||
const content = [];
|
||||
const GALLERY = "https://i.nhentai.net/galleries";
|
||||
const TYPE: any = {
|
||||
j: "jpg",
|
||||
p: "png",
|
||||
g: "gif",
|
||||
};
|
||||
for (let i = 0; i < rawData.result.length; i++) {
|
||||
const time = new Date(rawData.result[i].upload_date * 1000);
|
||||
const objectData: INhentaiSearch = {
|
||||
title: rawData.result[i].title,
|
||||
id: rawData.result[i].id,
|
||||
language: rawData.result[i].tags.find((tag: any) => tag.type === "language") ? rawData.result[i].tags.find((tag: any) => tag.type === "language").name : null,
|
||||
upload_date: `${getDate(time)} (${timeAgo(time)})`,
|
||||
total: rawData.result[i].num_pages,
|
||||
cover: `${GALLERY}/${rawData.result[i].media_id}/1.${TYPE[rawData.result[i].images.cover.t]}`,
|
||||
tags: rawData.result[i].tags.map((tag: any) => tag.name),
|
||||
};
|
||||
content.push(objectData);
|
||||
}
|
||||
|
||||
const data = {
|
||||
data: content,
|
||||
page: Number(url.split("&page=")[1]),
|
||||
sort: url.split("&sort=")[1].split("&")[0],
|
||||
source: url.replace(c.NHENTAI_IP, c.NHENTAI),
|
||||
};
|
||||
return data;
|
||||
|
||||
} catch (err: any) {
|
||||
throw Error(err.message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import { load } from "cheerio";
|
||||
import p from "phin";
|
||||
import c from "../../utils/options";
|
||||
import { getPururinInfo, getUrl } from "../../utils/modifier";
|
||||
|
||||
interface IGetPururin {
|
||||
title: string;
|
||||
id: number;
|
||||
tags: string[];
|
||||
extension: string;
|
||||
total: number;
|
||||
image: string[];
|
||||
}
|
||||
|
||||
interface IData{
|
||||
data: object;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await p(url);
|
||||
const $ = load(res.body);
|
||||
const title: string = $("div.content-wrapper h1").html() || "";
|
||||
|
||||
const tags: string[] = $("div.content-wrapper ul.list-inline li").map((i, abc) => {
|
||||
return getPururinInfo($(abc).text());
|
||||
}).get();
|
||||
|
||||
const cover = $("meta[property='og:image']").attr("content");
|
||||
const extension = `.${cover?.split(".").pop()}`;
|
||||
const total: number = parseInt($("gallery-thumbnails").attr(":total") || "0");
|
||||
const id: number = parseInt($("gallery-thumbnails").attr(":id") || "0");
|
||||
|
||||
const image = [];
|
||||
for (let i = 0; i < total; i++) {
|
||||
image.push(`${getUrl(cover?.replace("cover", `${i + 1}`) ?? "")}`);
|
||||
}
|
||||
|
||||
const objectData: IGetPururin = {
|
||||
title,
|
||||
id,
|
||||
tags,
|
||||
extension,
|
||||
total,
|
||||
image
|
||||
};
|
||||
|
||||
const data: IData = {
|
||||
data: objectData,
|
||||
source: `${c.PURURIN}/gallery/${id}/janda`
|
||||
};
|
||||
return data;
|
||||
} catch (err: any) {
|
||||
throw Error(err.message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import { load } from "cheerio";
|
||||
import p from "phin";
|
||||
import c from "../../utils/options";
|
||||
import { isText } from "domhandler";
|
||||
import { getPururinInfo, getPururinPageCount } from "../../utils/modifier";
|
||||
|
||||
interface ISearchPururin {
|
||||
title: string;
|
||||
cover: string;
|
||||
id: number;
|
||||
info: string;
|
||||
link: string;
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface IData {
|
||||
data: object;
|
||||
page: number;
|
||||
sort: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await p(url);
|
||||
const $ = load(res.body);
|
||||
const dataRaw = $("img.card-img-top");
|
||||
const info = $("div.info");
|
||||
|
||||
const infoBook = [];
|
||||
for (let i = 0; i < info.length; i++) {
|
||||
const child = info[i].children[0];
|
||||
if (isText(child)) {
|
||||
infoBook.push(getPururinInfo(child.data));
|
||||
}
|
||||
}
|
||||
|
||||
const content = [];
|
||||
for (const abc of dataRaw) {
|
||||
|
||||
const objectData: ISearchPururin = {
|
||||
title: abc.attribs["alt"],
|
||||
cover: abc.attribs["data-src"].replace(/^\/\//, "https://"),
|
||||
id: parseInt(abc.attribs["data-src"].split("data/")[1].split("/cover")[0]),
|
||||
info: infoBook[dataRaw.index(abc)],
|
||||
link: `${c.PURURIN}/gallery/${abc.attribs["data-src"].split("data/")[1].split("/cover")[0]}/janda`,
|
||||
total: getPururinPageCount(infoBook[dataRaw.index(abc)])
|
||||
};
|
||||
content.push(objectData);
|
||||
|
||||
}
|
||||
|
||||
const data: IData = {
|
||||
data: content,
|
||||
page: parseInt(url.split("&page=")[1]),
|
||||
sort: url.split("/search/")[1].split("?")[0],
|
||||
source: c.PURURIN
|
||||
};
|
||||
return data;
|
||||
} catch (err: any) {
|
||||
throw Error(err.message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { load } from "cheerio";
|
||||
import p from "phin";
|
||||
import c from "../../utils/options";
|
||||
|
||||
interface ISimplyHentaiGet {
|
||||
title: string;
|
||||
id: string;
|
||||
tags: string[];
|
||||
total: number;
|
||||
image: string[];
|
||||
language: string;
|
||||
}
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await p(url);
|
||||
const $ = load(res.body as Buffer);
|
||||
const script = $("script#__NEXT_DATA__");
|
||||
const json = JSON.parse(script.html() as string);
|
||||
const dataScrape: any = json.props.pageProps.data.pages;
|
||||
const images: string[] = Object.keys(dataScrape)
|
||||
.map((key: string) => dataScrape[key].sizes.full);
|
||||
const tagsRaw: any = json.props.pageProps.data.tags;
|
||||
const tags: string[] = Object.keys(tagsRaw).map((key: string) => tagsRaw[key].slug);
|
||||
const language = json.props.pageProps.data.language;
|
||||
const metaRaw= json.props.pageProps.meta;
|
||||
|
||||
const objectData: ISimplyHentaiGet = {
|
||||
title: metaRaw.title,
|
||||
id: url.replace(c.SIMPLY_HENTAI_PROXIFIED, ""),
|
||||
tags: tags,
|
||||
total: images.length,
|
||||
image: images,
|
||||
language: language.slug
|
||||
};
|
||||
|
||||
const data = {
|
||||
data: objectData,
|
||||
source: url,
|
||||
};
|
||||
return data;
|
||||
} catch (err: any) {
|
||||
throw Error(err.message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import rateLimit from "express-rate-limit";
|
||||
import slowDown from "express-slow-down";
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 50,
|
||||
message: "Too nasty, please slow down"
|
||||
});
|
||||
|
||||
const slow = slowDown({
|
||||
delayAfter: 50,
|
||||
windowMs: 15 * 60 * 1000,
|
||||
delayMs: 1000,
|
||||
maxDelayMs: 20000,
|
||||
});
|
||||
|
||||
export { limiter, slow };
|
|
@ -0,0 +1,8 @@
|
|||
import pino from "pino";
|
||||
|
||||
export const logger = pino({
|
||||
level: "info",
|
||||
transport: {
|
||||
target: "pino-pretty"
|
||||
},
|
||||
});
|
|
@ -0,0 +1,90 @@
|
|||
import p from "phin";
|
||||
import { CookieJar } from "tough-cookie";
|
||||
import { HttpsCookieAgent } from "http-cookie-agent/http";
|
||||
import { config } from "dotenv";
|
||||
import * as pkg from "../../package.json";
|
||||
config();
|
||||
|
||||
const jar = new CookieJar();
|
||||
jar.setCookie(process.env.CF as string, "https://nhentai.net/");
|
||||
|
||||
async function nhentaiStatus(): Promise<boolean> {
|
||||
const res = await p({
|
||||
url: "https://nhentai.net/api/galleries/search?query=futanari",
|
||||
core: {
|
||||
agent: new HttpsCookieAgent({ cookies: { jar, }, }),
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": `${pkg.name}/${pkg.version} Node.js/16.9.1`
|
||||
},
|
||||
});
|
||||
if (res.statusCode === 200) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getPururinInfo(value: string) {
|
||||
return value.replace(/\n/g, " ").replace(/\s\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
function getPururinPageCount(value: string) {
|
||||
const data = value.replace(/\n/g, " ").replace(/\s\s+/g, " ").trim().split(", ").pop();
|
||||
return Number(data?.split(" ")[0]);
|
||||
}
|
||||
|
||||
function getUrl(url: string) {
|
||||
return url.replace(/^\/\//, "https://");
|
||||
}
|
||||
|
||||
function getId(url: string) {
|
||||
return url.replace(/^https?:\/\/[^\\/]+/, "").replace(/\/$/, "");
|
||||
}
|
||||
|
||||
function getDate(date: Date) {
|
||||
return date.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function timeAgo(input: Date) {
|
||||
const date = new Date(input);
|
||||
const formatter: any = new Intl.RelativeTimeFormat("en");
|
||||
const ranges: { [key: string]: number } = {
|
||||
years: 3600 * 24 * 365,
|
||||
months: 3600 * 24 * 30,
|
||||
weeks: 3600 * 24 * 7,
|
||||
days: 3600 * 24,
|
||||
hours: 3600,
|
||||
minutes: 60,
|
||||
seconds: 1
|
||||
};
|
||||
const secondsElapsed = (date.getTime() - Date.now()) / 1000;
|
||||
for (const key in ranges) {
|
||||
if (ranges[key] < Math.abs(secondsElapsed)) {
|
||||
const delta = secondsElapsed / ranges[key];
|
||||
return formatter.format(Math.round(delta), key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function mock(url: string) {
|
||||
const site = await p({ url: url });
|
||||
if (site.statusCode === 200) {
|
||||
return true;
|
||||
} else if (site.statusCode === 308) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const isNumeric = (val: string) : boolean => {
|
||||
return !isNaN(Number(val));
|
||||
};
|
||||
|
||||
|
||||
export { getPururinInfo, getPururinPageCount, getUrl, getId, getDate, timeAgo, mock, nhentaiStatus };
|
|
@ -0,0 +1,10 @@
|
|||
export default {
|
||||
PURURIN: "https://pururin.to",
|
||||
HENTAIFOX: "https://hentaifox.com",
|
||||
HENTAI2READ: "https://hentai2read.com",
|
||||
SIMPLY_HENTAI: "https://simply-hentai.com",
|
||||
SIMPLY_HENTAI_PROXIFIED: "https://simplyh.sinxdr.workers.dev",
|
||||
NHENTAI: "https://nhentai.net",
|
||||
NHENTAI_IP: "http://35.186.156.165",
|
||||
NHENTAI_IP_2: "http://173.82.30.99:3002",
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
import p from "phin";
|
||||
import { CookieJar } from "tough-cookie";
|
||||
import { HttpsCookieAgent } from "http-cookie-agent/http";
|
||||
import { config } from "dotenv";
|
||||
import * as pkg from "../package.json";
|
||||
config();
|
||||
|
||||
const jar = new CookieJar();
|
||||
jar.setCookie(process.env.CF as string, "https://nhentai.net/");
|
||||
|
||||
async function test(): Promise<void> {
|
||||
const res = await p({
|
||||
url: "https://nhentai.net/api/galleries/search?query=futanari",
|
||||
core: {
|
||||
agent: new HttpsCookieAgent({ cookies: { jar, }, }),
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": `${pkg.name}/${pkg.version} Node.js/16.9.1`
|
||||
},
|
||||
});
|
||||
console.log(res.statusCode);
|
||||
}
|
||||
|
||||
test();
|
|
@ -0,0 +1,15 @@
|
|||
import c from "../src/utils/options";
|
||||
import p from "phin";
|
||||
|
||||
for (const url of
|
||||
[c.HENTAIFOX, c.PURURIN, c.HENTAI2READ, c.SIMPLY_HENTAI,
|
||||
c.SIMPLY_HENTAI_PROXIFIED, c.NHENTAI, c.NHENTAI_IP, c.NHENTAI_IP_2]) {
|
||||
p({ url }).then(res => {
|
||||
if (res.statusCode !== 200) {
|
||||
console.log(`${url} is not available, status code: ${res.statusCode}`);
|
||||
}
|
||||
else {
|
||||
console.log(`${url} is available, can be scraped`);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build",
|
||||
"allowJs": true,
|
||||
"target": "ESNext",
|
||||
"baseUrl": "src",
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"paths": {},
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"inlineSourceMap": true,
|
||||
"charset": "UTF-8",
|
||||
"downlevelIteration": true,
|
||||
"newLine": "lf",
|
||||
"strict": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"declaration": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": ["node_modules", "build", "out", "tmp", "logs", "test"]
|
||||
}
|
読み込み中…
新しいイシューから参照