From 649273db629c5cf4121a4006f5de9fce9580cfa8 Mon Sep 17 00:00:00 2001 From: ldm2060 Date: Thu, 5 Dec 2024 15:52:21 +0800 Subject: [PATCH] fix #696 --- src/header.json | 4 +- src/router/download.ts | 5 + src/rules/special/reprint/bilibili.ts | 238 ++++++++++++++++++++++++++ 3 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 src/rules/special/reprint/bilibili.ts diff --git a/src/header.json b/src/header.json index 0281fe7a..37075ea4 100644 --- a/src/header.json +++ b/src/header.json @@ -238,7 +238,8 @@ "*://www.esjzone.cc/detail/*", "*://www.fxshu.top/*/*.html", "*://xr.unionread.net/bookdetail/*", - "*://www.qu-la.com/booktxt/*/" + "*://www.qu-la.com/booktxt/*/", + "*://www.bilibili.com/read/readlist/*" ], "exclude": [ "*://www.jjwxc.net/onebook.php?novelid=*&chapterid=*", @@ -304,6 +305,7 @@ ], "connect": [ "self", + "bilibili.com", "lightnovel.us", "www.fxshu.top", "qidian.com", diff --git a/src/router/download.ts b/src/router/download.ts index e90b6b73..b6cc96fe 100644 --- a/src/router/download.ts +++ b/src/router/download.ts @@ -649,6 +649,11 @@ export async function getRule(): Promise { ruleClass = la42zw(); break; } + case "www.bilibili.com": { + const { Bilibili } = await import("../rules/special/reprint/bilibili"); + ruleClass = Bilibili; + break; + } case "www.esjzone.cc": case "www.esjzone.me": { const { esjzone } = await import("../rules/onePage/original/esjzone"); diff --git a/src/rules/special/reprint/bilibili.ts b/src/rules/special/reprint/bilibili.ts new file mode 100644 index 00000000..6e821f47 --- /dev/null +++ b/src/rules/special/reprint/bilibili.ts @@ -0,0 +1,238 @@ +import { getAttachment } from "../../../lib/attachments"; +import { cleanDOM } from "../../../lib/cleanDOM"; +import { getHtmlDOM } from "../../../lib/http"; +import { introDomHandle } from "../../../lib/rule"; +import { log } from "../../../log"; +import { Chapter } from "../../../main/Chapter"; +import { Book, BookAdditionalMetadate } from "../../../main/Book"; +import { BaseRuleClass } from "../../../rules"; +import { sandboxed } from "../../../lib/dom"; + + +export class Bilibili extends BaseRuleClass { + public constructor() { + super(); + this.attachmentMode = "TM"; + this.streamZip = true; + this.concurrencyLimit = 1; + this.maxRunLimit = 1; + } + + public async bookParse() { + const bookUrl = document.location.href; + const match = bookUrl.match(/readlist\/rl(\d+)/); + const bookID = match ? match[1] : null; + const bookname = ( + document.querySelector(".list-header .title") as HTMLHeadElement + ).innerText.trim(); + const author = ( + document.querySelector(".up-name") as HTMLAnchorElement + ).innerText + .trim(); + const introDom = document.querySelector("div.introduce") as HTMLElement; + const coverUrl = (document.querySelector("img.cover") as HTMLImageElement).src; + const [introduction, introductionHTML] = await introDomHandle(introDom); + + const additionalMetadate: BookAdditionalMetadate = {}; + if (coverUrl) { + getAttachment(coverUrl, this.attachmentMode, "cover-") + .then((coverClass) => { + additionalMetadate.cover = coverClass; + }) + .catch((error) => log.error(error)); + } + const chapters: Chapter[] = []; + const bookListURL = `https://api.bilibili.com/x/article/list/web/articles?id=${bookID}`; + const res = await fetch(bookListURL, { + headers: { + Accept: "application/json", + }, + method: "GET", + }); + const booklist = (await res.json()) as BookList; + let i = 0 + for (const article of booklist.data.articles) { + i++; + const chapter = new Chapter({ + bookUrl, + bookname, + chapterUrl: `https://www.bilibili.com/read/cv${article.id}`, + chapterNumber: i, + chapterName: article.title, + isVIP: false, + isPaid: false, + sectionName: null, + sectionNumber: null, + sectionChapterNumber: null, + chapterParse: this.chapterParse.bind(this), + charset: this.charset, + options: {chapterimg: article.image_urls}, + }); + + chapters.push(chapter); + } + return new Book({ + bookUrl, + bookname, + author, + introduction, + introductionHTML, + additionalMetadate, + chapters, + }); + } + public async chapterParse( + chapterUrl: string, + chapterName: string | null, + isVIP: boolean, + isPaid: boolean, + charset: string, + options: chapterOption + ) { + const chapterDom = await getHtmlDOM(chapterUrl); + const content = chapterDom.querySelector("div.opus-module-content") as HTMLElement; + const chapterimg = document.createElement("img"); + chapterimg.src = options["chapterimg"][0]; + content.insertBefore(chapterimg, content.firstChild); + const chapterimgTitle = document.createElement("p"); + chapterimgTitle.innerText = "章节封面插图"; + content.insertBefore(chapterimgTitle, chapterimg); + const { dom, text, images } = await cleanDOM(content, "TM"); + + return { + chapterName, + contentRaw: content, + contentText: text, + contentHTML: dom, + contentImages: images, + additionalMetadate: null, + }; + } +} + + +interface BookList { + code: number; + message: string; + ttl: number; + data: { + list: { + id: number; + mid: number; + name: string; + image_url: string; + update_time: number; + ctime: number; + publish_time: number; + summary: string; + words: number; + read: number; + articles_count: number; + state: number; + reason: string; + apply_time: string; + check_time: string; + }; + articles: { + id: number; + title: string; + state: number; + publish_time: number; + words: number; + image_urls: string[]; + category: { + id: number; + parent_id: number; + name: string; + }; + categories: { + id: number; + parent_id: number; + name: string; + }[]; + summary: string; + type: number; + dyn_id_str: string; + attributes: number; + author_uid: number; + stats: { + view: number; + favorite: number; + like: number; + dislike: number; + reply: number; + share: number; + coin: number; + dynamic: number; + }; + like_state: number; + }[]; + author: { + mid: number; + name: string; + face: string; + pendant: { + pid: number; + name: string; + image: string; + expire: number; + }; + official_verify: { + type: number; + desc: string; + }; + nameplate: { + nid: number; + name: string; + image: string; + image_small: string; + level: string; + condition: string; + }; + vip: { + type: number; + status: number; + due_date: number; + vip_pay_type: number; + theme_type: number; + label: { + path: string; + text: string; + label_theme: string; + }; + avatar_subscript: number; + nickname_color: string; + }; + fans: number; + level: number; + }; + last: { + id: number; + title: string; + state: number; + publish_time: number; + words: number; + image_urls: string[]; + category: { + id: number; + parent_id: number; + name: string; + }; + categories: { + id: number; + parent_id: number; + name: string; + }[]; + summary: string; + type: number; + dyn_id_str: string; + attributes: number; + author_uid: number; + }; + attention: boolean; + }; +} + +interface chapterOption{ + chapterimg: string[]; +} \ No newline at end of file